From f2a92ac62f5c8e72451bd86e77e46c05e75cf42e Mon Sep 17 00:00:00 2001 From: Dominik Csapak Date: Tue, 23 May 2023 09:08:46 +0200 Subject: [PATCH] import 8.41.0 source Signed-off-by: Dominik Csapak --- Makefile | 2 +- eslint/.eslintignore | 5 +- eslint/.eslintrc.js | 9 +- eslint/.github/CODEOWNERS | 2 + eslint/.github/CODEOWNERS.md | 17 - eslint/.github/ISSUE_TEMPLATE/bug-report.yml | 7 + eslint/.github/ISSUE_TEMPLATE/change.yml | 1 - eslint/.github/ISSUE_TEMPLATE/config.yml | 6 + eslint/.github/PULL_REQUEST_TEMPLATE.md | 2 +- eslint/.github/dependabot.yml | 2 + eslint/.github/workflows/add-to-triage.yml | 18 + eslint/.github/workflows/ci.yml | 17 +- eslint/.github/workflows/stale.yml | 10 +- eslint/.github/workflows/update-readme.yml | 33 + eslint/.npmrc | 1 + eslint/CHANGELOG.md | 358 ++ eslint/CONTRIBUTING.md | 12 +- eslint/Makefile.js | 38 +- eslint/README.md | 112 +- eslint/conf/rule-type-list.json | 4 +- eslint/docs/.eleventy.js | 78 +- eslint/docs/.stylelintrc.json | 33 + eslint/docs/README.md | 18 + eslint/docs/package.json | 38 +- eslint/docs/postcss.config.js | 9 + eslint/docs/src/_data/config.json | 3 +- .../docs/src/_data/further_reading_links.json | 21 + eslint/docs/src/_data/languages.json | 4 +- eslint/docs/src/_data/layout.js | 1 + eslint/docs/src/_data/links.json | 13 +- eslint/docs/src/_data/rule_versions.json | 5 +- eslint/docs/src/_data/rules.json | 39 +- eslint/docs/src/_data/rules_meta.json | 614 ++-- eslint/docs/src/_data/sites/en.yml | 5 +- eslint/docs/src/_data/sites/zh-hans.yml | 116 + .../components/language-switcher.html | 8 +- .../docs/src/_includes/components/logo.html | 2 +- .../components/nav-version-switcher.html | 7 +- .../components/rule-categories.macro.html | 10 +- .../src/_includes/components/rule.macro.html | 8 +- .../_includes/components/social-icons.html | 25 +- .../components/version-switcher.html | 5 +- eslint/docs/src/_includes/layouts/base.html | 34 +- eslint/docs/src/_includes/layouts/doc.html | 3 + .../src/_includes/partials/versions-list.html | 5 +- .../src/_plugins/md-syntax-highlighter.js | 91 + eslint/docs/src/about/index.md | 1 - eslint/docs/src/assets/js/main.js | 41 +- eslint/docs/src/assets/js/scroll-up-btn.js | 13 + eslint/docs/src/assets/js/search.js | 58 +- eslint/docs/src/assets/scss/carbon-ads.scss | 12 +- .../src/assets/scss/components/alert.scss | 21 +- .../src/assets/scss/components/buttons.scss | 4 +- .../assets/scss/components/docs-index.scss | 2 - .../scss/components/docs-navigation.scss | 8 - .../docs/src/assets/scss/components/hero.scss | 6 +- .../src/assets/scss/components/index.scss | 1 - .../scss/components/language-switcher.scss | 5 +- .../src/assets/scss/components/resources.scss | 6 +- .../src/assets/scss/components/rules.scss | 13 +- .../src/assets/scss/components/search.scss | 12 +- .../assets/scss/components/social-icons.scss | 1 - .../docs/src/assets/scss/components/tabs.scss | 7 +- .../scss/components/theme-switcher.scss | 9 +- .../docs/src/assets/scss/components/toc.scss | 35 +- eslint/docs/src/assets/scss/docs-header.scss | 5 +- eslint/docs/src/assets/scss/docs.scss | 56 +- eslint/docs/src/assets/scss/forms.scss | 9 +- eslint/docs/src/assets/scss/foundations.scss | 28 +- eslint/docs/src/assets/scss/languages.scss | 7 +- eslint/docs/src/assets/scss/print.scss | 44 +- eslint/docs/src/assets/scss/styles.scss | 64 +- .../src/assets/scss/syntax-highlighter.scss | 78 +- .../docs/src/assets/scss/tokens/spacing.scss | 8 - .../docs/src/assets/scss/tokens/themes.scss | 112 +- .../src/assets/scss/tokens/typography.scss | 63 +- eslint/docs/src/assets/scss/tokens/ui.scss | 7 +- eslint/docs/src/assets/scss/utilities.scss | 5 +- eslint/docs/src/assets/scss/versions.scss | 7 +- .../architecture/index.md | 9 +- .../code-conventions.md | 3 +- eslint/docs/src/contribute/code-of-conduct.md | 10 + eslint/docs/src/contribute/core-rules.md | 110 + .../development-environment.md | 45 +- .../governance.md | 19 +- .../contributing => contribute}/index.md | 49 +- .../package-json-conventions.md | 31 +- .../propose-new-rule.md} | 13 +- .../propose-rule-change.md} | 13 +- .../pull-requests.md | 23 +- .../report-bugs.md} | 13 +- .../report-security-vulnerability.md | 10 + .../request-change.md} | 11 +- .../unit-tests.md => contribute/tests.md} | 10 +- .../work-on-issue.md} | 13 +- eslint/docs/src/developer-guide/index.md | 55 - .../src/developer-guide/shareable-configs.md | 219 -- .../docs/src/developer-guide/source-code.md | 53 - .../working-with-custom-parsers.md | 82 - .../developer-guide/working-with-plugins.md | 249 -- .../src/developer-guide/working-with-rules.md | 777 ----- .../code-path-analysis.md | 52 +- .../custom-formatters.md} | 372 +- eslint/docs/src/extend/custom-parsers.md | 151 + eslint/docs/src/extend/custom-processors.md | 179 + .../custom-rules-deprecated.md} | 17 +- eslint/docs/src/extend/custom-rules.md | 798 +++++ eslint/docs/src/extend/index.md | 50 + eslint/docs/src/extend/plugins.md | 195 ++ .../scope-manager-interface.md | 1 - .../{developer-guide => extend}/selectors.md | 17 +- eslint/docs/src/extend/shareable-configs.md | 237 ++ eslint/docs/src/extend/ways-to-extend.md | 60 + .../nodejs-api.md | 137 +- eslint/docs/src/library/rule-categories.md | 2 +- eslint/docs/src/maintain/index.md | 30 + .../issues.md => maintain/manage-issues.md} | 105 +- .../manage-releases.md} | 35 +- eslint/docs/src/maintain/overview.md | 44 + .../review-pull-requests.md} | 23 +- .../working-groups.md | 9 +- eslint/docs/src/maintainer-guide/index.md | 31 - eslint/docs/src/pages/index.md | 14 +- eslint/docs/src/pages/rules.md | 9 +- eslint/docs/src/rules/accessor-pairs.md | 1 - .../docs/src/rules/array-bracket-newline.md | 1 - .../docs/src/rules/array-bracket-spacing.md | 1 - .../docs/src/rules/array-callback-return.md | 4 +- .../docs/src/rules/array-element-newline.md | 1 - eslint/docs/src/rules/arrow-body-style.md | 1 - eslint/docs/src/rules/arrow-parens.md | 3 +- eslint/docs/src/rules/arrow-spacing.md | 1 - eslint/docs/src/rules/block-scoped-var.md | 1 - eslint/docs/src/rules/block-spacing.md | 1 - eslint/docs/src/rules/brace-style.md | 1 - eslint/docs/src/rules/callback-return.md | 3 +- eslint/docs/src/rules/camelcase.md | 3 +- eslint/docs/src/rules/capitalized-comments.md | 1 - .../docs/src/rules/class-methods-use-this.md | 5 +- eslint/docs/src/rules/comma-dangle.md | 1 - eslint/docs/src/rules/comma-spacing.md | 1 - eslint/docs/src/rules/comma-style.md | 1 - eslint/docs/src/rules/complexity.md | 1 - .../src/rules/computed-property-spacing.md | 1 - eslint/docs/src/rules/consistent-return.md | 1 - eslint/docs/src/rules/consistent-this.md | 1 - eslint/docs/src/rules/constructor-super.md | 1 - eslint/docs/src/rules/curly.md | 1 - eslint/docs/src/rules/default-case-last.md | 1 - eslint/docs/src/rules/default-case.md | 1 - eslint/docs/src/rules/default-param-last.md | 1 - eslint/docs/src/rules/dot-location.md | 1 - eslint/docs/src/rules/dot-notation.md | 1 - eslint/docs/src/rules/eol-last.md | 1 - eslint/docs/src/rules/eqeqeq.md | 3 +- eslint/docs/src/rules/for-direction.md | 1 - eslint/docs/src/rules/func-call-spacing.md | 1 - eslint/docs/src/rules/func-name-matching.md | 3 +- eslint/docs/src/rules/func-names.md | 1 - eslint/docs/src/rules/func-style.md | 1 - .../rules/function-call-argument-newline.md | 1 - .../docs/src/rules/function-paren-newline.md | 1 - .../docs/src/rules/generator-star-spacing.md | 13 +- eslint/docs/src/rules/generator-star.md | 1 - eslint/docs/src/rules/getter-return.md | 1 - eslint/docs/src/rules/global-require.md | 3 +- eslint/docs/src/rules/global-strict.md | 1 - .../docs/src/rules/grouped-accessor-pairs.md | 1 - eslint/docs/src/rules/guard-for-in.md | 12 +- eslint/docs/src/rules/handle-callback-err.md | 3 +- eslint/docs/src/rules/id-blacklist.md | 1 - eslint/docs/src/rules/id-denylist.md | 3 +- eslint/docs/src/rules/id-length.md | 3 +- eslint/docs/src/rules/id-match.md | 5 +- .../src/rules/implicit-arrow-linebreak.md | 1 - eslint/docs/src/rules/indent-legacy.md | 3 +- eslint/docs/src/rules/indent.md | 3 +- eslint/docs/src/rules/init-declarations.md | 1 - eslint/docs/src/rules/jsx-quotes.md | 1 - eslint/docs/src/rules/key-spacing.md | 1 - eslint/docs/src/rules/keyword-spacing.md | 1 - .../docs/src/rules/line-comment-position.md | 1 - eslint/docs/src/rules/linebreak-style.md | 1 - eslint/docs/src/rules/lines-around-comment.md | 31 +- .../docs/src/rules/lines-around-directive.md | 1 - .../src/rules/lines-between-class-members.md | 1 - .../src/rules/logical-assignment-operators.md | 130 + eslint/docs/src/rules/max-classes-per-file.md | 1 - eslint/docs/src/rules/max-depth.md | 1 - eslint/docs/src/rules/max-len.md | 1 - .../docs/src/rules/max-lines-per-function.md | 1 - eslint/docs/src/rules/max-lines.md | 1 - eslint/docs/src/rules/max-nested-callbacks.md | 1 - eslint/docs/src/rules/max-params.md | 1 - .../docs/src/rules/max-statements-per-line.md | 1 - eslint/docs/src/rules/max-statements.md | 1 - .../docs/src/rules/multiline-comment-style.md | 40 +- eslint/docs/src/rules/multiline-ternary.md | 1 - eslint/docs/src/rules/new-cap.md | 2 +- eslint/docs/src/rules/new-parens.md | 1 - eslint/docs/src/rules/newline-after-var.md | 1 - .../docs/src/rules/newline-before-return.md | 1 - .../src/rules/newline-per-chained-call.md | 1 - eslint/docs/src/rules/no-alert.md | 1 - eslint/docs/src/rules/no-array-constructor.md | 1 - eslint/docs/src/rules/no-arrow-condition.md | 1 - .../src/rules/no-async-promise-executor.md | 1 - eslint/docs/src/rules/no-await-in-loop.md | 1 - eslint/docs/src/rules/no-bitwise.md | 1 - .../docs/src/rules/no-buffer-constructor.md | 3 +- eslint/docs/src/rules/no-caller.md | 1 - eslint/docs/src/rules/no-case-declarations.md | 1 - eslint/docs/src/rules/no-catch-shadow.md | 1 - eslint/docs/src/rules/no-class-assign.md | 1 - eslint/docs/src/rules/no-comma-dangle.md | 1 - eslint/docs/src/rules/no-compare-neg-zero.md | 1 - eslint/docs/src/rules/no-cond-assign.md | 1 - eslint/docs/src/rules/no-confusing-arrow.md | 1 - eslint/docs/src/rules/no-console.md | 1 - eslint/docs/src/rules/no-const-assign.md | 1 - .../rules/no-constant-binary-expression.md | 7 +- .../docs/src/rules/no-constant-condition.md | 9 +- .../docs/src/rules/no-constructor-return.md | 1 - eslint/docs/src/rules/no-continue.md | 1 - eslint/docs/src/rules/no-control-regex.md | 1 - eslint/docs/src/rules/no-debugger.md | 1 - eslint/docs/src/rules/no-delete-var.md | 1 - eslint/docs/src/rules/no-div-regex.md | 5 +- eslint/docs/src/rules/no-dupe-args.md | 1 - .../docs/src/rules/no-dupe-class-members.md | 1 - eslint/docs/src/rules/no-dupe-else-if.md | 1 - eslint/docs/src/rules/no-dupe-keys.md | 1 - eslint/docs/src/rules/no-duplicate-case.md | 1 - eslint/docs/src/rules/no-duplicate-imports.md | 1 - eslint/docs/src/rules/no-else-return.md | 1 - .../src/rules/no-empty-character-class.md | 1 - eslint/docs/src/rules/no-empty-class.md | 1 - eslint/docs/src/rules/no-empty-function.md | 1 - eslint/docs/src/rules/no-empty-label.md | 1 - eslint/docs/src/rules/no-empty-pattern.md | 1 - .../docs/src/rules/no-empty-static-block.md | 56 + eslint/docs/src/rules/no-empty.md | 1 - eslint/docs/src/rules/no-eq-null.md | 1 - eslint/docs/src/rules/no-eval.md | 1 - eslint/docs/src/rules/no-ex-assign.md | 1 - eslint/docs/src/rules/no-extend-native.md | 1 - eslint/docs/src/rules/no-extra-bind.md | 1 - .../docs/src/rules/no-extra-boolean-cast.md | 1 - eslint/docs/src/rules/no-extra-label.md | 1 - eslint/docs/src/rules/no-extra-parens.md | 34 +- eslint/docs/src/rules/no-extra-semi.md | 1 - eslint/docs/src/rules/no-extra-strict.md | 1 - eslint/docs/src/rules/no-fallthrough.md | 5 +- eslint/docs/src/rules/no-floating-decimal.md | 1 - eslint/docs/src/rules/no-func-assign.md | 1 - eslint/docs/src/rules/no-global-assign.md | 5 +- eslint/docs/src/rules/no-implicit-coercion.md | 3 +- eslint/docs/src/rules/no-implicit-globals.md | 22 +- eslint/docs/src/rules/no-implied-eval.md | 1 - eslint/docs/src/rules/no-import-assign.md | 1 - eslint/docs/src/rules/no-inline-comments.md | 1 - .../docs/src/rules/no-inner-declarations.md | 1 - eslint/docs/src/rules/no-invalid-regexp.md | 1 - eslint/docs/src/rules/no-invalid-this.md | 1 - .../docs/src/rules/no-irregular-whitespace.md | 1 - eslint/docs/src/rules/no-iterator.md | 1 - eslint/docs/src/rules/no-label-var.md | 1 - eslint/docs/src/rules/no-labels.md | 1 - eslint/docs/src/rules/no-lone-blocks.md | 1 - eslint/docs/src/rules/no-lonely-if.md | 1 - eslint/docs/src/rules/no-loop-func.md | 1 - eslint/docs/src/rules/no-loss-of-precision.md | 1 - eslint/docs/src/rules/no-magic-numbers.md | 40 +- .../rules/no-misleading-character-class.md | 1 - eslint/docs/src/rules/no-mixed-operators.md | 1 - eslint/docs/src/rules/no-mixed-requires.md | 3 +- .../src/rules/no-mixed-spaces-and-tabs.md | 1 - eslint/docs/src/rules/no-multi-assign.md | 1 - eslint/docs/src/rules/no-multi-spaces.md | 1 - eslint/docs/src/rules/no-multi-str.md | 1 - .../docs/src/rules/no-multiple-empty-lines.md | 47 +- eslint/docs/src/rules/no-native-reassign.md | 5 +- eslint/docs/src/rules/no-negated-condition.md | 1 - eslint/docs/src/rules/no-negated-in-lhs.md | 1 - eslint/docs/src/rules/no-nested-ternary.md | 1 - eslint/docs/src/rules/no-new-func.md | 1 - .../src/rules/no-new-native-nonconstructor.md | 74 + eslint/docs/src/rules/no-new-object.md | 1 - eslint/docs/src/rules/no-new-require.md | 3 +- eslint/docs/src/rules/no-new-symbol.md | 1 - eslint/docs/src/rules/no-new-wrappers.md | 1 - eslint/docs/src/rules/no-new.md | 3 +- .../src/rules/no-nonoctal-decimal-escape.md | 1 - eslint/docs/src/rules/no-obj-calls.md | 19 +- eslint/docs/src/rules/no-octal-escape.md | 1 - eslint/docs/src/rules/no-octal.md | 1 - eslint/docs/src/rules/no-param-reassign.md | 1 - eslint/docs/src/rules/no-path-concat.md | 3 +- eslint/docs/src/rules/no-plusplus.md | 1 - eslint/docs/src/rules/no-process-env.md | 3 +- eslint/docs/src/rules/no-process-exit.md | 3 +- .../src/rules/no-promise-executor-return.md | 1 - eslint/docs/src/rules/no-proto.md | 1 - .../docs/src/rules/no-prototype-builtins.md | 1 - eslint/docs/src/rules/no-redeclare.md | 1 - eslint/docs/src/rules/no-regex-spaces.md | 1 - eslint/docs/src/rules/no-reserved-keys.md | 1 - .../docs/src/rules/no-restricted-exports.md | 100 +- .../docs/src/rules/no-restricted-globals.md | 1 - .../docs/src/rules/no-restricted-imports.md | 1 - .../docs/src/rules/no-restricted-modules.md | 3 +- .../src/rules/no-restricted-properties.md | 1 - eslint/docs/src/rules/no-restricted-syntax.md | 3 +- eslint/docs/src/rules/no-return-assign.md | 1 - eslint/docs/src/rules/no-return-await.md | 1 - eslint/docs/src/rules/no-script-url.md | 1 - eslint/docs/src/rules/no-self-assign.md | 1 - eslint/docs/src/rules/no-self-compare.md | 1 - eslint/docs/src/rules/no-sequences.md | 1 - eslint/docs/src/rules/no-setter-return.md | 1 - .../src/rules/no-shadow-restricted-names.md | 1 - eslint/docs/src/rules/no-shadow.md | 1 - eslint/docs/src/rules/no-space-before-semi.md | 1 - eslint/docs/src/rules/no-spaced-func.md | 1 - eslint/docs/src/rules/no-sparse-arrays.md | 1 - eslint/docs/src/rules/no-sync.md | 3 +- eslint/docs/src/rules/no-tabs.md | 1 - .../src/rules/no-template-curly-in-string.md | 1 - eslint/docs/src/rules/no-ternary.md | 1 - eslint/docs/src/rules/no-this-before-super.md | 1 - eslint/docs/src/rules/no-throw-literal.md | 1 - eslint/docs/src/rules/no-trailing-spaces.md | 1 - eslint/docs/src/rules/no-undef-init.md | 1 - eslint/docs/src/rules/no-undef.md | 5 +- eslint/docs/src/rules/no-undefined.md | 5 +- eslint/docs/src/rules/no-underscore-dangle.md | 47 +- .../docs/src/rules/no-unexpected-multiline.md | 1 - .../src/rules/no-unmodified-loop-condition.md | 1 - eslint/docs/src/rules/no-unneeded-ternary.md | 1 - eslint/docs/src/rules/no-unreachable-loop.md | 1 - eslint/docs/src/rules/no-unreachable.md | 1 - eslint/docs/src/rules/no-unsafe-finally.md | 1 - eslint/docs/src/rules/no-unsafe-negation.md | 1 - .../src/rules/no-unsafe-optional-chaining.md | 1 - .../docs/src/rules/no-unused-expressions.md | 1 - eslint/docs/src/rules/no-unused-labels.md | 1 - .../rules/no-unused-private-class-members.md | 1 - eslint/docs/src/rules/no-unused-vars.md | 121 +- eslint/docs/src/rules/no-use-before-define.md | 1 - .../src/rules/no-useless-backreference.md | 1 - eslint/docs/src/rules/no-useless-call.md | 1 - eslint/docs/src/rules/no-useless-catch.md | 1 - .../docs/src/rules/no-useless-computed-key.md | 1 - eslint/docs/src/rules/no-useless-concat.md | 1 - .../docs/src/rules/no-useless-constructor.md | 1 - eslint/docs/src/rules/no-useless-escape.md | 1 - eslint/docs/src/rules/no-useless-rename.md | 1 - eslint/docs/src/rules/no-useless-return.md | 1 - eslint/docs/src/rules/no-var.md | 1 - eslint/docs/src/rules/no-void.md | 1 - eslint/docs/src/rules/no-warning-comments.md | 1 - .../rules/no-whitespace-before-property.md | 1 - eslint/docs/src/rules/no-with.md | 1 - eslint/docs/src/rules/no-wrap-func.md | 1 - .../rules/nonblock-statement-body-position.md | 5 +- eslint/docs/src/rules/object-curly-newline.md | 1 - eslint/docs/src/rules/object-curly-spacing.md | 1 - .../docs/src/rules/object-property-newline.md | 1 - eslint/docs/src/rules/object-shorthand.md | 1 - .../src/rules/one-var-declaration-per-line.md | 1 - eslint/docs/src/rules/one-var.md | 1 - eslint/docs/src/rules/operator-assignment.md | 1 - eslint/docs/src/rules/operator-linebreak.md | 1 - eslint/docs/src/rules/padded-blocks.md | 1 - .../rules/padding-line-between-statements.md | 1 - .../docs/src/rules/prefer-arrow-callback.md | 1 - eslint/docs/src/rules/prefer-const.md | 3 +- eslint/docs/src/rules/prefer-destructuring.md | 1 - .../rules/prefer-exponentiation-operator.md | 1 - .../src/rules/prefer-named-capture-group.md | 1 - .../docs/src/rules/prefer-numeric-literals.md | 1 - .../docs/src/rules/prefer-object-has-own.md | 1 - eslint/docs/src/rules/prefer-object-spread.md | 1 - .../src/rules/prefer-promise-reject-errors.md | 1 - eslint/docs/src/rules/prefer-reflect.md | 44 +- .../docs/src/rules/prefer-regex-literals.md | 1 - eslint/docs/src/rules/prefer-rest-params.md | 1 - eslint/docs/src/rules/prefer-spread.md | 1 - eslint/docs/src/rules/prefer-template.md | 1 - eslint/docs/src/rules/quote-props.md | 1 - eslint/docs/src/rules/quotes.md | 1 - eslint/docs/src/rules/radix.md | 1 - .../docs/src/rules/require-atomic-updates.md | 1 - eslint/docs/src/rules/require-await.md | 1 - eslint/docs/src/rules/require-jsdoc.md | 1 - .../docs/src/rules/require-unicode-regexp.md | 1 - eslint/docs/src/rules/require-yield.md | 1 - eslint/docs/src/rules/rest-spread-spacing.md | 3 +- eslint/docs/src/rules/semi-spacing.md | 1 - eslint/docs/src/rules/semi-style.md | 1 - eslint/docs/src/rules/semi.md | 78 +- eslint/docs/src/rules/sort-imports.md | 1 - eslint/docs/src/rules/sort-keys.md | 1 - eslint/docs/src/rules/sort-vars.md | 1 - .../src/rules/space-after-function-name.md | 1 - eslint/docs/src/rules/space-after-keywords.md | 3 +- eslint/docs/src/rules/space-before-blocks.md | 1 - .../src/rules/space-before-function-paren.md | 4 +- .../space-before-function-parentheses.md | 1 - .../docs/src/rules/space-before-keywords.md | 3 +- eslint/docs/src/rules/space-in-brackets.md | 1 - eslint/docs/src/rules/space-in-parens.md | 1 - eslint/docs/src/rules/space-infix-ops.md | 1 - .../docs/src/rules/space-return-throw-case.md | 3 +- eslint/docs/src/rules/space-unary-ops.md | 1 - eslint/docs/src/rules/space-unary-word-ops.md | 1 - eslint/docs/src/rules/spaced-comment.md | 1 - eslint/docs/src/rules/spaced-line-comment.md | 1 - eslint/docs/src/rules/strict.md | 9 +- eslint/docs/src/rules/switch-colon-spacing.md | 1 - eslint/docs/src/rules/symbol-description.md | 1 - .../docs/src/rules/template-curly-spacing.md | 1 - eslint/docs/src/rules/template-tag-spacing.md | 1 - eslint/docs/src/rules/unicode-bom.md | 1 - eslint/docs/src/rules/use-isnan.md | 1 - eslint/docs/src/rules/valid-jsdoc.md | 1 - eslint/docs/src/rules/valid-typeof.md | 1 - eslint/docs/src/rules/vars-on-top.md | 1 - eslint/docs/src/rules/wrap-iife.md | 1 - eslint/docs/src/rules/wrap-regex.md | 1 - eslint/docs/src/rules/yield-star-spacing.md | 1 - eslint/docs/src/rules/yoda.md | 1 - eslint/docs/src/static/feed.njk | 1 + eslint/docs/src/static/sitemap.njk | 1 + eslint/docs/src/use/command-line-interface.md | 763 ++++ .../configure}/configuration-files-new.md | 183 +- .../configure}/configuration-files.md | 59 +- .../configure/ignore.md} | 43 +- .../configuring => use/configure}/index.md | 40 +- .../configure}/language-options.md | 37 +- eslint/docs/src/use/configure/parser.md | 38 + .../configuring => use/configure}/plugins.md | 214 +- .../configuring => use/configure}/rules.md | 51 +- eslint/docs/src/use/core-concepts.md | 82 + .../formatters/html-formatter-example.html | 167 +- .../formatters/html-formatter-example.json | 3 + eslint/docs/src/use/formatters/index.md | 296 ++ eslint/docs/src/use/getting-started.md | 152 + eslint/docs/src/{user-guide => use}/index.md | 29 +- .../src/{user-guide => use}/integrations.md | 40 +- .../migrate-to-8.0.0.md} | 35 +- .../migrating-from-jscs.md | 5 +- .../{user-guide => use}/migrating-to-1.0.0.md | 139 +- .../{user-guide => use}/migrating-to-2.0.0.md | 33 +- .../{user-guide => use}/migrating-to-3.0.0.md | 13 +- .../{user-guide => use}/migrating-to-4.0.0.md | 17 +- .../{user-guide => use}/migrating-to-5.0.0.md | 25 +- .../{user-guide => use}/migrating-to-6.0.0.md | 31 +- .../{user-guide => use}/migrating-to-7.0.0.md | 55 +- .../{user-guide => use}/rule-deprecation.md | 1 - .../src/user-guide/command-line-interface.md | 585 ---- .../docs/src/user-guide/formatters/index.md | 243 -- eslint/docs/src/user-guide/getting-started.md | 83 - eslint/docs/tools/validate-links.js | 57 + eslint/eslint.config.js | 30 +- eslint/lib/cli-engine/cli-engine.js | 8 +- eslint/lib/cli-engine/file-enumerator.js | 14 +- .../formatters/formatters-meta.json | 46 + eslint/lib/cli-engine/formatters/html.js | 137 +- eslint/lib/cli.js | 49 +- eslint/lib/config/default-config.js | 9 +- eslint/lib/config/flat-config-array.js | 96 +- eslint/lib/config/flat-config-schema.js | 120 +- eslint/lib/eslint/eslint.js | 7 +- eslint/lib/eslint/flat-eslint.js | 333 +- eslint/lib/linter/apply-disable-directives.js | 12 +- eslint/lib/linter/config-comment-parser.js | 11 +- eslint/lib/linter/linter.js | 136 +- eslint/lib/linter/report-translator.js | 23 +- eslint/lib/options.js | 28 +- eslint/lib/rule-tester/flat-rule-tester.js | 24 +- eslint/lib/rule-tester/rule-tester.js | 26 +- eslint/lib/rules/accessor-pairs.js | 4 +- eslint/lib/rules/array-bracket-newline.js | 4 +- eslint/lib/rules/array-bracket-spacing.js | 4 +- eslint/lib/rules/array-callback-return.js | 6 +- eslint/lib/rules/array-element-newline.js | 5 +- eslint/lib/rules/arrow-body-style.js | 4 +- eslint/lib/rules/arrow-parens.js | 4 +- eslint/lib/rules/arrow-spacing.js | 4 +- eslint/lib/rules/block-scoped-var.js | 5 +- eslint/lib/rules/block-spacing.js | 4 +- eslint/lib/rules/brace-style.js | 4 +- eslint/lib/rules/callback-return.js | 4 +- eslint/lib/rules/camelcase.js | 11 +- eslint/lib/rules/capitalized-comments.js | 4 +- eslint/lib/rules/class-methods-use-this.js | 4 +- eslint/lib/rules/comma-dangle.js | 10 +- eslint/lib/rules/comma-spacing.js | 4 +- eslint/lib/rules/comma-style.js | 4 +- eslint/lib/rules/complexity.js | 2 +- eslint/lib/rules/computed-property-spacing.js | 4 +- eslint/lib/rules/consistent-return.js | 6 +- eslint/lib/rules/consistent-this.js | 8 +- eslint/lib/rules/constructor-super.js | 2 +- eslint/lib/rules/curly.js | 4 +- eslint/lib/rules/default-case-last.js | 2 +- eslint/lib/rules/default-case.js | 4 +- eslint/lib/rules/default-param-last.js | 2 +- eslint/lib/rules/dot-location.js | 4 +- eslint/lib/rules/dot-notation.js | 4 +- eslint/lib/rules/eol-last.js | 4 +- eslint/lib/rules/eqeqeq.js | 4 +- eslint/lib/rules/for-direction.js | 4 +- eslint/lib/rules/func-call-spacing.js | 4 +- eslint/lib/rules/func-name-matching.js | 6 +- eslint/lib/rules/func-names.js | 6 +- eslint/lib/rules/func-style.js | 2 +- .../rules/function-call-argument-newline.js | 4 +- eslint/lib/rules/function-paren-newline.js | 4 +- eslint/lib/rules/generator-star-spacing.js | 4 +- eslint/lib/rules/getter-return.js | 26 +- eslint/lib/rules/global-require.js | 8 +- eslint/lib/rules/grouped-accessor-pairs.js | 4 +- eslint/lib/rules/guard-for-in.js | 2 +- eslint/lib/rules/handle-callback-err.js | 5 +- eslint/lib/rules/id-blacklist.js | 7 +- eslint/lib/rules/id-denylist.js | 7 +- eslint/lib/rules/id-length.js | 14 +- eslint/lib/rules/id-match.js | 7 +- eslint/lib/rules/implicit-arrow-linebreak.js | 4 +- eslint/lib/rules/indent-legacy.js | 4 +- eslint/lib/rules/indent.js | 96 +- eslint/lib/rules/index.js | 3 + eslint/lib/rules/init-declarations.js | 2 +- eslint/lib/rules/jsx-quotes.js | 2 +- eslint/lib/rules/key-spacing.js | 126 +- eslint/lib/rules/keyword-spacing.js | 4 +- eslint/lib/rules/line-comment-position.js | 4 +- eslint/lib/rules/linebreak-style.js | 4 +- eslint/lib/rules/lines-around-comment.js | 19 +- eslint/lib/rules/lines-around-directive.js | 4 +- .../lib/rules/lines-between-class-members.js | 4 +- .../lib/rules/logical-assignment-operators.js | 474 +++ eslint/lib/rules/max-classes-per-file.js | 2 +- eslint/lib/rules/max-depth.js | 2 +- eslint/lib/rules/max-len.js | 4 +- eslint/lib/rules/max-lines-per-function.js | 4 +- eslint/lib/rules/max-lines.js | 4 +- eslint/lib/rules/max-nested-callbacks.js | 2 +- eslint/lib/rules/max-params.js | 4 +- eslint/lib/rules/max-statements-per-line.js | 4 +- eslint/lib/rules/max-statements.js | 2 +- eslint/lib/rules/multiline-comment-style.js | 49 +- eslint/lib/rules/multiline-ternary.js | 8 +- eslint/lib/rules/new-cap.js | 4 +- eslint/lib/rules/new-parens.js | 23 +- eslint/lib/rules/newline-after-var.js | 4 +- eslint/lib/rules/newline-before-return.js | 4 +- eslint/lib/rules/newline-per-chained-call.js | 4 +- eslint/lib/rules/no-alert.js | 6 +- eslint/lib/rules/no-array-constructor.js | 2 +- eslint/lib/rules/no-async-promise-executor.js | 4 +- eslint/lib/rules/no-await-in-loop.js | 2 +- eslint/lib/rules/no-bitwise.js | 2 +- eslint/lib/rules/no-buffer-constructor.js | 2 +- eslint/lib/rules/no-caller.js | 2 +- eslint/lib/rules/no-case-declarations.js | 2 +- eslint/lib/rules/no-catch-shadow.js | 6 +- eslint/lib/rules/no-class-assign.js | 6 +- eslint/lib/rules/no-compare-neg-zero.js | 2 +- eslint/lib/rules/no-cond-assign.js | 4 +- eslint/lib/rules/no-confusing-arrow.js | 4 +- eslint/lib/rules/no-console.js | 7 +- eslint/lib/rules/no-const-assign.js | 6 +- .../rules/no-constant-binary-expression.js | 65 +- eslint/lib/rules/no-constant-condition.js | 7 +- eslint/lib/rules/no-constructor-return.js | 2 +- eslint/lib/rules/no-continue.js | 2 +- eslint/lib/rules/no-control-regex.js | 4 +- eslint/lib/rules/no-debugger.js | 2 +- eslint/lib/rules/no-delete-var.js | 2 +- eslint/lib/rules/no-div-regex.js | 6 +- eslint/lib/rules/no-dupe-args.js | 6 +- eslint/lib/rules/no-dupe-class-members.js | 2 +- eslint/lib/rules/no-dupe-else-if.js | 4 +- eslint/lib/rules/no-dupe-keys.js | 2 +- eslint/lib/rules/no-duplicate-case.js | 4 +- eslint/lib/rules/no-duplicate-imports.js | 2 +- eslint/lib/rules/no-else-return.js | 29 +- eslint/lib/rules/no-empty-character-class.js | 2 +- eslint/lib/rules/no-empty-function.js | 4 +- eslint/lib/rules/no-empty-pattern.js | 2 +- eslint/lib/rules/no-empty-static-block.js | 47 + eslint/lib/rules/no-empty.js | 25 +- eslint/lib/rules/no-eq-null.js | 2 +- eslint/lib/rules/no-eval.js | 14 +- eslint/lib/rules/no-ex-assign.js | 6 +- eslint/lib/rules/no-extend-native.js | 7 +- eslint/lib/rules/no-extra-bind.js | 4 +- eslint/lib/rules/no-extra-boolean-cast.js | 6 +- eslint/lib/rules/no-extra-label.js | 4 +- eslint/lib/rules/no-extra-parens.js | 58 +- eslint/lib/rules/no-extra-semi.js | 6 +- eslint/lib/rules/no-fallthrough.js | 26 +- eslint/lib/rules/no-floating-decimal.js | 4 +- eslint/lib/rules/no-func-assign.js | 6 +- eslint/lib/rules/no-global-assign.js | 7 +- eslint/lib/rules/no-implicit-coercion.js | 25 +- eslint/lib/rules/no-implicit-globals.js | 12 +- eslint/lib/rules/no-implied-eval.js | 11 +- eslint/lib/rules/no-import-assign.js | 10 +- eslint/lib/rules/no-inline-comments.js | 4 +- eslint/lib/rules/no-inner-declarations.js | 2 +- eslint/lib/rules/no-invalid-regexp.js | 64 +- eslint/lib/rules/no-invalid-this.js | 8 +- eslint/lib/rules/no-irregular-whitespace.js | 4 +- eslint/lib/rules/no-iterator.js | 2 +- eslint/lib/rules/no-label-var.js | 5 +- eslint/lib/rules/no-labels.js | 2 +- eslint/lib/rules/no-lone-blocks.js | 16 +- eslint/lib/rules/no-lonely-if.js | 9 +- eslint/lib/rules/no-loop-func.js | 6 +- eslint/lib/rules/no-loss-of-precision.js | 6 +- eslint/lib/rules/no-magic-numbers.js | 21 +- .../rules/no-misleading-character-class.js | 57 +- eslint/lib/rules/no-mixed-operators.js | 4 +- eslint/lib/rules/no-mixed-requires.js | 2 +- eslint/lib/rules/no-mixed-spaces-and-tabs.js | 4 +- eslint/lib/rules/no-multi-assign.js | 2 +- eslint/lib/rules/no-multi-spaces.js | 4 +- eslint/lib/rules/no-multi-str.js | 2 +- eslint/lib/rules/no-multiple-empty-lines.js | 4 +- eslint/lib/rules/no-native-reassign.js | 7 +- eslint/lib/rules/no-negated-condition.js | 2 +- eslint/lib/rules/no-negated-in-lhs.js | 2 +- eslint/lib/rules/no-nested-ternary.js | 2 +- eslint/lib/rules/no-new-func.js | 15 +- .../lib/rules/no-new-native-nonconstructor.js | 66 + eslint/lib/rules/no-new-object.js | 7 +- eslint/lib/rules/no-new-require.js | 2 +- eslint/lib/rules/no-new-symbol.js | 16 +- eslint/lib/rules/no-new-wrappers.js | 2 +- eslint/lib/rules/no-new.js | 2 +- .../lib/rules/no-nonoctal-decimal-escape.js | 4 +- eslint/lib/rules/no-obj-calls.js | 18 +- eslint/lib/rules/no-octal-escape.js | 2 +- eslint/lib/rules/no-octal.js | 2 +- eslint/lib/rules/no-param-reassign.js | 5 +- eslint/lib/rules/no-path-concat.js | 2 +- eslint/lib/rules/no-plusplus.js | 2 +- eslint/lib/rules/no-process-env.js | 2 +- eslint/lib/rules/no-process-exit.js | 2 +- .../lib/rules/no-promise-executor-return.js | 7 +- eslint/lib/rules/no-proto.js | 2 +- eslint/lib/rules/no-prototype-builtins.js | 2 +- eslint/lib/rules/no-redeclare.js | 10 +- eslint/lib/rules/no-regex-spaces.js | 8 +- eslint/lib/rules/no-restricted-exports.js | 121 +- eslint/lib/rules/no-restricted-globals.js | 8 +- eslint/lib/rules/no-restricted-imports.js | 4 +- eslint/lib/rules/no-restricted-modules.js | 2 +- eslint/lib/rules/no-restricted-properties.js | 2 +- eslint/lib/rules/no-restricted-syntax.js | 2 +- eslint/lib/rules/no-return-assign.js | 4 +- eslint/lib/rules/no-return-await.js | 33 +- eslint/lib/rules/no-script-url.js | 2 +- eslint/lib/rules/no-self-assign.js | 4 +- eslint/lib/rules/no-self-compare.js | 4 +- eslint/lib/rules/no-sequences.js | 4 +- eslint/lib/rules/no-setter-return.js | 7 +- .../lib/rules/no-shadow-restricted-names.js | 5 +- eslint/lib/rules/no-shadow.js | 7 +- eslint/lib/rules/no-spaced-func.js | 4 +- eslint/lib/rules/no-sparse-arrays.js | 2 +- eslint/lib/rules/no-sync.js | 2 +- eslint/lib/rules/no-tabs.js | 4 +- .../lib/rules/no-template-curly-in-string.js | 2 +- eslint/lib/rules/no-ternary.js | 2 +- eslint/lib/rules/no-this-before-super.js | 2 +- eslint/lib/rules/no-throw-literal.js | 2 +- eslint/lib/rules/no-trailing-spaces.js | 4 +- eslint/lib/rules/no-undef-init.js | 6 +- eslint/lib/rules/no-undef.js | 7 +- eslint/lib/rules/no-undefined.js | 8 +- eslint/lib/rules/no-underscore-dangle.js | 50 +- eslint/lib/rules/no-unexpected-multiline.js | 4 +- .../lib/rules/no-unmodified-loop-condition.js | 8 +- eslint/lib/rules/no-unneeded-ternary.js | 6 +- eslint/lib/rules/no-unreachable-loop.js | 2 +- eslint/lib/rules/no-unreachable.js | 4 +- eslint/lib/rules/no-unsafe-finally.js | 2 +- eslint/lib/rules/no-unsafe-negation.js | 4 +- .../lib/rules/no-unsafe-optional-chaining.js | 2 +- eslint/lib/rules/no-unused-expressions.js | 11 +- eslint/lib/rules/no-unused-labels.js | 4 +- .../rules/no-unused-private-class-members.js | 2 +- eslint/lib/rules/no-unused-vars.js | 8 +- eslint/lib/rules/no-use-before-define.js | 9 +- eslint/lib/rules/no-useless-backreference.js | 18 +- eslint/lib/rules/no-useless-call.js | 4 +- eslint/lib/rules/no-useless-catch.js | 2 +- eslint/lib/rules/no-useless-computed-key.js | 4 +- eslint/lib/rules/no-useless-concat.js | 4 +- eslint/lib/rules/no-useless-constructor.js | 2 +- eslint/lib/rules/no-useless-escape.js | 4 +- eslint/lib/rules/no-useless-rename.js | 4 +- eslint/lib/rules/no-useless-return.js | 6 +- eslint/lib/rules/no-var.js | 10 +- eslint/lib/rules/no-void.js | 2 +- eslint/lib/rules/no-warning-comments.js | 4 +- .../rules/no-whitespace-before-property.js | 4 +- eslint/lib/rules/no-with.js | 2 +- .../rules/nonblock-statement-body-position.js | 4 +- eslint/lib/rules/object-curly-newline.js | 4 +- eslint/lib/rules/object-curly-spacing.js | 8 +- eslint/lib/rules/object-property-newline.js | 4 +- eslint/lib/rules/object-shorthand.js | 9 +- .../lib/rules/one-var-declaration-per-line.js | 2 +- eslint/lib/rules/one-var.js | 4 +- eslint/lib/rules/operator-assignment.js | 4 +- eslint/lib/rules/operator-linebreak.js | 4 +- eslint/lib/rules/padded-blocks.js | 4 +- .../rules/padding-line-between-statements.js | 8 +- eslint/lib/rules/prefer-arrow-callback.js | 11 +- eslint/lib/rules/prefer-const.js | 6 +- eslint/lib/rules/prefer-destructuring.js | 4 +- .../rules/prefer-exponentiation-operator.js | 14 +- .../lib/rules/prefer-named-capture-group.js | 93 +- eslint/lib/rules/prefer-numeric-literals.js | 4 +- eslint/lib/rules/prefer-object-has-own.js | 8 +- eslint/lib/rules/prefer-object-spread.js | 31 +- .../lib/rules/prefer-promise-reject-errors.js | 5 +- eslint/lib/rules/prefer-reflect.js | 2 +- eslint/lib/rules/prefer-regex-literals.js | 212 +- eslint/lib/rules/prefer-rest-params.js | 9 +- eslint/lib/rules/prefer-spread.js | 4 +- eslint/lib/rules/prefer-template.js | 4 +- eslint/lib/rules/quote-props.js | 4 +- eslint/lib/rules/quotes.js | 4 +- eslint/lib/rules/radix.js | 24 +- eslint/lib/rules/require-atomic-updates.js | 8 +- eslint/lib/rules/require-await.js | 4 +- eslint/lib/rules/require-jsdoc.js | 4 +- eslint/lib/rules/require-unicode-regexp.js | 76 +- eslint/lib/rules/require-yield.js | 2 +- eslint/lib/rules/rest-spread-spacing.js | 4 +- eslint/lib/rules/semi-spacing.js | 4 +- eslint/lib/rules/semi-style.js | 4 +- eslint/lib/rules/semi.js | 35 +- eslint/lib/rules/sort-imports.js | 4 +- eslint/lib/rules/sort-keys.js | 4 +- eslint/lib/rules/sort-vars.js | 4 +- eslint/lib/rules/space-before-blocks.js | 4 +- .../lib/rules/space-before-function-paren.js | 4 +- eslint/lib/rules/space-in-parens.js | 4 +- eslint/lib/rules/space-infix-ops.js | 4 +- eslint/lib/rules/space-unary-ops.js | 4 +- eslint/lib/rules/spaced-comment.js | 4 +- eslint/lib/rules/strict.js | 4 +- eslint/lib/rules/switch-colon-spacing.js | 4 +- eslint/lib/rules/symbol-description.js | 14 +- eslint/lib/rules/template-curly-spacing.js | 4 +- eslint/lib/rules/template-tag-spacing.js | 4 +- eslint/lib/rules/unicode-bom.js | 4 +- eslint/lib/rules/use-isnan.js | 2 +- eslint/lib/rules/utils/regular-expressions.js | 42 + eslint/lib/rules/valid-jsdoc.js | 4 +- eslint/lib/rules/valid-typeof.js | 10 +- eslint/lib/rules/vars-on-top.js | 2 +- eslint/lib/rules/wrap-iife.js | 6 +- eslint/lib/rules/wrap-regex.js | 9 +- eslint/lib/rules/yield-star-spacing.js | 4 +- eslint/lib/rules/yoda.js | 6 +- eslint/lib/shared/directives.js | 15 + eslint/lib/shared/runtime-info.js | 2 +- eslint/lib/shared/string-utils.js | 40 +- eslint/lib/shared/traverser.js | 2 +- eslint/lib/shared/types.js | 23 +- eslint/lib/source-code/source-code.js | 138 +- eslint/lib/source-code/token-store/index.js | 2 +- eslint/lib/source-code/token-store/utils.js | 55 +- eslint/lib/unsupported-api.js | 3 +- eslint/messages/invalid-rule-options.js | 17 + eslint/messages/invalid-rule-severity.js | 13 + eslint/messages/no-config-found.js | 2 +- .../print-config-with-directory-path.js | 2 +- eslint/messages/shared.js | 18 + eslint/package.json | 64 +- eslint/packages/js/LICENSE | 19 + eslint/packages/js/README.md | 57 + eslint/packages/js/package.json | 31 + eslint/packages/js/src/index.js | 17 + eslint/templates/blogpost.md.ejs | 2 +- eslint/templates/bug-report.md | 19 +- eslint/templates/formatter-examples.md.ejs | 34 +- eslint/templates/rule-change-proposal.md | 13 +- eslint/templates/rule-proposal.md | 11 +- eslint/tests/bench/large.js | 12 +- .../empty/.keep => .eslintignore_empty} | 0 .../cli-engine/eslint.config_with_ignores2.js | 3 + .../cli/ignore-pattern-relative/.eslintrc.js | 3 + .../ignore-pattern-relative/eslint.config.js | 1 + .../subdir/subsubdir/a.js | 0 eslint/tests/fixtures/dot-files/.a.js | 1 + eslint/tests/fixtures/dot-files/.c.js | 1 + eslint/tests/fixtures/dot-files/b.js | 1 + .../tests/fixtures/dot-files/eslint.config.js | 8 + eslint/tests/fixtures/dots-in-files/a..b.js | 1 + .../fixtures/dots-in-files/eslint.config.js | 8 + .../fixtures/eslint.config_with_ignores.js | 8 + .../fixtures/eslint.config_with_ignores2.js | 8 + .../fixtures/eslint.config_with_rules.js | 5 + eslint/tests/fixtures/example-app/app.js | 1 + .../fixtures/example-app/eslint.config.js | 1 + .../tests/fixtures/example-app/subdir/util.js | 1 + .../fixtures/example-app2/eslint.config.js | 3 + .../tests/fixtures/example-app2/subdir1/a.js | 0 .../tests/fixtures/example-app2/subdir2/b.js | 0 .../ignores-directory/eslint.config.js | 3 + .../ignores-directory/subdir/subsubdir/a.js | 0 eslint/tests/fixtures/ignores-relative/a.js | 0 .../ignores-relative/eslint.config.js | 5 + .../fixtures/ignores-relative/subdir/a.js | 0 .../fixtures/ignores-self/eslint.config.js | 3 + .../ignores-subdirectory/eslint.config.js | 3 + .../subdir/subsubdir/a.js | 0 .../fixtures/parsers/all-comments-parser.js | 28 + eslint/tests/fixtures/rules/custom-rule.js | 24 +- .../testers/rule-tester/no-test-filename | 2 +- .../testers/rule-tester/no-test-global.js | 7 +- .../fixtures/testers/rule-tester/no-var.js | 2 +- .../{curly-path}/client/eslint.config.js | 1 + .../fixtures/{curly-path}/client/src/one.js | 1 + .../{curly-path}/server/eslint.config.js | 1 + .../fixtures/{curly-path}/server/src/two.js | 1 + eslint/tests/lib/cli-engine/cli-engine.js | 17 +- .../tests/lib/cli-engine/file-enumerator.js | 68 + eslint/tests/lib/cli.js | 207 +- eslint/tests/lib/config/flat-config-array.js | 487 ++- eslint/tests/lib/eslint/eslint.js | 60 +- eslint/tests/lib/eslint/flat-eslint.js | 1370 ++++++-- .../code-path-analysis/code-path-analyzer.js | 406 ++- .../linter/code-path-analysis/code-path.js | 12 +- eslint/tests/lib/linter/linter.js | 3107 ++++++++++------- eslint/tests/lib/options.js | 16 +- .../tests/lib/rule-tester/flat-rule-tester.js | 18 +- eslint/tests/lib/rule-tester/rule-tester.js | 30 +- .../tests/lib/rules/array-callback-return.js | 13 + eslint/tests/lib/rules/comma-dangle.js | 120 +- eslint/tests/lib/rules/func-name-matching.js | 39 +- eslint/tests/lib/rules/getter-return.js | 83 +- eslint/tests/lib/rules/id-length.js | 268 ++ eslint/tests/lib/rules/key-spacing.js | 270 +- .../tests/lib/rules/lines-around-comment.js | 27 + .../lib/rules/logical-assignment-operators.js | 1460 ++++++++ .../lib/rules/multiline-comment-style.js | 14 + .../rules/no-constant-binary-expression.js | 12 +- .../tests/lib/rules/no-empty-static-block.js | 51 + eslint/tests/lib/rules/no-empty.js | 177 +- eslint/tests/lib/rules/no-extra-parens.js | 233 +- eslint/tests/lib/rules/no-fallthrough.js | 13 + .../tests/lib/rules/no-implicit-coercion.js | 45 +- eslint/tests/lib/rules/no-implicit-globals.js | 379 +- eslint/tests/lib/rules/no-invalid-regexp.js | 51 + eslint/tests/lib/rules/no-magic-numbers.js | 109 + .../rules/no-misleading-character-class.js | 33 +- .../lib/rules/no-new-native-nonconstructor.js | 68 + eslint/tests/lib/rules/no-obj-calls.js | 69 + .../tests/lib/rules/no-restricted-exports.js | 86 +- eslint/tests/lib/rules/no-return-await.js | 171 +- .../tests/lib/rules/no-underscore-dangle.js | 82 + eslint/tests/lib/rules/no-unused-vars.js | 29 +- eslint/tests/lib/rules/no-var.js | 68 + .../tests/lib/rules/prefer-arrow-callback.js | 40 + eslint/tests/lib/rules/prefer-const.js | 14 +- .../lib/rules/prefer-named-capture-group.js | 371 +- .../tests/lib/rules/prefer-object-spread.js | 1 - .../tests/lib/rules/prefer-regex-literals.js | 373 +- .../tests/lib/rules/require-unicode-regexp.js | 216 +- eslint/tests/lib/rules/semi.js | 203 ++ eslint/tests/lib/rules/strict.js | 46 +- eslint/tests/lib/rules/utils/ast-utils.js | 144 +- eslint/tests/lib/rules/valid-jsdoc.js | 2 +- eslint/tests/lib/shared/config-validator.js | 85 +- eslint/tests/lib/shared/runtime-info.js | 4 +- eslint/tests/lib/shared/string-utils.js | 50 +- eslint/tests/lib/source-code/source-code.js | 1624 +++++++-- eslint/tests/lib/source-code/token-store.js | 80 +- eslint/tests/lib/unsupported-api.js | 4 + eslint/tests/performance/jshint.js | 12 +- eslint/tools/commit-readme.sh | 18 + eslint/tools/fetch-docs-links.js | 4 +- eslint/tools/fuzzer-runner.js | 4 +- eslint/tools/rule-types.json | 3 + eslint/tools/update-eslint-all.js | 42 + eslint/tools/update-readme.js | 75 +- 897 files changed, 21346 insertions(+), 8775 deletions(-) create mode 100644 eslint/.github/CODEOWNERS delete mode 100644 eslint/.github/CODEOWNERS.md create mode 100644 eslint/.github/workflows/add-to-triage.yml create mode 100644 eslint/.github/workflows/update-readme.yml create mode 100644 eslint/docs/.stylelintrc.json create mode 100644 eslint/docs/postcss.config.js create mode 100644 eslint/docs/src/_data/layout.js create mode 100644 eslint/docs/src/_data/sites/zh-hans.yml create mode 100644 eslint/docs/src/_plugins/md-syntax-highlighter.js create mode 100644 eslint/docs/src/assets/js/scroll-up-btn.js rename eslint/docs/src/{developer-guide => contribute}/architecture/index.md (97%) rename eslint/docs/src/{developer-guide => contribute}/code-conventions.md (84%) create mode 100644 eslint/docs/src/contribute/code-of-conduct.md create mode 100644 eslint/docs/src/contribute/core-rules.md rename eslint/docs/src/{developer-guide => contribute}/development-environment.md (70%) rename eslint/docs/src/{maintainer-guide => contribute}/governance.md (95%) rename eslint/docs/src/{developer-guide/contributing => contribute}/index.md (61%) rename eslint/docs/src/{developer-guide => contribute}/package-json-conventions.md (83%) rename eslint/docs/src/{developer-guide/contributing/new-rules.md => contribute/propose-new-rule.md} (89%) rename eslint/docs/src/{developer-guide/contributing/rule-changes.md => contribute/propose-rule-change.md} (76%) rename eslint/docs/src/{developer-guide/contributing => contribute}/pull-requests.md (90%) rename eslint/docs/src/{developer-guide/contributing/reporting-bugs.md => contribute/report-bugs.md} (61%) create mode 100644 eslint/docs/src/contribute/report-security-vulnerability.md rename eslint/docs/src/{developer-guide/contributing/changes.md => contribute/request-change.md} (73%) rename eslint/docs/src/{developer-guide/unit-tests.md => contribute/tests.md} (95%) rename eslint/docs/src/{developer-guide/contributing/working-on-issues.md => contribute/work-on-issue.md} (85%) delete mode 100644 eslint/docs/src/developer-guide/index.md delete mode 100644 eslint/docs/src/developer-guide/shareable-configs.md delete mode 100644 eslint/docs/src/developer-guide/source-code.md delete mode 100644 eslint/docs/src/developer-guide/working-with-custom-parsers.md delete mode 100644 eslint/docs/src/developer-guide/working-with-plugins.md delete mode 100644 eslint/docs/src/developer-guide/working-with-rules.md rename eslint/docs/src/{developer-guide => extend}/code-path-analysis.md (95%) rename eslint/docs/src/{developer-guide/working-with-custom-formatters.md => extend/custom-formatters.md} (66%) create mode 100644 eslint/docs/src/extend/custom-parsers.md create mode 100644 eslint/docs/src/extend/custom-processors.md rename eslint/docs/src/{developer-guide/working-with-rules-deprecated.md => extend/custom-rules-deprecated.md} (96%) create mode 100644 eslint/docs/src/extend/custom-rules.md create mode 100644 eslint/docs/src/extend/index.md create mode 100644 eslint/docs/src/extend/plugins.md rename eslint/docs/src/{developer-guide => extend}/scope-manager-interface.md (99%) rename eslint/docs/src/{developer-guide => extend}/selectors.md (90%) create mode 100644 eslint/docs/src/extend/shareable-configs.md create mode 100644 eslint/docs/src/extend/ways-to-extend.md rename eslint/docs/src/{developer-guide => integrate}/nodejs-api.md (91%) create mode 100644 eslint/docs/src/maintain/index.md rename eslint/docs/src/{maintainer-guide/issues.md => maintain/manage-issues.md} (67%) rename eslint/docs/src/{maintainer-guide/releases.md => maintain/manage-releases.md} (67%) create mode 100644 eslint/docs/src/maintain/overview.md rename eslint/docs/src/{maintainer-guide/pullrequests.md => maintain/review-pull-requests.md} (89%) rename eslint/docs/src/{maintainer-guide => maintain}/working-groups.md (77%) delete mode 100644 eslint/docs/src/maintainer-guide/index.md create mode 100644 eslint/docs/src/rules/logical-assignment-operators.md create mode 100644 eslint/docs/src/rules/no-empty-static-block.md create mode 100644 eslint/docs/src/rules/no-new-native-nonconstructor.md create mode 100644 eslint/docs/src/use/command-line-interface.md rename eslint/docs/src/{user-guide/configuring => use/configure}/configuration-files-new.md (68%) rename eslint/docs/src/{user-guide/configuring => use/configure}/configuration-files.md (72%) rename eslint/docs/src/{user-guide/configuring/ignoring-code.md => use/configure/ignore.md} (73%) rename eslint/docs/src/{user-guide/configuring => use/configure}/index.md (75%) rename eslint/docs/src/{user-guide/configuring => use/configure}/language-options.md (78%) create mode 100644 eslint/docs/src/use/configure/parser.md rename eslint/docs/src/{user-guide/configuring => use/configure}/plugins.md (60%) rename eslint/docs/src/{user-guide/configuring => use/configure}/rules.md (72%) create mode 100644 eslint/docs/src/use/core-concepts.md rename eslint/docs/src/{user-guide => use}/formatters/html-formatter-example.html (67%) create mode 100644 eslint/docs/src/use/formatters/html-formatter-example.json create mode 100644 eslint/docs/src/use/formatters/index.md create mode 100644 eslint/docs/src/use/getting-started.md rename eslint/docs/src/{user-guide => use}/index.md (69%) rename eslint/docs/src/{user-guide => use}/integrations.md (57%) rename eslint/docs/src/{user-guide/migrating-to-8.0.0.md => use/migrate-to-8.0.0.md} (85%) rename eslint/docs/src/{user-guide => use}/migrating-from-jscs.md (93%) rename eslint/docs/src/{user-guide => use}/migrating-to-1.0.0.md (54%) rename eslint/docs/src/{user-guide => use}/migrating-to-2.0.0.md (88%) rename eslint/docs/src/{user-guide => use}/migrating-to-3.0.0.md (70%) rename eslint/docs/src/{user-guide => use}/migrating-to-4.0.0.md (88%) rename eslint/docs/src/{user-guide => use}/migrating-to-5.0.0.md (92%) rename eslint/docs/src/{user-guide => use}/migrating-to-6.0.0.md (83%) rename eslint/docs/src/{user-guide => use}/migrating-to-7.0.0.md (77%) rename eslint/docs/src/{user-guide => use}/rule-deprecation.md (99%) delete mode 100644 eslint/docs/src/user-guide/command-line-interface.md delete mode 100644 eslint/docs/src/user-guide/formatters/index.md delete mode 100644 eslint/docs/src/user-guide/getting-started.md create mode 100644 eslint/docs/tools/validate-links.js create mode 100644 eslint/lib/cli-engine/formatters/formatters-meta.json create mode 100644 eslint/lib/rules/logical-assignment-operators.js create mode 100644 eslint/lib/rules/no-empty-static-block.js create mode 100644 eslint/lib/rules/no-new-native-nonconstructor.js create mode 100644 eslint/lib/rules/utils/regular-expressions.js create mode 100644 eslint/lib/shared/directives.js create mode 100644 eslint/messages/invalid-rule-options.js create mode 100644 eslint/messages/invalid-rule-severity.js create mode 100644 eslint/messages/shared.js create mode 100644 eslint/packages/js/LICENSE create mode 100644 eslint/packages/js/README.md create mode 100644 eslint/packages/js/package.json create mode 100644 eslint/packages/js/src/index.js rename eslint/tests/fixtures/{cli-engine/empty/.keep => .eslintignore_empty} (100%) create mode 100644 eslint/tests/fixtures/cli-engine/eslint.config_with_ignores2.js create mode 100644 eslint/tests/fixtures/cli/ignore-pattern-relative/.eslintrc.js create mode 100644 eslint/tests/fixtures/cli/ignore-pattern-relative/eslint.config.js create mode 100644 eslint/tests/fixtures/cli/ignore-pattern-relative/subdir/subsubdir/a.js create mode 100644 eslint/tests/fixtures/dot-files/.a.js create mode 100644 eslint/tests/fixtures/dot-files/.c.js create mode 100644 eslint/tests/fixtures/dot-files/b.js create mode 100644 eslint/tests/fixtures/dot-files/eslint.config.js create mode 100644 eslint/tests/fixtures/dots-in-files/a..b.js create mode 100644 eslint/tests/fixtures/dots-in-files/eslint.config.js create mode 100644 eslint/tests/fixtures/eslint.config_with_ignores.js create mode 100644 eslint/tests/fixtures/eslint.config_with_ignores2.js create mode 100644 eslint/tests/fixtures/eslint.config_with_rules.js create mode 100644 eslint/tests/fixtures/example-app/app.js create mode 100644 eslint/tests/fixtures/example-app/eslint.config.js create mode 100644 eslint/tests/fixtures/example-app/subdir/util.js create mode 100644 eslint/tests/fixtures/example-app2/eslint.config.js create mode 100644 eslint/tests/fixtures/example-app2/subdir1/a.js create mode 100644 eslint/tests/fixtures/example-app2/subdir2/b.js create mode 100644 eslint/tests/fixtures/ignores-directory/eslint.config.js create mode 100644 eslint/tests/fixtures/ignores-directory/subdir/subsubdir/a.js create mode 100644 eslint/tests/fixtures/ignores-relative/a.js create mode 100644 eslint/tests/fixtures/ignores-relative/eslint.config.js create mode 100644 eslint/tests/fixtures/ignores-relative/subdir/a.js create mode 100644 eslint/tests/fixtures/ignores-self/eslint.config.js create mode 100644 eslint/tests/fixtures/ignores-subdirectory/eslint.config.js create mode 100644 eslint/tests/fixtures/ignores-subdirectory/subdir/subsubdir/a.js create mode 100644 eslint/tests/fixtures/parsers/all-comments-parser.js create mode 100644 eslint/tests/fixtures/{curly-path}/client/eslint.config.js create mode 100644 eslint/tests/fixtures/{curly-path}/client/src/one.js create mode 100644 eslint/tests/fixtures/{curly-path}/server/eslint.config.js create mode 100644 eslint/tests/fixtures/{curly-path}/server/src/two.js create mode 100644 eslint/tests/lib/rules/logical-assignment-operators.js create mode 100644 eslint/tests/lib/rules/no-empty-static-block.js create mode 100644 eslint/tests/lib/rules/no-new-native-nonconstructor.js create mode 100644 eslint/tools/commit-readme.sh create mode 100644 eslint/tools/update-eslint-all.js diff --git a/Makefile b/Makefile index 66b36f6..92414ca 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ DSC=${PACKAGE}_${DEB_VERSION_UPSTREAM_REVISION}.dsc SRCDIR=src UPSTREAM=eslint -UPSTREAMTAG=v8.23.1 +UPSTREAMTAG=v8.41.0 BUILDSRC=${UPSTREAM}-${UPSTREAMTAG} all: ${DEB} diff --git a/eslint/.eslintignore b/eslint/.eslintignore index 905f2a3..ffc34e9 100644 --- a/eslint/.eslintignore +++ b/eslint/.eslintignore @@ -1,7 +1,8 @@ /build/** /coverage/** -/docs/** -!/docs/.eleventy.js +/docs/* +!/docs/*.js +!/docs/tools/ /jsdoc/** /templates/** /tests/bench/** diff --git a/eslint/.eslintrc.js b/eslint/.eslintrc.js index 1a39568..4ceed4d 100644 --- a/eslint/.eslintrc.js +++ b/eslint/.eslintrc.js @@ -83,9 +83,10 @@ module.exports = { }, overrides: [ { - files: ["tools/*.js"], + files: ["tools/*.js", "docs/tools/*.js"], rules: { - "no-console": "off" + "no-console": "off", + "n/no-process-exit": "off" } }, { @@ -101,7 +102,7 @@ module.exports = { "eslint-plugin/prefer-placeholders": "error", "eslint-plugin/prefer-replace-text": "error", "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], - "eslint-plugin/require-meta-docs-description": ["error", { pattern: "^(Enforce|Require|Disallow)" }], + "eslint-plugin/require-meta-docs-description": ["error", { pattern: "^(Enforce|Require|Disallow) .+[^. ]$" }], "internal-rules/no-invalid-meta": "error" } }, @@ -109,7 +110,7 @@ module.exports = { files: ["lib/rules/*"], excludedFiles: ["index.js"], rules: { - "eslint-plugin/require-meta-docs-url": ["error", { pattern: "https://eslint.org/docs/rules/{{name}}" }] + "eslint-plugin/require-meta-docs-url": ["error", { pattern: "https://eslint.org/docs/latest/rules/{{name}}" }] } }, { diff --git a/eslint/.github/CODEOWNERS b/eslint/.github/CODEOWNERS new file mode 100644 index 0000000..5496e79 --- /dev/null +++ b/eslint/.github/CODEOWNERS @@ -0,0 +1,2 @@ +/docs/ @eslint/website-team @eslint/eslint-team +* @eslint/eslint-team \ No newline at end of file diff --git a/eslint/.github/CODEOWNERS.md b/eslint/.github/CODEOWNERS.md deleted file mode 100644 index b032d30..0000000 --- a/eslint/.github/CODEOWNERS.md +++ /dev/null @@ -1,17 +0,0 @@ -# Config-related files - -lib/conf/config-schema.js @nzakas -lib/cli-engine/config-array/* @nzakas -lib/cli-engine/config-array-factory.js @nzakas -lib/cli-engine/cascading-config-array-factory.js @nzakas -lib/shared/config-* @nzakas -lib/shared/naming.js @nzakas -lib/shared/relative-module-resolver.js @nzakas - -tests/lib/conf/config-schema.js @nzakas -tests/lib/cli-engine/config-array/* @nzakas -tests/lib/cli-engine/config-array-factory.js @nzakas -tests/lib/cli-engine/cascading-config-array-factory.js @nzakas -tests/lib/shared/config-* @nzakas -tests/lib/shared/naming.js @nzakas -tests/lib/shared/relative-module-resolver.js @nzakas diff --git a/eslint/.github/ISSUE_TEMPLATE/bug-report.yml b/eslint/.github/ISSUE_TEMPLATE/bug-report.yml index 18c1353..101bbcd 100644 --- a/eslint/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/eslint/.github/ISSUE_TEMPLATE/bug-report.yml @@ -68,6 +68,13 @@ body: Please copy-paste the actual ESLint output. You can use Markdown in this field. validations: required: true +- type: input + attributes: + label: Link to Minimal Reproducible Example + description: 'Link to a [playground](https://eslint.org/play), [StackBlitz](https://stackblitz.com), or GitHub repo with a minimal reproduction of the problem. **A minimal reproduction is required** so that others can help debug your issue. If a report is vague (e.g. just a generic error message) and has no reproduction, it may be auto-closed.' + placeholder: 'https://stackblitz.com/abcd1234' + validations: + required: true - type: checkboxes attributes: label: Participation diff --git a/eslint/.github/ISSUE_TEMPLATE/change.yml b/eslint/.github/ISSUE_TEMPLATE/change.yml index cc2066d..c7a2112 100644 --- a/eslint/.github/ISSUE_TEMPLATE/change.yml +++ b/eslint/.github/ISSUE_TEMPLATE/change.yml @@ -3,7 +3,6 @@ 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 diff --git a/eslint/.github/ISSUE_TEMPLATE/config.yml b/eslint/.github/ISSUE_TEMPLATE/config.yml index af8c434..1e20844 100644 --- a/eslint/.github/ISSUE_TEMPLATE/config.yml +++ b/eslint/.github/ISSUE_TEMPLATE/config.yml @@ -3,3 +3,9 @@ contact_links: - name: 🗣 Ask a Question, Discuss url: https://github.com/eslint/eslint/discussions about: Get help using ESLint + - name: 📝 Help with VS Code ESLint + url: https://github.com/microsoft/vscode-eslint/issues/ + about: Bugs and feature requests for the VS Code ESLint plugin + - name: Discord Server + url: https://eslint.org/chat + about: Talk with the team diff --git a/eslint/.github/PULL_REQUEST_TEMPLATE.md b/eslint/.github/PULL_REQUEST_TEMPLATE.md index 8c4bf8e..35d2410 100644 --- a/eslint/.github/PULL_REQUEST_TEMPLATE.md +++ b/eslint/.github/PULL_REQUEST_TEMPLATE.md @@ -31,7 +31,7 @@ diff --git a/eslint/.github/dependabot.yml b/eslint/.github/dependabot.yml index 5ace460..4c39a33 100644 --- a/eslint/.github/dependabot.yml +++ b/eslint/.github/dependabot.yml @@ -4,3 +4,5 @@ updates: directory: "/" schedule: interval: "weekly" + commit-message: + prefix: "ci" diff --git a/eslint/.github/workflows/add-to-triage.yml b/eslint/.github/workflows/add-to-triage.yml new file mode 100644 index 0000000..83297dc --- /dev/null +++ b/eslint/.github/workflows/add-to-triage.yml @@ -0,0 +1,18 @@ +name: Add to Triage + +on: + issues: + types: + - opened + +jobs: + add-to-project: + name: Add issue to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v0.5.0 + with: + project-url: https://github.com/orgs/eslint/projects/3 + github-token: ${{ secrets.PROJECT_BOT_TOKEN }} + labeled: "triage:no" + label-operator: NOT diff --git a/eslint/.github/workflows/ci.yml b/eslint/.github/workflows/ci.yml index 9401ec5..27dae4c 100644 --- a/eslint/.github/workflows/ci.yml +++ b/eslint/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: '16.x' + node-version: 'lts/*' - name: Install Packages run: npm install - name: Lint Files @@ -28,20 +28,29 @@ jobs: - name: Install Docs Packages working-directory: docs run: npm install + - name: Stylelint Docs + working-directory: docs + run: npm run lint:scss - name: Lint Docs JS Files run: node Makefile lintDocsJS + - name: Build Docs Website + working-directory: docs + run: npm run build + - name: Validate internal links + working-directory: docs + run: npm run lint:links test_on_node: name: Test strategy: matrix: os: [ubuntu-latest] - node: [18.x, 17.x, 16.x, 14.x, 12.x, "12.22.0"] + node: [20.x, 19.x, 18.x, 17.x, 16.x, 14.x, 12.x, "12.22.0"] include: - os: windows-latest - node: "16.x" + node: "lts/*" - os: macOS-latest - node: "16.x" + node: "lts/*" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 diff --git a/eslint/.github/workflows/stale.yml b/eslint/.github/workflows/stale.yml index 8680ebd..9e7355d 100644 --- a/eslint/.github/workflows/stale.yml +++ b/eslint/.github/workflows/stale.yml @@ -18,13 +18,15 @@ jobs: pull-requests: write steps: - - uses: actions/stale@v5 + - uses: actions/stale@v8 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - days-before-stale: 60 + days-before-issue-stale: 30 + days-before-pr-stale: 10 days-before-close: 7 stale-issue-message: 'Oops! It looks like we lost track of this issue. What do we want to do here? This issue will auto-close in 7 days without an update.' - 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.' + close-issue-message: 'This issue was auto-closed due to inactivity. While we wish we could keep responding to every issue, we unfortunately don''t have the bandwidth and need to focus on high-value issues.' + stale-pr-message: 'Hi everyone, it looks like we lost track of this pull request. Please review and see what the next steps are. This pull request will auto-close in 7 days without an update.' + close-pr-message: 'This pull request was auto-closed due to inactivity. While we wish we could keep working on every request, we unfortunately don''t have the bandwidth to continue here and need to focus on other things. You can resubmit this pull request if you would like to continue working on it.' exempt-all-assignees: true exempt-issue-labels: accepted - exempt-pr-labels: accepted diff --git a/eslint/.github/workflows/update-readme.yml b/eslint/.github/workflows/update-readme.yml new file mode 100644 index 0000000..e639992 --- /dev/null +++ b/eslint/.github/workflows/update-readme.yml @@ -0,0 +1,33 @@ +name: Data Fetch + +on: + schedule: + - cron: "0 8 * * *" # Every day at 1am PDT + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Check out repo + uses: actions/checkout@v3 + with: + token: ${{ secrets.WORKFLOW_PUSH_BOT_TOKEN }} + + - name: Set up Node.js + uses: actions/setup-node@v3 + + - name: Install npm packages + run: npm install + + - name: Update README with latest team and sponsor data + run: npm run build:readme + + - name: Setup Git + run: | + git config user.name "GitHub Actions Bot" + git config user.email "" + + - name: Save updated files + run: | + chmod +x ./tools/commit-readme.sh + ./tools/commit-readme.sh diff --git a/eslint/.npmrc b/eslint/.npmrc index c1ca392..8783d62 100644 --- a/eslint/.npmrc +++ b/eslint/.npmrc @@ -1 +1,2 @@ package-lock = false +install-links = false diff --git a/eslint/CHANGELOG.md b/eslint/CHANGELOG.md index be53ce6..a103b11 100644 --- a/eslint/CHANGELOG.md +++ b/eslint/CHANGELOG.md @@ -1,3 +1,361 @@ +v8.41.0 - May 19, 2023 + +* [`f43216a`](https://github.com/eslint/eslint/commit/f43216a8c77ab6cf1d0823978e8c728786b4cba7) chore: upgrade @eslint/js@8.41.0 (#17200) (Milos Djermanovic) +* [`95c3007`](https://github.com/eslint/eslint/commit/95c300780a1cfd9ad680bc78850542eb55d7fbf4) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`4f5440d`](https://github.com/eslint/eslint/commit/4f5440db631707b17140c4e5cc7beb223afbd2b9) fix: incorrect warning message for ignored dotfiles (#17196) (Milos Djermanovic) +* [`ddc5291`](https://github.com/eslint/eslint/commit/ddc5291debd90ff476e17c532af7577e26720b91) chore: don't use deprecated `context` methods in `ast-utils` tests (#17194) (Milos Djermanovic) +* [`880a431`](https://github.com/eslint/eslint/commit/880a4317b949e575a4a6c5e8baaba1eea7674cc6) feat: change default ignore pattern to `**/node_modules/` in flat config (#17184) (Milos Djermanovic) +* [`94da96c`](https://github.com/eslint/eslint/commit/94da96cbf0fb2bb6694fa2e757eb1b3e74c40db7) fix: unify `LintMessage` type (#17076) (Brandon Mills) +* [`7709b14`](https://github.com/eslint/eslint/commit/7709b14e18ad4e11c1119ed6575454243b8e7084) docs: Update README (GitHub Actions Bot) +* [`8bf5505`](https://github.com/eslint/eslint/commit/8bf550594fca6d29fab1a3453e701c1a457767e1) feat: expose `shouldUseFlatConfig` (#17169) (Connor Prussin) +* [`7f183e0`](https://github.com/eslint/eslint/commit/7f183e020579380fa57473caaf9ed154470c25b3) docs: Update triage process description (#17157) (Nicholas C. Zakas) +* [`0c415cd`](https://github.com/eslint/eslint/commit/0c415cda5d76dbe5120ab9f3c4c81320538e35f0) fix: validate `ignorePatterns` constructor option in `FlatESLint` class (#17139) (Milos Djermanovic) +* [`b1516db`](https://github.com/eslint/eslint/commit/b1516db51514032ed06e1425c4b1f955238dc682) chore: Fix return type of `findFlatConfigFile` (#17161) (Milos Djermanovic) +* [`b68346b`](https://github.com/eslint/eslint/commit/b68346b290d55324e73868ca42b3854157b27375) docs: fix license to reflect relicensing of jshint (#17165) (Stefan Bischof) +* [`9682d66`](https://github.com/eslint/eslint/commit/9682d669e4ee8641293914e21679f40fee8bc354) fix: switch `grapheme-splitter` to `graphemer` (#17160) (fisker Cheung) +* [`918b0fd`](https://github.com/eslint/eslint/commit/918b0fd21723e84bd7acb17942a36606f1d8360a) perf: Store indent descriptors in a plain array (#17148) (Francesco Trotta) +* [`4caa344`](https://github.com/eslint/eslint/commit/4caa34449555d8a680222ec2049d97c59476c11e) refactor: locateConfigFileToUse returns an Error object (#17159) (唯然) + +v8.40.0 - May 5, 2023 + +* [`4053004`](https://github.com/eslint/eslint/commit/4053004c951813473d1c43f9f9959a9a3484242f) chore: upgrade @eslint/js@8.40.0 (#17156) (Milos Djermanovic) +* [`50fed1d`](https://github.com/eslint/eslint/commit/50fed1da4449ad7ecbb558294438273cfce603d4) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`f076e54`](https://github.com/eslint/eslint/commit/f076e54ecdb0fae70d9b43ad6888606097beef97) fix: Ensure FlatESLint#findConfigFile() doesn't throw. (#17151) (Nicholas C. Zakas) +* [`4c7a170`](https://github.com/eslint/eslint/commit/4c7a170b04c5a746e401bef7ce79766ff66a1168) chore: upgrade @eslint/eslintrc@2.0.3 (#17155) (Milos Djermanovic) +* [`e80b7cc`](https://github.com/eslint/eslint/commit/e80b7cce640b60c00802148dbb51d03c7223afa9) chore: upgrade espree@9.5.2 (#17154) (Milos Djermanovic) +* [`ce3ac91`](https://github.com/eslint/eslint/commit/ce3ac91b510576e2afba1657aa5f09e162b4ab07) chore: upgrade eslint-visitor-keys@3.4.1 (#17153) (Milos Djermanovic) +* [`5db7808`](https://github.com/eslint/eslint/commit/5db7808139c1f2172797285a0700f01644bda254) feat: improve flat config errors for invalid rule options and severities (#17140) (Josh Goldberg ✨) +* [`f5574dc`](https://github.com/eslint/eslint/commit/f5574dc739fcc74a7841217ba1f31cce02bee1ff) feat: Add findConfigFile() method to FlatESLint (#17142) (Nicholas C. Zakas) +* [`e52b98b`](https://github.com/eslint/eslint/commit/e52b98bf25d882da4efd5559ce5974b6697cf701) feat: add `sourceCode` property to the rule context (#17107) (Nitin Kumar) +* [`e980bf3`](https://github.com/eslint/eslint/commit/e980bf38cf441f2eb29c458b93df77dc0111b391) docs: Update README (GitHub Actions Bot) +* [`9094d79`](https://github.com/eslint/eslint/commit/9094d79fb42c0ebb6100426a3f2f851e8d42a0ee) chore: add `latest/` to `meta.docs.url` in all core rules (#17136) (Milos Djermanovic) +* [`1468f5b`](https://github.com/eslint/eslint/commit/1468f5b640cfa6fdd8a5ec895337f692def2780b) feat: add `physicalFilename` property to the rule context (#17111) (Nitin Kumar) +* [`0df4d4f`](https://github.com/eslint/eslint/commit/0df4d4f658c214e51310a986c03d44d34ceae3ec) feat: add `cwd` to rule context (#17106) (Nitin Kumar) +* [`52018f2`](https://github.com/eslint/eslint/commit/52018f21c19b3e461cae32843cddd17ed42f19cd) feat: add `filename` property to the rule context (#17108) (Nitin Kumar) +* [`559ff4e`](https://github.com/eslint/eslint/commit/559ff4e4bc54a8b6e6b54825d83c532d724204b3) feat: add new `omitLastInOneLineClassBody` option to the `semi` rule (#17105) (Nitin Kumar) +* [`e92a6fc`](https://github.com/eslint/eslint/commit/e92a6fc7ed2a427f5e95f4b3a1c21d71553c97ee) docs: Update README (GitHub Actions Bot) +* [`d85efad`](https://github.com/eslint/eslint/commit/d85efad655deacc0dc3fdbbace33307094c3b91b) perf: don't use `grapheme-splitter` on ASCII strings in key-spacing rule (#17122) (Milos Djermanovic) +* [`af5fe64`](https://github.com/eslint/eslint/commit/af5fe64c398c9bd4206c3c6c1ade81768b291031) docs: Fix custom rule schema docs (#17115) (Adam Jones) +* [`4a352a9`](https://github.com/eslint/eslint/commit/4a352a957ba9e721bec9f6f403b419a22b0ec423) docs: explain how to include predefined globals (#17114) (Marcus Wyatt) +* [`5ea15d9`](https://github.com/eslint/eslint/commit/5ea15d92ee358e8f3f652c94c019cac96aaec651) docs: add mastodon link in readme (#17110) (唯然) + +v8.39.0 - April 21, 2023 + +* [`60a6f26`](https://github.com/eslint/eslint/commit/60a6f2694deb4aa1c54de2a28d0357cddfd16644) chore: upgrade @eslint/js@8.39.0 (#17102) (Milos Djermanovic) +* [`d5ba5c0`](https://github.com/eslint/eslint/commit/d5ba5c0a85e7a10777761f5d46c104ab7f25845b) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`6987dc5`](https://github.com/eslint/eslint/commit/6987dc59e46f4e345d0d6c20c1f2c6846bbd7acc) docs: Fix formatting in Custom Rules docs (#17097) (Milos Djermanovic) +* [`4ee92e5`](https://github.com/eslint/eslint/commit/4ee92e5cbdeba6fea2147901ce926de16946958a) docs: Update README (GitHub Actions Bot) +* [`3f7af9f`](https://github.com/eslint/eslint/commit/3f7af9f408625dbc486af914706d34c4b483b5ba) feat: Implement `SourceCode#markVariableAsUsed()` (#17086) (Nicholas C. Zakas) +* [`d8e9887`](https://github.com/eslint/eslint/commit/d8e9887c2c384d24d586d08ee9ae2ada79bd234c) docs: Custom Rules cleanup/expansion (#16906) (Ben Perlmutter) +* [`f57eff2`](https://github.com/eslint/eslint/commit/f57eff20f5789408e95061f1af5354bb9b4f4784) ci: run tests on Node.js v20 (#17093) (Nitin Kumar) +* [`1fea279`](https://github.com/eslint/eslint/commit/1fea2797801a82a2718814c83dad641dab092bcc) docs: Clarify how to add to tsc agenda (#17084) (Nicholas C. Zakas) +* [`970ef1c`](https://github.com/eslint/eslint/commit/970ef1c868235a58297682513842f1256cdfbd03) docs: Update triage board location (Nicholas C. Zakas) +* [`9d1b8fc`](https://github.com/eslint/eslint/commit/9d1b8fc60cc31f12618e58c10a2669506b7ce9bf) perf: Binary search in token store `utils.search` (#17066) (Francesco Trotta) +* [`07a4435`](https://github.com/eslint/eslint/commit/07a4435a0c08cb63ebf11b71f735bac20318829b) chore: Add request for minimal repro to bug report (#17081) (Nicholas C. Zakas) +* [`eac4943`](https://github.com/eslint/eslint/commit/eac4943ba2e4edb3dbfea0470e5d4b15a4926c40) refactor: remove unnecessary use of `SourceCode#getAncestors` in rules (#17075) (Milos Djermanovic) +* [`6d8bffd`](https://github.com/eslint/eslint/commit/6d8bffdf45d50e272dc45e6d2d05b4a737514468) docs: Update README (GitHub Actions Bot) +* [`0a7b60a`](https://github.com/eslint/eslint/commit/0a7b60a9d5621dbbc1a8a8adda3b7c2060c779ca) chore: update description of `SourceCode#getDeclaredVariables` (#17072) (Milos Djermanovic) +* [`6e2df71`](https://github.com/eslint/eslint/commit/6e2df71cc390252aaca212abe3dc0467fe397450) chore: remove unnecessary references to the LICENSE file (#17071) (Milos Djermanovic) + +v8.38.0 - April 7, 2023 + +* [`59ed060`](https://github.com/eslint/eslint/commit/59ed06041d4670781956221086ea0fca6683788d) chore: upgrade @eslint/js@8.38.0 (#17069) (Milos Djermanovic) +* [`88c0898`](https://github.com/eslint/eslint/commit/88c08984ec259ac22d839397c06beec8ef213120) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`7162d34`](https://github.com/eslint/eslint/commit/7162d34df9a66c817c3bd4aafd3a03d226b58dd5) docs: Mention new config system is complete (#17068) (Nicholas C. Zakas) +* [`a1d561d`](https://github.com/eslint/eslint/commit/a1d561d18ed653b56bddbfb1bab1ebe957293563) feat: Move getDeclaredVariables and getAncestors to SourceCode (#17059) (Nicholas C. Zakas) +* [`0fd6bb2`](https://github.com/eslint/eslint/commit/0fd6bb213ad2de77543c936eda21501653182e52) docs: Update README (GitHub Actions Bot) +* [`c83531c`](https://github.com/eslint/eslint/commit/c83531c1a6026675f36aa9e33fef14458043974a) docs: Update/remove external links, eg. point to `eslint-community` (#17061) (Pelle Wessman) +* [`cf682d2`](https://github.com/eslint/eslint/commit/cf682d249f04a6a304407d5b9ddbbc4a9714dd62) refactor: simplify new-parens rule schema (#17060) (MHO) +* [`a3aa6f5`](https://github.com/eslint/eslint/commit/a3aa6f5f146534ed7999ebf8930c524a4871ec0b) docs: Clarify `no-div-regex` rule docs (#17051) (Francesco Trotta) +* [`0dde022`](https://github.com/eslint/eslint/commit/0dde02211268394bcbc2b0beef55ea2409b6f55d) ci: bump actions/add-to-project from 0.4.1 to 0.5.0 (#17055) (dependabot[bot]) +* [`b0f11cf`](https://github.com/eslint/eslint/commit/b0f11cf977a4180bf7c3042e7faeaaa067ffafd0) docs: Update README (GitHub Actions Bot) +* [`da8d52a`](https://github.com/eslint/eslint/commit/da8d52a9d4edd9b2016cd4a15cd78f1ddadf20c7) docs: Update the second object instance for the "no-new" rule (#17020) (Ahmadou Waly NDIAYE) +* [`518130a`](https://github.com/eslint/eslint/commit/518130ae79a16d7bf4d752c211ae88152cc5a6f0) docs: switch language based on current path (#16687) (Percy Ma) +* [`24206c4`](https://github.com/eslint/eslint/commit/24206c49a138d4390f815ae122ee12f564bc604b) docs: Update README (GitHub Actions Bot) +* [`1c1ece2`](https://github.com/eslint/eslint/commit/1c1ece26d1da61e523b83dda25353ec9379eb6c9) fix: do not report on `RegExp(...args)` in `require-unicode-regexp` (#17037) (Francesco Trotta) + +v8.37.0 - March 28, 2023 + +* [`c67f299`](https://github.com/eslint/eslint/commit/c67f2992a743de4765bb6f11c12622e3651324b9) chore: upgrade @eslint/js@8.37.0 (#17033) (Milos Djermanovic) +* [`ee9ddbd`](https://github.com/eslint/eslint/commit/ee9ddbd63e262aed0052853760866c7a054af561) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`dddb475`](https://github.com/eslint/eslint/commit/dddb47528816cd7e2e737bfde108ed4d62e6a219) chore: upgrade @eslint/eslintrc@2.0.2 (#17032) (Milos Djermanovic) +* [`522431e`](https://github.com/eslint/eslint/commit/522431e5206bac2fcb41c0d6dc98a84929203bee) chore: upgrade espree@9.5.1 (#17031) (Milos Djermanovic) +* [`f5f9a88`](https://github.com/eslint/eslint/commit/f5f9a88c79b32222c0331a9bac1c02571d953b69) chore: upgrade eslint-visitor-keys@3.4.0 (#17030) (Milos Djermanovic) +* [`75339df`](https://github.com/eslint/eslint/commit/75339df99418df4d7e05a77e42ed7e22eabcc9e0) docs: fix typos and missing info in id-match docs (#17029) (Ed Lucas) +* [`b6ab8b2`](https://github.com/eslint/eslint/commit/b6ab8b2a2ca8807baca121407f5bfb0a0a839427) feat: `require-unicode-regexp` add suggestions (#17007) (Josh Goldberg) +* [`4dd8d52`](https://github.com/eslint/eslint/commit/4dd8d524e0fc9e8e2019df13f8b968021600e85c) ci: bump actions/stale from 7 to 8 (#17026) (dependabot[bot]) +* [`619f3fd`](https://github.com/eslint/eslint/commit/619f3fd17324c7b71bf17e02047d0c6dc7e5109e) fix: correctly handle `null` default config in `RuleTester` (#17023) (Brad Zacher) +* [`ec2d830`](https://github.com/eslint/eslint/commit/ec2d8307850dd039e118c001416606e1e0342bc8) docs: Fix typos in the `semi` rule docs (#17012) (Andrii Lundiak) +* [`e39f28d`](https://github.com/eslint/eslint/commit/e39f28d8578a00f4da8d4ddad559547950128a0d) docs: add back to top button (#16979) (Tanuj Kanti) +* [`ad9dd6a`](https://github.com/eslint/eslint/commit/ad9dd6a933fd098a0d99c6a9aa059850535c23ee) chore: remove duplicate scss, (#17005) (Strek) +* [`10022b1`](https://github.com/eslint/eslint/commit/10022b1f4bda1ad89193512ecf18c2ee61db8202) feat: Copy getScope() to SourceCode (#17004) (Nicholas C. Zakas) +* [`1665c02`](https://github.com/eslint/eslint/commit/1665c029acb92bf8812267f1647ad1a7054cbcb4) feat: Use plugin metadata for flat config serialization (#16992) (Nicholas C. Zakas) +* [`b3634f6`](https://github.com/eslint/eslint/commit/b3634f695ddab6a82c0a9b1d8695e62b60d23366) feat: docs license (#17010) (Samuel Roldan) +* [`721c717`](https://github.com/eslint/eslint/commit/721c71782a7c11025689a1500e7690fb3794fcce) docs: Custom Processors cleanup and expansion (#16838) (Ben Perlmutter) +* [`1fbf118`](https://github.com/eslint/eslint/commit/1fbf1184fed57df02640aad4659afb54dc26a2e9) fix: `getFirstToken`/`getLastToken` on comment-only node (#16889) (Francesco Trotta) +* [`129e252`](https://github.com/eslint/eslint/commit/129e252132c7c476d7de17f40b54a333ddb2e6bb) fix: Fix typo in `logical-assignment-operators` rule description (#17000) (Francesco Trotta) +* [`892e6e5`](https://github.com/eslint/eslint/commit/892e6e58c5a07a549d3104de3b6b5879797dc97f) feat: languageOptions.parser must be an object. (#16985) (Nicholas C. Zakas) +* [`ada6a3e`](https://github.com/eslint/eslint/commit/ada6a3e6e3607523958f35e1260537630ec0e976) ci: unpin Node 19 (#16993) (Milos Djermanovic) +* [`c3da975`](https://github.com/eslint/eslint/commit/c3da975e69fde46f35338ce48528841a8dc1ffd2) chore: Remove triage label from template (#16990) (Nicholas C. Zakas) +* [`d049f97`](https://github.com/eslint/eslint/commit/d049f974103e530ef76ede25af701635caf1f405) docs: 'How ESLint is Maintained' page (#16961) (Ben Perlmutter) +* [`5251a92`](https://github.com/eslint/eslint/commit/5251a921866e8d3b380dfe8db8a6e6ab97773d5e) docs: Describe guard options for guard-for-in (#16986) (alope107) +* [`69bc0e2`](https://github.com/eslint/eslint/commit/69bc0e2f4412998f9384600a100d7882ea4dd3f3) ci: pin Node 19 to 19.7.0 (#16987) (Milos Djermanovic) +* [`6157d81`](https://github.com/eslint/eslint/commit/6157d813e19b80481a46f8cbdf9eae18a55e5619) docs: Add example to guard-for-in docs. (#16983) (alope107) +* [`fd47998`](https://github.com/eslint/eslint/commit/fd47998af6efadcdf5ba93e0bd1f4c02d97d22b3) docs: update `Array.prototype.toSorted` specification link (#16982) (Milos Djermanovic) +* [`3e1cf6b`](https://github.com/eslint/eslint/commit/3e1cf6bfc5ebc29314ddbe462d6cb580e9ab085c) docs: Copy edits on Maintain ESLint docs (#16939) (Ben Perlmutter) + +v8.36.0 - March 10, 2023 + +* [`602b111`](https://github.com/eslint/eslint/commit/602b11121910a97ab2bc4a95a46dd0ccd0a89309) chore: upgrade @eslint/js@8.36.0 (#16978) (Milos Djermanovic) +* [`43c2345`](https://github.com/eslint/eslint/commit/43c2345c27024aeab6127e6bbfd55c8b70bd317e) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`00afb84`](https://github.com/eslint/eslint/commit/00afb84e5039874c8745a45c953fceaf0c71c454) chore: upgrade @eslint/eslintrc@2.0.1 (#16977) (Milos Djermanovic) +* [`698c5aa`](https://github.com/eslint/eslint/commit/698c5aad50e628ff00281dbc786e42de79834035) chore: upgrade espree@9.5.0 (#16976) (Milos Djermanovic) +* [`b98fdd4`](https://github.com/eslint/eslint/commit/b98fdd413a3b07b262bfce6f704c1c1bb8582770) docs: Update README (GitHub Actions Bot) +* [`c89a485`](https://github.com/eslint/eslint/commit/c89a485c49450532ee3db74f2638429f1f37d0dd) feat: Add `checkJSDoc` option to multiline-comment-style (#16807) (Laurent Cozic) +* [`f5f5e11`](https://github.com/eslint/eslint/commit/f5f5e11bd5fd3daab9ccae41e270739c836c305e) feat: Serialize parsers/processors in flat config (#16944) (Nicholas C. Zakas) +* [`caf08ce`](https://github.com/eslint/eslint/commit/caf08ce0cc74917f7c0eec92d25fd784dc33ac4d) docs: fix estree link in custom formatters docs (#16967) (Milos Djermanovic) +* [`75acdd2`](https://github.com/eslint/eslint/commit/75acdd21c5ce7024252e9d41ed77d2f30587caac) chore: lint more js files in docs (#16964) (Milos Djermanovic) +* [`3398431`](https://github.com/eslint/eslint/commit/3398431574b903757bc78b08c8ed36b7b9fce8eb) docs: Custom Parsers cleanup/expansion (#16887) (Ben Perlmutter) +* [`19d3531`](https://github.com/eslint/eslint/commit/19d3531d9b54e1004318d28f9a6e18305c5bcc18) docs: Update README (GitHub Actions Bot) +* [`4799297`](https://github.com/eslint/eslint/commit/4799297ea582c81fd1e5623d32a7ddf7a7f3a126) feat: use @eslint-community dependencies (#16784) (Michaël De Boey) +* [`b09a512`](https://github.com/eslint/eslint/commit/b09a512107249a4eb19ef5a37b0bd672266eafdb) docs: detect and fix broken links (#16837) (Nitin Kumar) +* [`92c1943`](https://github.com/eslint/eslint/commit/92c1943ba73ea01e87086236e8736539b0eed558) fix: correctly iterate files matched by glob patterns (#16831) (Nitin Kumar) +* [`89d9844`](https://github.com/eslint/eslint/commit/89d9844b3151f09b5b21b6eeeda671009ec301e9) ci: bump actions/add-to-project from 0.4.0 to 0.4.1 (#16943) (dependabot[bot]) + +v8.35.0 - February 26, 2023 + +* [`cdcbe12`](https://github.com/eslint/eslint/commit/cdcbe127de20cbcc4e24131a808c13b1024e61a2) chore: upgrade @eslint/js@8.35.0 (#16935) (Brandon Mills) +* [`c954c34`](https://github.com/eslint/eslint/commit/c954c349c0c2f88919614efc95e1368c245582fd) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`5a517da`](https://github.com/eslint/eslint/commit/5a517da8e55f6de28e9c028c5627fa7d82945969) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`9f10926`](https://github.com/eslint/eslint/commit/9f10926d76be7cf675721b29bd5030e85cb4ab30) chore: upgrade @eslint/eslintrc@2.0.0 (#16928) (Milos Djermanovic) +* [`8e34a04`](https://github.com/eslint/eslint/commit/8e34a04e3a4395bce59bc6acadf84281abc11d18) feat: add `afterHashbangComment` option to `lines-around-comment` rule (#16920) (SUZUKI Sosuke) +* [`c8c0c71`](https://github.com/eslint/eslint/commit/c8c0c715a2964cc1859b99f9d4f542675094d1d5) feat: Move all and recommended configs into package. (#16844) (Nicholas C. Zakas) +* [`f9f195e`](https://github.com/eslint/eslint/commit/f9f195ef12deb114fb86763010a23ea0cb4c78d1) docs: Plugin docs cleanup & expansion (#16862) (Ben Perlmutter) +* [`df809fd`](https://github.com/eslint/eslint/commit/df809fdedc5fc92df4be8340e28baedbde605b4f) docs: Custom Formatters page cleanup/expansion (#16886) (Ben Perlmutter) +* [`0700d1b`](https://github.com/eslint/eslint/commit/0700d1b14659bf39b1a08f082c44c9084cf676a8) docs: Add PostCSS/Autoprefixer/CSSNano (#16502) (Nick Schonning) +* [`da728fa`](https://github.com/eslint/eslint/commit/da728fae6c4e5fdda74195e84d45d67ad5cafc45) ci: use LTS node version in workflows (#16907) (Nitin Kumar) +* [`7b9e9bf`](https://github.com/eslint/eslint/commit/7b9e9bf78bedb009fe2813308ede1f46502c3890) docs: support unicode anchors (#16782) (Percy Ma) +* [`5fbc0bf`](https://github.com/eslint/eslint/commit/5fbc0bffdd9f84feb43296eb502d1e484fb323f2) docs: Update README (GitHub Actions Bot) +* [`c57b4f3`](https://github.com/eslint/eslint/commit/c57b4f3dc6383e452120381204ee4a7c874225a0) perf: upgrade to esquery@1.4.2 (#16901) (Milos Djermanovic) +* [`9698bc5`](https://github.com/eslint/eslint/commit/9698bc5cdec1bbee567a6a489da82e87fe65d019) fix: pin esquery v1.4.0 (fixes #16896) (#16897) (唯然) +* [`67865a0`](https://github.com/eslint/eslint/commit/67865a064cc1a4e320030299edc1cfdd1f9ac3b8) docs: Remove mention of mailing list (#16869) (Amaresh S M) +* [`43af24a`](https://github.com/eslint/eslint/commit/43af24a88b939a62880c37d1332b02f677d82f16) docs: Add explanation of when to use 'warn' severity (#16882) (Nicholas C. Zakas) +* [`71f6f0d`](https://github.com/eslint/eslint/commit/71f6f0dcd574320ee71c3eb1f313841899bdf260) feat: report more cases with `??` in no-constant-binary-expression (#16826) (Daiki Nishikawa) +* [`ed2999b`](https://github.com/eslint/eslint/commit/ed2999b38b4d61f5c278301738e294012d5d3c9e) docs: Shareable configs page edits and expansion (#16824) (Ben Perlmutter) +* [`2780635`](https://github.com/eslint/eslint/commit/27806358b5e1c4d37b63b1c61595e86ff03b5b42) docs: fix typos (#16884) (Lioness100) +* [`5bdaae2`](https://github.com/eslint/eslint/commit/5bdaae205c3a0089ea338b382df59e21d5b06436) docs: Ways to Extend ESLint page (#16861) (Ben Perlmutter) +* [`9122f07`](https://github.com/eslint/eslint/commit/9122f0764031dc36970df715bc5e16973890e18d) chore: Update stale bot settings (#16870) (Nicholas C. Zakas) + +v8.34.0 - February 10, 2023 + +* [`f0a9883`](https://github.com/eslint/eslint/commit/f0a988384ea1a262150e70d83abd8a5e50c46fa7) docs: split rules documentation (#16797) (Ben Perlmutter) +* [`923f61d`](https://github.com/eslint/eslint/commit/923f61d8fc82d83b912c6ba95abb5a509c4d7b52) fix: false positive with assignment in `no-extra-parens` (#16872) (Francesco Trotta) +* [`9dbe06d`](https://github.com/eslint/eslint/commit/9dbe06d0ad875e6d5964497e2975e8d789e763d0) chore: add `type` property to array-element-newline schema (#16877) (MHO) +* [`a061527`](https://github.com/eslint/eslint/commit/a061527a0332f0edf559acfc2902a327cae098d9) chore: Remove unused functions (#16868) (Nicholas C. Zakas) +* [`67aa37b`](https://github.com/eslint/eslint/commit/67aa37b583f059226b9c959672400f04ed6a56b5) docs: fix typo in command-line-interface.md (#16871) (Kevin Rouchut) +* [`337f7ed`](https://github.com/eslint/eslint/commit/337f7ed96131d873be7ae6b010739476d0ad15e9) docs: fix width of language input (#16849) (Tanuj Kanti) +* [`9b2fcf7`](https://github.com/eslint/eslint/commit/9b2fcf7e928fc92ac6d43617bdee1bda250b7491) feat: `array-callback-return` supports `Array.prototype.toSorted` (#16845) (SUZUKI Sosuke) +* [`71349a1`](https://github.com/eslint/eslint/commit/71349a1f709baa361bd656a7ce4a7d35d857a9a8) docs: Configure a Parser page (#16803) (Ben Perlmutter) +* [`de7e925`](https://github.com/eslint/eslint/commit/de7e925d03764f3681269b30bb60b92ee463c10f) docs: remove extra line numbers in example (#16848) (jonz94) +* [`ad38d77`](https://github.com/eslint/eslint/commit/ad38d77102d6fe30cfa92c831174f178bb35c88b) docs: Update README (GitHub Actions Bot) + +v8.33.0 - January 28, 2023 + +* [`17f4be2`](https://github.com/eslint/eslint/commit/17f4be2b66deb81f4e9ffb3d6bdfb79f3fcf85a2) docs: Fix examples in no-multiple-empty-lines rule (#16835) (jonz94) +* [`9c7cfe3`](https://github.com/eslint/eslint/commit/9c7cfe33c4a39cf2c23529afe02030ea7f8acf70) docs: 'Source Code' content in 'Set up Development Environment' page (#16780) (Ben Perlmutter) +* [`ede5c64`](https://github.com/eslint/eslint/commit/ede5c6475469a905da4f559ab55f0ee73168a9d7) docs: Custom processors page (#16802) (Ben Perlmutter) +* [`2620614`](https://github.com/eslint/eslint/commit/2620614f525de13f2e3ab0a7cd92abe89dae4897) docs: Code of Conduct page (#16781) (Ben Perlmutter) +* [`50a8efd`](https://github.com/eslint/eslint/commit/50a8efd957c70c9978a8ed25744a24193b00e078) docs: report a sec vulnerability page (#16808) (Ben Perlmutter) +* [`2cc7954`](https://github.com/eslint/eslint/commit/2cc7954cdb1fed44e8a5d3c9b3ea1deceadb5e00) feat: add `restrictDefaultExports` option to no-restricted-exports rule (#16785) (Nitin Kumar) +* [`ed60afd`](https://github.com/eslint/eslint/commit/ed60afd4450e769a975447178299446f4439d926) docs: Update page titles, section landing pages, and side TOC (#16760) (Ben Perlmutter) +* [`333c712`](https://github.com/eslint/eslint/commit/333c71243537966930e9ab8178bc98c37949b5f2) docs: add background to code-path-diagrams for dark-mode (#16822) (Tanuj Kanti) +* [`f5f7b9b`](https://github.com/eslint/eslint/commit/f5f7b9b8b512f5c6a5b4a1037f81bb3f5a7311e0) docs: Update README (GitHub Actions Bot) +* [`2aa4f5f`](https://github.com/eslint/eslint/commit/2aa4f5fb2fdb1c4a1734093c225e5c6251b0ee0f) docs: no-constant-condition: Add multi-comparison example (#16776) (Sebastian Simon) +* [`40287db`](https://github.com/eslint/eslint/commit/40287dbe7407934a69805f02ece07491778c3694) docs: Remove Google Group icon (#16779) (Nicholas C. Zakas) +* [`ea10ca5`](https://github.com/eslint/eslint/commit/ea10ca5b7b5bd8f6e6daf030ece9a3a82f10994c) docs: 'a .eslint' -> 'an .eslint' for consistency (#16809) (Ben Perlmutter) +* [`3be0748`](https://github.com/eslint/eslint/commit/3be07488ee7b6a9591d169be9648fbd36b32105e) docs: add example for nodejs lintText api (#16789) (Siva K) +* [`ce4f5ff`](https://github.com/eslint/eslint/commit/ce4f5ff30590df053a539c8e8e2597838e038a36) docs: Replace removed related rules with a valid rule (#16800) (Ville Saalo) + +v8.32.0 - January 14, 2023 + +* [`17b65ad`](https://github.com/eslint/eslint/commit/17b65ad10d653bb05077f21d8b1f79bee96e38d8) docs: IA Update page URL move (#16665) (Ben Perlmutter) +* [`b4f8329`](https://github.com/eslint/eslint/commit/b4f8329164d7b293a1557e05b987d2a685fe1d30) fix: ignore directives for no-fallthrough (#16757) (gfyoung) +* [`5981296`](https://github.com/eslint/eslint/commit/5981296d5c7c86228ad766009901191fdd87d5a4) docs: fix theme switcher button (#16752) (Sam Chen) +* [`6669413`](https://github.com/eslint/eslint/commit/66694136b67277c050bd27f60050779687a88c9f) docs: deploy prerelease docs under the `/docs/next/` path (#16541) (Nitin Kumar) +* [`2952d6e`](https://github.com/eslint/eslint/commit/2952d6ed95811ce0971b6855d66fb7a9767a7b72) chore: sync templates/*.md files with issue templates (#16758) (gfyoung) +* [`78ecfe0`](https://github.com/eslint/eslint/commit/78ecfe0e52c0e5780fefc8dc9a98864e48de6637) docs: use inline code for rule options name (#16768) (Percy Ma) +* [`3e34418`](https://github.com/eslint/eslint/commit/3e34418b31664decfb2337de798feafbf985b66c) chore: Add new issues to triage project (#16740) (Nicholas C. Zakas) +* [`fc2ea59`](https://github.com/eslint/eslint/commit/fc2ea598aee97beb6d768866da1ee4f63775f0c9) docs: Update README (GitHub Actions Bot) +* [`fc20f24`](https://github.com/eslint/eslint/commit/fc20f242a2ac073b5af6d5fca67e07a175f36c3b) feat: add suggestions for redundant wrapping in prefer-regex-literals (#16658) (YeonJuan) +* [`762a872`](https://github.com/eslint/eslint/commit/762a8727fb3b5619cff900826053b643ca5f1162) docs: Update README (GitHub Actions Bot) + +v8.31.0 - December 31, 2022 + +* [`65d4e24`](https://github.com/eslint/eslint/commit/65d4e24c36367cd63f0eba7371820e0e81dae7aa) chore: Upgrade @eslint/eslintrc@1.4.1 (#16729) (Brandon Mills) +* [`35439f1`](https://github.com/eslint/eslint/commit/35439f1572e1a8888f7feb6c5e51a15b5582495d) fix: correct syntax error in `prefer-arrow-callback` autofix (#16722) (Francesco Trotta) +* [`87b2470`](https://github.com/eslint/eslint/commit/87b247058ed520061fe1a146b7f0e7072a94990d) fix: new instance of FlatESLint should load latest config file version (#16608) (Milos Djermanovic) +* [`8d93081`](https://github.com/eslint/eslint/commit/8d93081a717f6e8b8cb60c3075cc1d7e4e655e6b) chore: fix CI failure (#16721) (Sam Chen) +* [`4339dc4`](https://github.com/eslint/eslint/commit/4339dc462d78888fe2e10acdfacd6f57245ce6ae) docs: Update README (GitHub Actions Bot) +* [`8f17247`](https://github.com/eslint/eslint/commit/8f17247a93240ff8a08980d8e06352e4ff4e8fe3) chore: Set up automatic updating of README (#16717) (Nicholas C. Zakas) +* [`4e4049c`](https://github.com/eslint/eslint/commit/4e4049c5fa355b2091afc8948690fcd7b1c1e6df) docs: optimize code block structure (#16669) (Sam Chen) +* [`54a7ade`](https://github.com/eslint/eslint/commit/54a7ade5d8e6f59554afeb9202ba6143f8afdf57) docs: do not escape code blocks of formatters examples (#16719) (Sam Chen) +* [`52c7c73`](https://github.com/eslint/eslint/commit/52c7c73c052e1ec2528c6b4af78181bc30cf8cdd) feat: check assignment patterns in no-underscore-dangle (#16693) (Milos Djermanovic) +* [`e5ecfef`](https://github.com/eslint/eslint/commit/e5ecfefa1c952195a3a8371f5953cc655d844079) docs: Add function call example for no-undefined (#16712) (Elliot Huffman) +* [`a3262f0`](https://github.com/eslint/eslint/commit/a3262f0a6305d2a721fac137a60c62c019b26aa4) docs: Add mastodon link (#16638) (Amaresh S M) +* [`4cd87cb`](https://github.com/eslint/eslint/commit/4cd87cb3c52412277577ba00c4fbb1aec36acc8c) ci: bump actions/stale from 6 to 7 (#16713) (dependabot[bot]) +* [`a14ccf9`](https://github.com/eslint/eslint/commit/a14ccf91af1122e419710f58ef494980fc4894b3) docs: clarify files property (#16709) (Sam Chen) +* [`3b29eb1`](https://github.com/eslint/eslint/commit/3b29eb14e00182614c986d8498b483a9917976e7) docs: fix npm link (#16710) (Abdullah Osama) +* [`fd20c75`](https://github.com/eslint/eslint/commit/fd20c75b1059c54d598c0abaf63e7d7a80f04f32) chore: sort package.json scripts in alphabetical order (#16705) (Darius Dzien) +* [`a638673`](https://github.com/eslint/eslint/commit/a638673ee6e94344c46d12dfc988adeb3783f817) docs: fix search bar focus on `Esc` (#16700) (Shanmughapriyan S) +* [`f62b722`](https://github.com/eslint/eslint/commit/f62b722251858a5dfb157591910edbaaeb4a966f) docs: country flag missing in windows (#16698) (Shanmughapriyan S) +* [`4d27ec6`](https://github.com/eslint/eslint/commit/4d27ec6019847afabeebf592dddc014e9220057c) docs: display zh-hans in the docs language switcher (#16686) (Percy Ma) +* [`8bda20e`](https://github.com/eslint/eslint/commit/8bda20e8276c6ba17d31842fcdd63ba65476fbbd) docs: remove manually maintained anchors (#16685) (Percy Ma) +* [`b401cde`](https://github.com/eslint/eslint/commit/b401cde47d44746ff91b8feced3fb3a4e32c0e12) feat: add options to check destructuring in no-underscore-dangle (#16006) (Morten Kaltoft) +* [`b68440f`](https://github.com/eslint/eslint/commit/b68440ff2b8322fc00373792701169205c94ed94) docs: User Guide Getting Started expansion (#16596) (Ben Perlmutter) +* [`30d0daf`](https://github.com/eslint/eslint/commit/30d0daf55e85a412995f6d69f47cab3fb591f2c3) feat: group properties with values in parentheses in `key-spacing` (#16677) (Francesco Trotta) +* [`10a5c78`](https://github.com/eslint/eslint/commit/10a5c7839370219c79f44d4206cbd7c28a72bad5) chore: update ignore patterns in `eslint.config.js` (#16678) (Milos Djermanovic) + +v8.30.0 - December 16, 2022 + +* [`f2c4737`](https://github.com/eslint/eslint/commit/f2c47372420f050ad8f2300271345de1c1232635) chore: upgrade @eslint/eslintrc@1.4.0 (#16675) (Milos Djermanovic) +* [`1a327aa`](https://github.com/eslint/eslint/commit/1a327aae57f1b68c96b27cc1bd57f8198d5a3a7c) fix: Ensure flat config unignores work consistently like eslintrc (#16579) (Nicholas C. Zakas) +* [`075ef2c`](https://github.com/eslint/eslint/commit/075ef2cf315e75b51b671c40ce9a97c66b2e4b50) feat: add suggestion for no-return-await (#16637) (Daniel Bartholomae) +* [`ba74253`](https://github.com/eslint/eslint/commit/ba74253e8bd63e9e163bbee0540031be77e39253) chore: standardize npm script names per #14827 (#16315) (Patrick McElhaney) +* [`6a8cd94`](https://github.com/eslint/eslint/commit/6a8cd94ed08983c70ca7d72dc6e360770a743405) docs: Clarify Discord info in issue template config (#16663) (Nicholas C. Zakas) +* [`0d9af4c`](https://github.com/eslint/eslint/commit/0d9af4c5674809be993439c766dcd9d7f65fcec9) ci: fix npm v9 problem with `file:` (#16664) (Milos Djermanovic) +* [`7190d98`](https://github.com/eslint/eslint/commit/7190d98ff40023f24b0c6a98319ae8a82c99ff5b) feat: update globals (#16654) (Sébastien Règne) +* [`ad44344`](https://github.com/eslint/eslint/commit/ad44344ef6fdeac7217eb83bc54a230382c0da5e) docs: CLI documentation standardization (#16563) (Ben Perlmutter) +* [`90c9219`](https://github.com/eslint/eslint/commit/90c9219181e0aadcae7224602d2988186d457113) refactor: migrate off deprecated function-style rules in all tests (#16618) (Bryan Mishkin) +* [`9b8bb72`](https://github.com/eslint/eslint/commit/9b8bb72c49a453086954b06a5d7dd390731b1975) fix: autofix recursive functions in no-var (#16611) (Milos Djermanovic) +* [`293573e`](https://github.com/eslint/eslint/commit/293573eb530d161d2a5b01efd9d3de49dadea022) docs: fix broken line numbers (#16606) (Sam Chen) +* [`fa2c64b`](https://github.com/eslint/eslint/commit/fa2c64be10d5854fb586c20957737d7d2da1975a) docs: use relative links for internal links (#16631) (Percy Ma) +* [`75276c9`](https://github.com/eslint/eslint/commit/75276c9bc7c4bc013fc6bdf277353c979934d73b) docs: reorder options in no-unused-vars (#16625) (Milos Djermanovic) +* [`7276fe5`](https://github.com/eslint/eslint/commit/7276fe5776f03fb90e575ed63a9b1a6766993e42) docs: Fix anchor in URL (#16628) (Karl Horky) +* [`6bef135`](https://github.com/eslint/eslint/commit/6bef1350e692c818c55c6d2074c12506e98cdf4f) docs: don't apply layouts to html formatter example (#16591) (Tanuj Kanti) +* [`dfc7ec1`](https://github.com/eslint/eslint/commit/dfc7ec11b11b56daaa10e8e6d08c5cddfc8c2c59) docs: Formatters page updates (#16566) (Ben Perlmutter) +* [`8ba124c`](https://github.com/eslint/eslint/commit/8ba124cfd8aaf01d14ccbcb1654798624948fb0a) docs: update the `prefer-const` example (#16607) (Pavel) +* [`e6cb05a`](https://github.com/eslint/eslint/commit/e6cb05aa35bafb9e88f161ad1fa6b01942a7c13c) docs: fix css leaking (#16603) (Sam Chen) + +v8.29.0 - December 2, 2022 + +* [`0311d81`](https://github.com/eslint/eslint/commit/0311d81834d675b8ae7cc92a460b37115edc4018) docs: Configuring Plugins page intro, page tweaks, and rename (#16534) (Ben Perlmutter) +* [`57089b1`](https://github.com/eslint/eslint/commit/57089b1ede624452bc94404b6e60d01d48cfd468) docs: add a property assignment example for camelcase rule (#16605) (Milos Djermanovic) +* [`b6ab030`](https://github.com/eslint/eslint/commit/b6ab030897d2e8b314b33a6502346a4ac45bb8da) docs: add docs codeowners (#16601) (Strek) +* [`7628403`](https://github.com/eslint/eslint/commit/7628403a57d9d9b4e2cb2b36309170900f58832e) chore: add discord channel link (#16590) (Amaresh S M) +* [`49a07c5`](https://github.com/eslint/eslint/commit/49a07c52c5af7e98d161ff4acd44bbbe0aa6383b) feat: add `allowParensAfterCommentPattern` option to no-extra-parens (#16561) (Nitin Kumar) +* [`6380c87`](https://github.com/eslint/eslint/commit/6380c87c563be5dc78ce0ddd5c7409aaf71692bb) docs: fix sitemap and feed (#16592) (Milos Djermanovic) +* [`e6a865d`](https://github.com/eslint/eslint/commit/e6a865d70aed9e1c07be712e40c38da1a5dda849) feat: `prefer-named-capture-group` add suggestions (#16544) (Josh Goldberg) +* [`ade621d`](https://github.com/eslint/eslint/commit/ade621dd12fcd3b65644bb3468248cc040db756c) docs: perf debounce the search query (#16586) (Shanmughapriyan S) +* [`a91332b`](https://github.com/eslint/eslint/commit/a91332b8bd9adfa2aa8110071bdf73f56d400050) feat: In no-invalid-regexp validate flags also for non-literal patterns (#16583) (trosos) +* [`fbcf3ab`](https://github.com/eslint/eslint/commit/fbcf3abd54dd20aec3c695cacece56493633c97f) docs: fix searchbar clear button (#16585) (Shanmughapriyan S) +* [`f894035`](https://github.com/eslint/eslint/commit/f89403553b31d24f4fc841424cc7dcb8c3ef689f) docs: HTTPS link to yeoman.io (#16582) (Christian Oliff) +* [`de12b26`](https://github.com/eslint/eslint/commit/de12b266f2aa6f063d0af888b8f0de41d09ec33f) docs: Update configuration file pages (#16509) (Ben Perlmutter) +* [`f5808cb`](https://github.com/eslint/eslint/commit/f5808cb51529174a67b4938223f06435ad6d5118) chore: fix rule doc headers check (#16564) (Milos Djermanovic) +* [`1ae9f20`](https://github.com/eslint/eslint/commit/1ae9f2067442434c6ccc6b41703624b302d17c67) docs: update correct code examples for `no-extra-parens` rule (#16560) (Nitin Kumar) + +v8.28.0 - November 18, 2022 + +* [`34c05a7`](https://github.com/eslint/eslint/commit/34c05a779ada3142995392ae12978461900088df) docs: Language Options page intro and tweaks (#16511) (Ben Perlmutter) +* [`3e66387`](https://github.com/eslint/eslint/commit/3e663873c97773ab1ecdff54aaa122075d5bb389) docs: add intro and edit ignoring files page (#16510) (Ben Perlmutter) +* [`436f712`](https://github.com/eslint/eslint/commit/436f712843360f98b2bd63256bf0c4f77013b54c) docs: fix Header UI inconsistency (#16464) (Tanuj Kanti) +* [`f743816`](https://github.com/eslint/eslint/commit/f74381696703d8eed0e175d42f96904a3d1cb4cb) docs: switch to wrench emoji for auto-fixable rules (#16545) (Bryan Mishkin) +* [`bc0547e`](https://github.com/eslint/eslint/commit/bc0547eb149a1e04211826662d2d798fb331983d) docs: improve styles for versions and languages page (#16553) (Nitin Kumar) +* [`6070f58`](https://github.com/eslint/eslint/commit/6070f58d802d77c6c781c6bc1f554eef8b3d8f68) docs: clarify esquery issue workaround (#16556) (Milos Djermanovic) +* [`b48e4f8`](https://github.com/eslint/eslint/commit/b48e4f89c59bd1c5408e3db492a0e95a402820bd) docs: Command Line Interface intro and tweaks (#16535) (Ben Perlmutter) +* [`b92b30f`](https://github.com/eslint/eslint/commit/b92b30f93db64314827305b552cbb832c63fa949) docs: Add Rules page intro and content tweaks (#16523) (Ben Perlmutter) +* [`1769b42`](https://github.com/eslint/eslint/commit/1769b423392512db4adf1eff75896c1ac0c3606b) docs: Integrations page introduction (#16548) (Ben Perlmutter) +* [`63bce44`](https://github.com/eslint/eslint/commit/63bce44e7b6326e1e94fc7f6283df8de7bbac273) feat: add `ignoreClassFieldInitialValues` option to no-magic-numbers (#16539) (Milos Djermanovic) +* [`c50ae4f`](https://github.com/eslint/eslint/commit/c50ae4f840d1ee9dc7b80a46c887398c0ec0a67c) fix: Ensure that dot files are found with globs. (#16550) (Nicholas C. Zakas) +* [`a8d0a57`](https://github.com/eslint/eslint/commit/a8d0a57cbc29a917258df41d3254ecd29bcf61ab) docs: make table of contents sticky on desktop (#16506) (Sam Chen) +* [`9432b67`](https://github.com/eslint/eslint/commit/9432b67f76ddd7b8a73d37e8a041a9ff25822f0c) fix: throw error for first unmatched pattern (#16533) (Milos Djermanovic) +* [`8385ecd`](https://github.com/eslint/eslint/commit/8385ecdbbe342211e20aebe76fa7affe8ec04c33) feat: multiline properties in rule `key-spacing` with option `align` (#16532) (Francesco Trotta) +* [`a4e89db`](https://github.com/eslint/eslint/commit/a4e89dbe85589dab982885872dc206e090c27b3c) feat: `no-obj-calls` support `Intl` (#16543) (Sosuke Suzuki) +* [`a01315a`](https://github.com/eslint/eslint/commit/a01315a7d8f3a70468b7a644fde01d6983778c6b) docs: fix route of japanese translation site (#16542) (Tanuj Kanti) +* [`e94a4a9`](https://github.com/eslint/eslint/commit/e94a4a95ee301b0344d3292c37a0b29d8e18ab30) chore: Add tests to verify #16038 is fixed (#16538) (Nicholas C. Zakas) +* [`0515628`](https://github.com/eslint/eslint/commit/05156285396eba9ce3d3a0990a8c89d5bc229636) docs: use emoji instead of svg for deprecated rule (#16536) (Bryan Mishkin) +* [`e76c382`](https://github.com/eslint/eslint/commit/e76c3827727b48c16af8467c02c31160e5595d83) fix: allow `* 1` when followed by `/` in no-implicit-coercion (#16522) (Milos Djermanovic) +* [`68f1288`](https://github.com/eslint/eslint/commit/68f12882fbaeda8ffb26425d42d261346ff5af51) docs: set default layouts (#16484) (Percy Ma) +* [`e13f194`](https://github.com/eslint/eslint/commit/e13f194f89f591730aa955f7b62192c7e8296069) chore: stricter validation of `meta.docs.description` in core rules (#16529) (Milos Djermanovic) +* [`776827a`](https://github.com/eslint/eslint/commit/776827a1748da88a25e7903bd794f5439de922b5) docs: init config about specifying shared configs (#16483) (Percy Ma) +* [`72dbfbc`](https://github.com/eslint/eslint/commit/72dbfbc0c45d2b9d19b21c6a5a6b4ca71403ffbf) chore: use `pkg` parameter in `getNpmPackageVersion` (#16525) (webxmsj) +* [`5c39425`](https://github.com/eslint/eslint/commit/5c39425fc55ecc0b97bbd07ac22654c0eb4f789c) docs: fix broken link to plugins (#16520) (Ádám T. Nagy) +* [`c97c789`](https://github.com/eslint/eslint/commit/c97c7897686ac4dc2828537d6a017f3c99f7d905) docs: Add missing no-new-native-nonconstructor docs code fence (#16503) (Brandon Mills) + +v8.27.0 - November 6, 2022 + +* [`f14587c`](https://github.com/eslint/eslint/commit/f14587c42bb0fe6ec89529aede045a488083d6ee) feat: new `no-new-native-nonconstructor` rule (#16368) (Sosuke Suzuki) +* [`978799b`](https://github.com/eslint/eslint/commit/978799bd5c76fecf4ce8f17d89ad6c9f436c3228) feat: add new rule `no-empty-static-block` (#16325) (Sosuke Suzuki) +* [`ce93b42`](https://github.com/eslint/eslint/commit/ce93b429bf917640473dd7e26b49bba993c68ce4) docs: Stylelint property-no-unknown (#16497) (Nick Schonning) +* [`d2cecb4`](https://github.com/eslint/eslint/commit/d2cecb4ad2a6d33444cf0288a863c43acb3b468a) docs: Stylelint declaration-block-no-shorthand-property-overrides (#16498) (Nick Schonning) +* [`0a92805`](https://github.com/eslint/eslint/commit/0a92805d7713118866e519b0ff2a61c5d6238ad9) docs: stylelint color-hex-case (#16496) (Nick Schonning) +* [`c3ce521`](https://github.com/eslint/eslint/commit/c3ce5212f672d95dde3465d7d3c4bf99ff665f8b) fix: Ensure unmatched glob patterns throw an error (#16462) (Nicholas C. Zakas) +* [`74a5af4`](https://github.com/eslint/eslint/commit/74a5af487ac7296a46a8078e585f00df72b63d83) docs: fix stylelint error (#16491) (Milos Djermanovic) +* [`69216ee`](https://github.com/eslint/eslint/commit/69216ee69c7172e847b64e0e934b5121a34d0ea3) feat: no-empty suggest to add comment in empty BlockStatement (#16470) (Nitin Kumar) +* [`324db1a`](https://github.com/eslint/eslint/commit/324db1a11e43ba9d954dc522763faea19129ce6a) docs: explicit stylelint color related rules (#16465) (Nick Schonning) +* [`94dc4f1`](https://github.com/eslint/eslint/commit/94dc4f19ba49fe2358f8bcc2fc3555d222766755) docs: use Stylelint for HTML files (#16468) (Nick Schonning) +* [`cc6128d`](https://github.com/eslint/eslint/commit/cc6128db4f489c3ab80fff2f9dbeea313e72208d) docs: enable stylelint declaration-block-no-duplicate-properties (#16466) (Nick Schonning) +* [`d03a8bf`](https://github.com/eslint/eslint/commit/d03a8bf8978bd330aeb951f18cc92bf1ad24eeec) docs: Add heading to justification explanation (#16430) (Maritaria) +* [`886a038`](https://github.com/eslint/eslint/commit/886a0386897f96d2da95eba8c52bd893fcbf7e86) fix: handle files with unspecified path in `getRulesMetaForResults` (#16437) (Francesco Trotta) +* [`319f0a5`](https://github.com/eslint/eslint/commit/319f0a5491598825bbd528c6d1fc12771056a74c) feat: use `context.languageOptions.ecmaVersion` in core rules (#16458) (Milos Djermanovic) +* [`8a15968`](https://github.com/eslint/eslint/commit/8a159686f9d497262d573dd601855ce28362199b) docs: add Stylelint configuration and cleanup (#16379) (Nick Schonning) +* [`9b0a469`](https://github.com/eslint/eslint/commit/9b0a469d1e4650c1d9da26239357e715b11b2d97) docs: note commit messages don't support scope (#16435) (Andy Edwards) +* [`1581405`](https://github.com/eslint/eslint/commit/15814057fd69319b3744bdea5db2455f85d2e74f) docs: improve context.getScope() docs (#16417) (Ben Perlmutter) +* [`b797149`](https://github.com/eslint/eslint/commit/b7971496e9b44add405ca0360294f5c3be85b540) docs: update formatters template (#16454) (Milos Djermanovic) +* [`5ac4de9`](https://github.com/eslint/eslint/commit/5ac4de911f712cb3a5a16eb7a4063eee09dfc97c) docs: fix link to formatters on the Core Concepts page (#16455) (Vladislav) +* [`33313ef`](https://github.com/eslint/eslint/commit/33313ef56258a6a96b00a3e70025b94bd2f2fe9f) docs: core-concepts: fix link to semi rule (#16453) (coderaiser) + +v8.26.0 - October 21, 2022 + +* [`df77409`](https://github.com/eslint/eslint/commit/df7740967ffab2915974c7b310ac76ea2915ac2d) fix: use `baseConfig` constructor option in FlatESLint (#16432) (Milos Djermanovic) +* [`33668ee`](https://github.com/eslint/eslint/commit/33668ee9d22e1988ba03e07fb547738bdb21dc0e) fix: Ensure that glob patterns are matched correctly. (#16449) (Nicholas C. Zakas) +* [`651649b`](https://github.com/eslint/eslint/commit/651649b12797594a86c0d659d6a0d1cdbda6f57b) docs: Core concepts page (#16399) (Ben Perlmutter) +* [`4715787`](https://github.com/eslint/eslint/commit/4715787724a71494ba0bb0c5fe4639570bb6985b) feat: check `Object.create()` in getter-return (#16420) (Yuki Hirasawa) +* [`e917a9a`](https://github.com/eslint/eslint/commit/e917a9a2e555d398c64b985fc933d44a42c958f0) ci: add node v19 (#16443) (Koichi ITO) +* [`740b208`](https://github.com/eslint/eslint/commit/740b20826fadc5322ea5547c1ba41793944e571d) fix: ignore messages without a `ruleId` in `getRulesMetaForResults` (#16409) (Francesco Trotta) +* [`8f9759e`](https://github.com/eslint/eslint/commit/8f9759e2a94586357d85fac902e038fabdba79a7) fix: `--ignore-pattern` in flat config mode should be relative to `cwd` (#16425) (Milos Djermanovic) +* [`325ad37`](https://github.com/eslint/eslint/commit/325ad375a52d1c7b8b8fd23943350c91781366a2) fix: make `getRulesMetaForResults` return a plain object in trivial case (#16438) (Francesco Trotta) +* [`a2810bc`](https://github.com/eslint/eslint/commit/a2810bc485d9f1123a86b60702fcaa51e19d71a3) fix: Ensure that directories can be unignored. (#16436) (Nicholas C. Zakas) +* [`631cf72`](https://github.com/eslint/eslint/commit/631cf72e82f316a2cc08770e5c81b858637ab04a) docs: note --ignore-path not supported with flat config (#16434) (Andy Edwards) +* [`1692840`](https://github.com/eslint/eslint/commit/1692840a2f763737a4891419dc304db4ebedab5d) docs: fix syntax in examples for new config files (#16427) (Milos Djermanovic) +* [`28d1902`](https://github.com/eslint/eslint/commit/28d190264017dbaa29f2ab218f73b623143cd1af) feat: `no-implicit-globals` supports `exported` block comment (#16343) (Sosuke Suzuki) +* [`35916ad`](https://github.com/eslint/eslint/commit/35916ad9bfc07dab63361721df1bd7f21e43e094) fix: Ensure unignore and reignore work correctly in flat config. (#16422) (Nicholas C. Zakas) +* [`4b70b91`](https://github.com/eslint/eslint/commit/4b70b91a6e28669ab8e2a4ce2a6d9ed40be20fa7) chore: Add VS Code issues link (#16423) (Nicholas C. Zakas) +* [`e940be7`](https://github.com/eslint/eslint/commit/e940be7a83d0caea15b64c1e1c2785a6540e2641) feat: Use ESLINT_USE_FLAT_CONFIG environment variable for flat config (#16356) (Tomer Aberbach) +* [`d336cfc`](https://github.com/eslint/eslint/commit/d336cfc9145a72bf8730250ee1e331a135e6ee2c) docs: Document extending plugin with new config (#16394) (Ben Perlmutter) +* [`dd0c58f`](https://github.com/eslint/eslint/commit/dd0c58f0f34d24331ae55139af39cf2747125f5e) feat: Swap out Globby for custom globbing solution. (#16369) (Nicholas C. Zakas) +* [`232d291`](https://github.com/eslint/eslint/commit/232d2916ac5e44db55c2ffbd2f3b37ad70037b7b) chore: suppress a Node.js deprecation warning (#16398) (Koichi ITO) + +v8.25.0 - October 7, 2022 + +* [`1f78594`](https://github.com/eslint/eslint/commit/1f785944f61c97996445e48cb74fc300142e7310) chore: upgrade @eslint/eslintrc@1.3.3 (#16397) (Milos Djermanovic) +* [`173e820`](https://github.com/eslint/eslint/commit/173e82040895ad53b2d9940bfb3fb67a0478f00b) feat: Pass --max-warnings value to formatters (#16348) (Brandon Mills) +* [`8476a9b`](https://github.com/eslint/eslint/commit/8476a9b8b81164887cdf38a21d431b75ff2956b1) chore: Remove CODEOWNERS (#16375) (Nick Schonning) +* [`720ff75`](https://github.com/eslint/eslint/commit/720ff75beb9f4fdcf2a185fcb8020cf78483fdeb) chore: use "ci" for Dependabot commit message (#16377) (Nick Schonning) +* [`90c6028`](https://github.com/eslint/eslint/commit/90c602802b6e330b79c42f282e9a615c583e32d7) docs: Conflicting fixes (#16366) (Ben Perlmutter) +* [`5a3fe70`](https://github.com/eslint/eslint/commit/5a3fe70c5261acbf115fa5f47231cbc4ac62c1bc) docs: Add VS to integrations page (#16381) (Maria José Solano) +* [`6964cb1`](https://github.com/eslint/eslint/commit/6964cb1e0f073b236cb3288b9d8be495336bbf29) feat: remove support for ignore files in FlatESLint (#16355) (Milos Djermanovic) +* [`49bd1e5`](https://github.com/eslint/eslint/commit/49bd1e5669b34fd7e0f4a3cf42009866980d7e15) docs: remove unused link definitions (#16376) (Nick Schonning) +* [`42f5479`](https://github.com/eslint/eslint/commit/42f547948f284f1c67799f237dfeb86fc400c7c7) chore: bump actions/stale from 5 to 6 (#16350) (dependabot[bot]) +* [`3bd380d`](https://github.com/eslint/eslint/commit/3bd380d3ea7e88ade4905ec0b240c866ab79a69d) docs: typo cleanups for docs (#16374) (Nick Schonning) +* [`b3a0837`](https://github.com/eslint/eslint/commit/b3a08376cfb61275a7557d6d166b6116f36e5ac2) docs: remove duplicate words (#16378) (Nick Schonning) +* [`a682562`](https://github.com/eslint/eslint/commit/a682562458948f74a227be60a80e10e7a3753124) docs: add `BigInt` to `new-cap` docs (#16362) (Sosuke Suzuki) +* [`1cc4b3a`](https://github.com/eslint/eslint/commit/1cc4b3a8f82a7945dcd8c59550b6a906a0fabbb4) feat: `id-length` counts graphemes instead of code units (#16321) (Sosuke Suzuki) +* [`f6d57fb`](https://github.com/eslint/eslint/commit/f6d57fb657c2f4e8e0140ad057da34c935482972) docs: Update docs README (#16352) (Ben Perlmutter) +* [`e5e9e27`](https://github.com/eslint/eslint/commit/e5e9e271da58361bda16f7abc8f367ccc6f91510) chore: remove `jsdoc` dev dependency (#16344) (Milos Djermanovic) +* [`7214347`](https://github.com/eslint/eslint/commit/721434705bd569e33911e25d2688e33f10898d52) docs: fix logical-assignment-operators option typo (#16346) (Jonathan Wilsson) + +v8.24.0 - September 23, 2022 + +* [`131e646`](https://github.com/eslint/eslint/commit/131e646e227b9aca3937fe287343bf2c3df408af) chore: Upgrade @humanwhocodes/config-array for perf (#16339) (Nicholas C. Zakas) +* [`2c152ff`](https://github.com/eslint/eslint/commit/2c152ff0fb709b99e62c19ecd2c95689efacbe4c) docs: note false positive `Object.getOwnPropertyNames` in prefer-reflect (#16317) (AnnAngela) +* [`bf7bd88`](https://github.com/eslint/eslint/commit/bf7bd885a92046a6b6bcbcaaa1e78e9f2c4b482f) docs: fix warn severity description for new config files (#16324) (Nitin Kumar) +* [`504fe59`](https://github.com/eslint/eslint/commit/504fe59b0e0f4f5a2afb6a69aaed5cb4ca631012) perf: switch from object spread to `Object.assign` when merging globals (#16311) (Milos Djermanovic) +* [`1729f9e`](https://github.com/eslint/eslint/commit/1729f9ea4d7b2945b2b701d72027fd4aace954cf) feat: account for `sourceType: "commonjs"` in the strict rule (#16308) (Milos Djermanovic) +* [`b0d72c9`](https://github.com/eslint/eslint/commit/b0d72c96b2a9cde7a5798c2b08ec4e70683c6aca) feat: add rule logical-assignment-operators (#16102) (fnx) +* [`f02bcd9`](https://github.com/eslint/eslint/commit/f02bcd91bf89b6c167d5346a36677fdb854f0c05) feat: `array-callback-return` support `findLast` and `findLastIndex` (#16314) (Sosuke Suzuki) +* [`8cc0bbe`](https://github.com/eslint/eslint/commit/8cc0bbe440dc5e6af6ef02f00d0514a40ca07c24) docs: use more clean link syntax (#16309) (Percy Ma) +* [`6ba269e`](https://github.com/eslint/eslint/commit/6ba269ed673f965d081287b769c12beeb5f98887) docs: fix typo (#16288) (jjangga0214) + v8.23.1 - September 12, 2022 * [`b719893`](https://github.com/eslint/eslint/commit/b71989388a921886caa4c6cb48729bbf60c46100) fix: Upgrade eslintrc to stop redefining plugins (#16297) (Brandon Mills) diff --git a/eslint/CONTRIBUTING.md b/eslint/CONTRIBUTING.md index 40163d8..c3c3a2a 100644 --- a/eslint/CONTRIBUTING.md +++ b/eslint/CONTRIBUTING.md @@ -10,18 +10,18 @@ This project adheres to the [Open JS Foundation Code of Conduct](https://eslint. Before filing an issue, please be sure to read the guidelines for what you're reporting: -* [Bug Report](https://eslint.org/docs/developer-guide/contributing/reporting-bugs) -* [Propose a New Rule](https://eslint.org/docs/developer-guide/contributing/new-rules) -* [Proposing a Rule Change](https://eslint.org/docs/developer-guide/contributing/rule-changes) -* [Request a Change](https://eslint.org/docs/developer-guide/contributing/changes) +* [Report Bugs](https://eslint.org/docs/latest/contribute/report-bugs) +* [Propose a New Rule](https://eslint.org/docs/latest/contribute/propose-new-rule) +* [Propose a Rule Change](https://eslint.org/docs/latest/contribute/propose-rule-change) +* [Request a Change](https://eslint.org/docs/latest/contribute/request-change) To report a security vulnerability in ESLint, please use our [HackerOne program](https://hackerone.com/eslint). ## Contributing Code -In order to submit code or documentation to an ESLint project, you’ll be asked to sign our CLA when you send your first pull request. (Read more about the Open JS Foundation CLA process at .) Also, please read over the [Pull Request Guidelines](https://eslint.org/docs/developer-guide/contributing/pull-requests). +In order to submit code or documentation to an ESLint project, you’ll be asked to sign our CLA when you send your first pull request. (Read more about the Open JS Foundation CLA process at .) Also, please read over the [Pull Request Guidelines](https://eslint.org/docs/latest/contribute/pull-requests). ## Full Documentation Our full contribution guidelines can be found at: - + diff --git a/eslint/Makefile.js b/eslint/Makefile.js index cbfc0c0..717cc78 100644 --- a/eslint/Makefile.js +++ b/eslint/Makefile.js @@ -15,6 +15,7 @@ const checker = require("npm-license"), fs = require("fs"), glob = require("glob"), marked = require("marked"), + matter = require("gray-matter"), markdownlint = require("markdownlint"), os = require("os"), path = require("path"), @@ -166,7 +167,7 @@ function generateBlogPost(releaseInfo, prereleaseMajorVersion) { */ function generateFormatterExamples(formatterInfo) { const output = ejs.render(cat("./templates/formatter-examples.md.ejs"), formatterInfo); - const outputDir = path.join(DOCS_SRC_DIR, "user-guide/formatters/"), + const outputDir = path.join(DOCS_SRC_DIR, "use/formatters/"), filename = path.join(outputDir, "index.md"), htmlFilename = path.join(outputDir, "html-formatter-example.html"); @@ -344,9 +345,18 @@ function generatePrerelease(prereleaseId) { */ function publishRelease() { ReleaseOps.publishRelease(); + const releaseInfo = JSON.parse(cat(".eslint-release-info.json")); + const isPreRelease = /[a-z]/u.test(releaseInfo.version); - // push to latest branch to trigger docs deploy - exec("git push origin HEAD:latest -f"); + /* + * for a pre-release, push to the "next" branch to trigger docs deploy + * for a release, push to the "latest" branch to trigger docs deploy + */ + if (isPreRelease) { + exec("git push origin HEAD:next -f"); + } else { + exec("git push origin HEAD:latest -f"); + } publishSite(); } @@ -446,8 +456,9 @@ function lintMarkdown(files) { */ function getFormatterResults() { const stripAnsi = require("strip-ansi"); + const formattersMetadata = require("./lib/cli-engine/formatters/formatters-meta.json"); - const formatterFiles = fs.readdirSync("./lib/cli-engine/formatters/"), + const formatterFiles = fs.readdirSync("./lib/cli-engine/formatters/").filter(fileName => !fileName.includes("formatters-meta.json")), rules = { "no-else-return": "warn", indent: ["warn", 4], @@ -488,7 +499,8 @@ function getFormatterResults() { ); data.formatterResults[name] = { - result: stripAnsi(formattedOutput) + result: stripAnsi(formattedOutput), + description: formattersMetadata.find(formatter => formatter.name === name).description }; } return data; @@ -518,11 +530,11 @@ target.lint = function([fix = false] = []) { * when analyzing `require()` calls from CJS modules in the `docs` directory. Since our release process does not run `npm install` * in the `docs` directory, linting would fail and break the release. Also, working on the main `eslint` package does not require * installing dependencies declared in `docs/package.json`, so most contributors will not have `docs/node_modules` locally. - * Therefore, we add `--ignore-pattern docs` to exclude linting the `docs` directory from this command. + * Therefore, we add `--ignore-pattern "docs/**"` to exclude linting the `docs` directory from this command. * There is a separate command `target.lintDocsJS` for linting JavaScript files in the `docs` directory. */ echo("Validating JavaScript files"); - lastReturn = exec(`${ESLINT}${fix ? "--fix" : ""} . --ignore-pattern docs`); + lastReturn = exec(`${ESLINT}${fix ? "--fix" : ""} . --ignore-pattern "docs/**"`); if (lastReturn.code !== 0) { errors++; } @@ -545,7 +557,7 @@ target.lintDocsJS = function([fix = false] = []) { let errors = 0; echo("Validating JavaScript files in the docs directory"); - const lastReturn = exec(`${ESLINT}${fix ? "--fix" : ""} docs/.eleventy.js`); + const lastReturn = exec(`${ESLINT}${fix ? "--fix" : ""} docs`); if (lastReturn.code !== 0) { errors++; @@ -629,7 +641,6 @@ target.karma = () => { }; target.test = function() { - target.lint(); target.checkRuleFiles(); target.mocha(); target.karma(); @@ -707,7 +718,8 @@ target.checkRuleFiles = function() { const basename = path.basename(filename, ".js"); const docFilename = `docs/src/rules/${basename}.md`; const docText = cat(docFilename); - const docMarkdown = marked.lexer(docText, { gfm: true, silent: false }); + const docTextWithoutFrontmatter = matter(String(docText)).content; + const docMarkdown = marked.lexer(docTextWithoutFrontmatter, { gfm: true, silent: false }); const ruleCode = cat(filename); const knownHeaders = ["Rule Details", "Options", "Environments", "Examples", "Known Limitations", "When Not To Use It", "Compatibility"]; @@ -823,16 +835,16 @@ target.checkRuleFiles = function() { } // check eslint:recommended - const recommended = require("./conf/eslint-recommended"); + const recommended = require("./packages/js").configs.recommended; if (ruleDef.meta.docs.recommended) { if (recommended.rules[basename] !== "error") { - console.error(`Missing rule from eslint:recommended (./conf/eslint-recommended.js): ${basename}. If you just made a rule recommended then add an entry for it in this file.`); + console.error(`Missing rule from eslint:recommended (./packages/js/src/configs/eslint-recommended.js): ${basename}. If you just made a rule recommended then add an entry for it in this file.`); errors++; } } else { if (basename in recommended.rules) { - console.error(`Extra rule in eslint:recommended (./conf/eslint-recommended.js): ${basename}. If you just added a rule then don't add an entry for it in this file.`); + console.error(`Extra rule in eslint:recommended (./packages/js/src/configs/eslint-recommended.js): ${basename}. If you just added a rule then don't add an entry for it in this file.`); errors++; } } diff --git a/eslint/README.md b/eslint/README.md index 309db3d..5060256 100644 --- a/eslint/README.md +++ b/eslint/README.md @@ -10,14 +10,14 @@ # ESLint [Website](https://eslint.org) | -[Configuring](https://eslint.org/docs/user-guide/configuring) | +[Configure ESLint](https://eslint.org/docs/latest/use/configure) | [Rules](https://eslint.org/docs/rules/) | -[Contributing](https://eslint.org/docs/developer-guide/contributing) | -[Reporting Bugs](https://eslint.org/docs/developer-guide/contributing/reporting-bugs) | +[Contribute to ESLint](https://eslint.org/docs/latest/contribute) | +[Report Bugs](https://eslint.org/docs/latest/contribute/report-bugs) | [Code of Conduct](https://eslint.org/conduct) | [Twitter](https://twitter.com/geteslint) | -[Mailing List](https://groups.google.com/group/eslint) | -[Chat Room](https://eslint.org/chat) +[Discord](https://eslint.org/chat) | +[Mastodon](https://fosstodon.org/@eslint) ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. In many ways, it is similar to JSLint and JSHint with a few exceptions: @@ -31,7 +31,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J 2. [Configuration](#configuration) 3. [Code of Conduct](#code-of-conduct) 4. [Filing Issues](#filing-issues) -5. [Frequently Asked Questions](#faq) +5. [Frequently Asked Questions](#frequently-asked-questions) 6. [Releases](#releases) 7. [Security Policy](#security-policy) 8. [Semantic Versioning Policy](#semantic-versioning-policy) @@ -41,7 +41,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J 12. [Sponsors](#sponsors) 13. [Technology Sponsors](#technology-sponsors) -## Installation and Usage +## Installation and Usage 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.) @@ -57,9 +57,9 @@ After that, you can run ESLint on any file or directory like this: ./node_modules/.bin/eslint yourfile.js ``` -## Configuration +## Configuration -After running `npm init @eslint/config`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this: +After running `npm init @eslint/config`, you'll have an `.eslintrc` file in your directory. In it, you'll see some rules configured like this: ```json { @@ -76,28 +76,28 @@ The names `"semi"` and `"quotes"` are the names of [rules](https://eslint.org/do * `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code) * `"error"` or `2` - turn the rule on as an error (exit code will be 1) -The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](https://eslint.org/docs/user-guide/configuring)). +The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](https://eslint.org/docs/latest/use/configure)). -## Code of Conduct +## Code of Conduct ESLint adheres to the [JS Foundation Code of Conduct](https://eslint.org/conduct). -## Filing Issues +## Filing Issues Before filing an issue, please be sure to read the guidelines for what you're reporting: -* [Bug Report](https://eslint.org/docs/developer-guide/contributing/reporting-bugs) -* [Propose a New Rule](https://eslint.org/docs/developer-guide/contributing/new-rules) -* [Proposing a Rule Change](https://eslint.org/docs/developer-guide/contributing/rule-changes) -* [Request a Change](https://eslint.org/docs/developer-guide/contributing/changes) +* [Bug Report](https://eslint.org/docs/latest/contribute/report-bugs) +* [Propose a New Rule](https://eslint.org/docs/latest/contribute/propose-new-rule) +* [Proposing a Rule Change](https://eslint.org/docs/latest/contribute/propose-rule-change) +* [Request a Change](https://eslint.org/docs/latest/contribute/request-change) -## Frequently Asked Questions +## Frequently Asked Questions ### I'm using JSCS, should I migrate to ESLint? Yes. [JSCS has reached end of life](https://eslint.org/blog/2016/07/jscs-end-of-life) and is no longer supported. -We have prepared a [migration guide](https://eslint.org/docs/user-guide/migrating-from-jscs) to help you convert your JSCS settings to an ESLint configuration. +We have prepared a [migration guide](https://eslint.org/docs/latest/use/migrating-from-jscs) to help you convert your JSCS settings to an ESLint configuration. We are now at or near 100% compatibility with JSCS. If you try ESLint and believe we are not yet compatible with a JSCS rule/configuration, please create an issue (mentioning that it is a JSCS compatibility issue) and we will evaluate it as per our normal process. @@ -113,11 +113,11 @@ No, ESLint does both traditional linting (looking for problematic patterns) and ### Does ESLint support JSX? -Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](https://eslint.org/docs/user-guide/configuring)). Please note that supporting JSX syntax *is not* the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics. +Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](https://eslint.org/docs/latest/use/configure)). Please note that supporting JSX syntax *is not* the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics. ### What ECMAScript versions does ESLint support? -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). +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/latest/use/configure). ### What about experimental features? @@ -125,11 +125,11 @@ ESLint's parser only officially supports the latest final ECMAScript standard. W In other cases (including if rules need to warn on more or fewer cases due to new syntax, rather than just not crashing), we recommend you use other parsers and/or rule plugins. If you are using Babel, you can use [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) and [@babel/eslint-plugin](https://www.npmjs.com/package/@babel/eslint-plugin) to use any option available in Babel. -Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the [TC39 process](https://tc39.github.io/process-document/)), we will accept issues and pull requests related to the new feature, subject to our [contributing guidelines](https://eslint.org/docs/developer-guide/contributing). Until then, please use the appropriate parser and plugin(s) for your experimental feature. +Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the [TC39 process](https://tc39.github.io/process-document/)), we will accept issues and pull requests related to the new feature, subject to our [contributing guidelines](https://eslint.org/docs/latest/contribute). Until then, please use the appropriate parser and plugin(s) for your experimental feature. ### Where to ask for help? -Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://eslint.org/chat). +Open a [discussion](https://github.com/eslint/eslint/discussions) or stop by our [Discord server](https://eslint.org/chat). ### Why doesn't ESLint lock dependency versions? @@ -141,15 +141,15 @@ We intentionally don't lock dependency versions so that we have the latest compa The Twilio blog has a [deeper dive](https://www.twilio.com/blog/lockfiles-nodejs) to learn more. -## Releases +## Releases We have scheduled releases every two weeks on Friday or Saturday. You can follow a [release issue](https://github.com/eslint/eslint/issues?q=is%3Aopen+is%3Aissue+label%3Arelease) for updates about the scheduling of any particular release. -## Security Policy +## Security Policy ESLint takes security seriously. We work hard to ensure that ESLint is safe for everyone and that security issues are addressed quickly and responsibly. Read the full [security policy](https://github.com/eslint/.github/blob/master/SECURITY.md). -## Semantic Versioning Policy +## Semantic Versioning Policy ESLint follows [semantic versioning](https://semver.org). However, due to the nature of ESLint as a code quality tool, it's not always clear when a minor or major version bump occurs. To help clarify this for everyone, we've defined the following semantic versioning policy for ESLint: @@ -182,7 +182,7 @@ ESLint follows [semantic versioning](https://semver.org). However, due to the na According to our policy, any minor update may report more linting errors than the previous release (ex: from a bug fix). As such, we recommend using the tilde (`~`) in `package.json` e.g. `"eslint": "~3.1.0"` to guarantee the results of your builds. -## Stylistic Rule Updates +## Stylistic Rule Updates Stylistic rules are frozen according to [our policy](https://eslint.org/blog/2020/05/changes-to-rules-policies) on how we evaluate new rules and rule changes. This means: @@ -191,11 +191,11 @@ This means: * **New ECMAScript features**: We will also make sure stylistic rules are compatible with new ECMAScript features. * **New options**: We will **not** add any new options to stylistic rules unless an option is the only way to fix a bug or support a newly-added ECMAScript feature. -## License +## License [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_large) -## Team +## Team These folks keep the project moving and are resources for help. @@ -213,11 +213,6 @@ The people who manage releases, review feature requests, and meet regularly to e Nicholas C. Zakas - -
-Brandon Mills -
-
Milos Djermanovic @@ -245,53 +240,54 @@ Nitin Kumar The people who review and fix bugs and help triage issues.
- -
-Brett Zamir -
-

Bryan Mishkin
- -
-Sara Soueidan +
+
+Francesco Trotta +
+
+ +### Website Team + +Team members who focus specifically on eslint.org + +
+ +
+Amaresh S M
- -
-Pig Fang +
+
+Strek
- -
-Anix -
-
- -
-YeonJuan +
+
+Percy Ma
-##
Sponsors +## 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.

Platinum Sponsors

-

Automattic

Gold Sponsors

-

Salesforce Airbnb American Express

Silver Sponsors

-

Liftoff

Bronze Sponsors

-

launchdarkly Nx (by Nrwl) Anagram Solver VPS Icons8: free icons, photos, illustrations, and music Discord ThemeIsle Ignition HeroCoders

+

Chrome Frameworks Fund Automattic

Gold Sponsors

+

Salesforce Airbnb

Silver Sponsors

+

Sentry Liftoff American Express

Bronze Sponsors

+

Ilmaiset Pitkävetovihjeet PayDay Say ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

-## Technology Sponsors +## Technology Sponsors * Site search ([eslint.org](https://eslint.org)) is sponsored by [Algolia](https://www.algolia.com) * Hosting for ([eslint.org](https://eslint.org)) is sponsored by [Netlify](https://www.netlify.com) diff --git a/eslint/conf/rule-type-list.json b/eslint/conf/rule-type-list.json index f362aa4..d5823ac 100644 --- a/eslint/conf/rule-type-list.json +++ b/eslint/conf/rule-type-list.json @@ -6,12 +6,12 @@ ], "deprecated": { "name": "Deprecated", - "description": "These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:", + "description": "These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:", "rules": [] }, "removed": { "name": "Removed", - "description": "These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:", + "description": "These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:", "rules": [ { "removed": "generator-star", "replacedBy": ["generator-star-spacing"] }, { "removed": "global-strict", "replacedBy": ["strict"] }, diff --git a/eslint/docs/.eleventy.js b/eslint/docs/.eleventy.js index b3e3026..1bd1de1 100644 --- a/eslint/docs/.eleventy.js +++ b/eslint/docs/.eleventy.js @@ -10,7 +10,7 @@ const Image = require("@11ty/eleventy-img"); const path = require("path"); const { slug } = require("github-slugger"); const yaml = require("js-yaml"); - +const { highlighter, lineNumberPlugin } = require("./src/_plugins/md-syntax-highlighter"); const { DateTime } = require("luxon"); @@ -25,8 +25,9 @@ module.exports = function(eleventyConfig) { * it's easier to see if URLs are broken. * * When a release is published, HEAD is pushed to the "latest" branch. - * Netlify deploys that branch as well, and in that case, we want the - * docs to be loaded from /docs/latest on eslint.org. + * When a pre-release is published, HEAD is pushed to the "next" branch. + * Netlify deploys those branches as well, and in that case, we want the + * docs to be loaded from /docs/latest or /docs/next on eslint.org. * * The path prefix is turned off for deploy previews so we can properly * see changes before deployed. @@ -38,6 +39,8 @@ module.exports = function(eleventyConfig) { pathPrefix = "/"; } else if (process.env.BRANCH === "latest") { pathPrefix = "/docs/latest/"; + } else if (process.env.BRANCH === "next") { + pathPrefix = "/docs/next/"; } //------------------------------------------------------------------------------ @@ -49,6 +52,7 @@ module.exports = function(eleventyConfig) { eleventyConfig.addGlobalData("site_name", siteName); eleventyConfig.addGlobalData("GIT_BRANCH", process.env.BRANCH); + eleventyConfig.addGlobalData("HEAD", process.env.BRANCH === "main"); eleventyConfig.addGlobalData("NOINDEX", process.env.BRANCH !== "latest"); eleventyConfig.addDataExtension("yml", contents => yaml.load(contents)); @@ -60,23 +64,12 @@ module.exports = function(eleventyConfig) { eleventyConfig.addFilter("jsonify", variable => JSON.stringify(variable)); - /** - * Takes in a string and converts to a slug - * @param {string} text text to be converted into slug - * @returns {string} slug to be used as anchors - */ - function slugify(text) { - return slug(text.replace(/[<>()[\]{}]/gu, "")) - // eslint-disable-next-line no-control-regex -- used regex from https://github.com/eslint/archive-website/blob/master/_11ty/plugins/markdown-plugins.js#L37 - .replace(/[^\u{00}-\u{FF}]/gu, ""); - } - eleventyConfig.addFilter("slugify", str => { if (!str) { return ""; } - return slugify(str); + return slug(str); }); eleventyConfig.addFilter("URIencode", str => { @@ -145,7 +138,8 @@ module.exports = function(eleventyConfig) { eleventyConfig.addPlugin(eleventyNavigationPlugin); eleventyConfig.addPlugin(syntaxHighlight, { - alwaysWrapLineHighlights: true + alwaysWrapLineHighlights: true, + templateFormats: ["liquid", "njk"] }); eleventyConfig.addPlugin(pluginRss); eleventyConfig.addPlugin(pluginTOC, { @@ -186,30 +180,32 @@ module.exports = function(eleventyConfig) { } const markdownIt = require("markdown-it"); + const md = markdownIt({ html: true, linkify: true, typographer: true, highlight: (str, lang) => highlighter(md, str, lang) }) + .use(markdownItAnchor, { + slugify: s => slug(s) + }) + .use(markdownItContainer, "img-container", {}) + .use(markdownItContainer, "correct", {}) + .use(markdownItContainer, "incorrect", {}) + .use(markdownItContainer, "warning", { + render(tokens, idx) { + return generateAlertMarkup("warning", tokens, idx); + } + }) + .use(markdownItContainer, "tip", { + render(tokens, idx) { + return generateAlertMarkup("tip", tokens, idx); + } + }) + .use(markdownItContainer, "important", { + render(tokens, idx) { + return generateAlertMarkup("important", tokens, idx); + } + }) + .use(lineNumberPlugin) + .disable("code"); - eleventyConfig.setLibrary("md", - markdownIt({ html: true, linkify: true, typographer: true }) - .use(markdownItAnchor, { - slugify - }) - .use(markdownItContainer, "correct", {}) - .use(markdownItContainer, "incorrect", {}) - .use(markdownItContainer, "warning", { - render(tokens, idx) { - return generateAlertMarkup("warning", tokens, idx); - } - }) - .use(markdownItContainer, "tip", { - render(tokens, idx) { - return generateAlertMarkup("tip", tokens, idx); - } - }) - .use(markdownItContainer, "important", { - render(tokens, idx) { - return generateAlertMarkup("important", tokens, idx); - } - }) - .disable("code")); + eleventyConfig.setLibrary("md", md); //------------------------------------------------------------------------------ // Shortcodes @@ -247,7 +243,7 @@ module.exports = function(eleventyConfig) { eleventyConfig.addShortcode("fixable", () => `
- 🛠 Fixable + 🔧 Fixable

if some problems reported by the rule are automatically fixable by the --fix command line option

@@ -452,7 +448,7 @@ module.exports = function(eleventyConfig) { * URLs with a file extension, like main.css, main.js, sitemap.xml, etc. should not be rewritten */ eleventyConfig.setBrowserSyncConfig({ - middleware: (req, res, next) => { + middleware(req, res, next) { if (!/(?:\.[a-zA-Z][^/]*|\/)$/u.test(req.url)) { req.url += ".html"; } diff --git a/eslint/docs/.stylelintrc.json b/eslint/docs/.stylelintrc.json new file mode 100644 index 0000000..ab3b3fd --- /dev/null +++ b/eslint/docs/.stylelintrc.json @@ -0,0 +1,33 @@ +{ + "extends": ["stylelint-config-standard-scss"], + "rules": { + "alpha-value-notation": "number", + "at-rule-empty-line-before": null, + "color-function-notation": "legacy", + "custom-property-empty-line-before": null, + "custom-property-pattern": null, + "declaration-block-no-duplicate-properties": [true, { + "ignore": ["consecutive-duplicates-with-different-values"] + }], + "declaration-block-no-redundant-longhand-properties": null, + "hue-degree-notation": "number", + "indentation": 4, + "max-line-length": null, + "no-descending-specificity": null, + "number-leading-zero": null, + "number-no-trailing-zeros": null, + "selector-class-pattern": null, + "value-keyword-case": null + }, + "overrides": [ + { + "files": [ + "**/*.html" + ], + "extends": ["stylelint-config-html/html", "stylelint-config-standard"] + } + ], + "ignoreFiles": [ + "_site/**" + ] + } diff --git a/eslint/docs/README.md b/eslint/docs/README.md index ee5f64e..dbf3c02 100644 --- a/eslint/docs/README.md +++ b/eslint/docs/README.md @@ -1,11 +1,25 @@ # ESLint Documentation +## Install Dependencies + +Install the necessary dependencies for the documentation site by running this +from the `docs` folder: + +```shell +npm install +``` + ## Run Locally +Run this from the `docs` folder: + ```shell npm start ``` +Once the script finishes building the documentation site, you can visit it at +. + ## Scripts To update the links data file, run this from the root folder (not the `docs` folder): @@ -25,3 +39,7 @@ To autofix JS files, run this from the root folder (not the `docs` folder): ```shell npm run fix:docsjs ``` + +## License + +© OpenJS Foundation and ESLint contributors, [www.openjsf.org](https://www.openjsf.org/). Content licensed under [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/). diff --git a/eslint/docs/package.json b/eslint/docs/package.json index 788af86..7be17b9 100644 --- a/eslint/docs/package.json +++ b/eslint/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.23.1", + "version": "8.41.0", "description": "", "main": "index.js", "keywords": [], @@ -10,12 +10,17 @@ "files": [], "scripts": { "images": "imagemin '_site/assets/images' --out-dir='_site/assets/images'", - "watch:sass": "sass --watch --poll src/assets/scss:src/assets/css", + "watch:postcss": "postcss src/assets/css -d src/assets/css --watch --poll", + "watch:sass": "sass --watch --poll src/assets/scss:src/assets/css --no-source-map", "watch:eleventy": "eleventy --serve --port=2023", - "build:sass": "sass --style=compressed src/assets/scss:src/assets/css --no-source-map", + "build:postcss": "postcss src/assets/css -d src/assets/css", + "build:sass": "sass src/assets/scss:src/assets/css --no-source-map", "build:eleventy": "npx @11ty/eleventy", - "start": "npm-run-all build:sass --parallel watch:*", - "build": "npm-run-all build:sass build:eleventy images" + "start": "npm-run-all build:sass build:postcss --parallel watch:*", + "build": "npm-run-all build:sass build:postcss build:eleventy images", + "lint:scss": "stylelint \"**/*.{scss,html}\"", + "lint:links": "cross-env NODE_OPTIONS=--max-old-space-size=4096 node tools/validate-links.js", + "lint:fix:scss": "npm run lint:scss -- --fix" }, "devDependencies": { "@11ty/eleventy": "^1.0.1", @@ -23,13 +28,18 @@ "@11ty/eleventy-navigation": "^0.3.2", "@11ty/eleventy-plugin-rss": "^1.1.1", "@11ty/eleventy-plugin-syntaxhighlight": "^3.1.2", + "@munter/tap-render": "^0.2.0", "@types/markdown-it": "^12.2.3", "algoliasearch": "^4.12.1", + "autoprefixer": "^10.4.13", + "cross-env": "^7.0.3", + "cssnano": "^5.1.14", "dom-parser": "^0.1.6", "eleventy-plugin-nesting-toc": "^1.3.0", "eleventy-plugin-page-assets": "^0.3.0", "eleventy-plugin-reading-time": "^0.0.1", - "github-slugger": "^1.4.0", + "github-slugger": "^1.5.0", + "hyperlink": "^5.0.4", "imagemin": "^8.0.1", "imagemin-cli": "^7.0.0", "js-yaml": "^3.14.1", @@ -39,10 +49,22 @@ "markdown-it-container": "^3.0.0", "netlify-cli": "^10.3.1", "npm-run-all": "^4.1.5", + "postcss-cli": "^10.0.0", + "postcss-html": "^1.5.0", + "prismjs": "^1.29.0", "rimraf": "^3.0.2", - "sass": "^1.52.1" + "sass": "^1.52.1", + "stylelint": "^14.13.0", + "stylelint-config-html": "^1.1.0", + "stylelint-config-standard": "^29.0.0", + "stylelint-config-standard-scss": "^5.0.0", + "tap-spot": "^1.1.2" }, "engines": { "node": ">=14.0.0" - } + }, + "browserslist": [ + "defaults", + "IE 11" + ] } diff --git a/eslint/docs/postcss.config.js b/eslint/docs/postcss.config.js new file mode 100644 index 0000000..128e741 --- /dev/null +++ b/eslint/docs/postcss.config.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + plugins: [ + require("autoprefixer"), + require("cssnano") + ], + map: false +}; diff --git a/eslint/docs/src/_data/config.json b/eslint/docs/src/_data/config.json index 4acb482..9bd751d 100644 --- a/eslint/docs/src/_data/config.json +++ b/eslint/docs/src/_data/config.json @@ -1,4 +1,5 @@ { "lang": "en", - "version": "7.26.0" + "version": "7.26.0", + "showNextVersion": false } diff --git a/eslint/docs/src/_data/further_reading_links.json b/eslint/docs/src/_data/further_reading_links.json index dc81f7f..120dd37 100644 --- a/eslint/docs/src/_data/further_reading_links.json +++ b/eslint/docs/src/_data/further_reading_links.json @@ -698,5 +698,26 @@ "logo": "https://eslint.org/apple-touch-icon.png", "title": "Interesting bugs caught by no-constant-binary-expression - ESLint - Pluggable JavaScript Linter", "description": "A pluggable and configurable linter tool for identifying and reporting on patterns in JavaScript. Maintain your code quality with ease." + }, + "https://github.com/tc39/proposal-class-static-block": { + "domain": "github.com", + "url": "https://github.com/tc39/proposal-class-static-block", + "logo": "https://github.com/fluidicon.png", + "title": "GitHub - tc39/proposal-class-static-block: ECMAScript class static initialization blocks", + "description": "ECMAScript class static initialization blocks. Contribute to tc39/proposal-class-static-block development by creating an account on GitHub." + }, + "https://tc39.es/ecma262/#sec-symbol-constructor": { + "domain": "tc39.es", + "url": "https://tc39.es/ecma262/#sec-symbol-constructor", + "logo": "https://tc39.es/ecma262/img/favicon.ico", + "title": "ECMAScript® 2023 Language Specification", + "description": null + }, + "https://tc39.es/ecma262/#sec-bigint-constructor": { + "domain": "tc39.es", + "url": "https://tc39.es/ecma262/#sec-bigint-constructor", + "logo": "https://tc39.es/ecma262/img/favicon.ico", + "title": "ECMAScript® 2023 Language Specification", + "description": null } } \ No newline at end of file diff --git a/eslint/docs/src/_data/languages.json b/eslint/docs/src/_data/languages.json index 529a9e3..7defcb6 100644 --- a/eslint/docs/src/_data/languages.json +++ b/eslint/docs/src/_data/languages.json @@ -7,9 +7,9 @@ }, { "flag": "🇯🇵", - "code": "jp", + "code": "ja", "name": "Japanese - 日本語", - "url": "https://jp.eslint.org" + "url": "https://ja.eslint.org" }, { "flag": "🇫🇷", diff --git a/eslint/docs/src/_data/layout.js b/eslint/docs/src/_data/layout.js new file mode 100644 index 0000000..2665a70 --- /dev/null +++ b/eslint/docs/src/_data/layout.js @@ -0,0 +1 @@ +module.exports = "doc.html"; diff --git a/eslint/docs/src/_data/links.json b/eslint/docs/src/_data/links.json index d9b2973..9bf8222 100644 --- a/eslint/docs/src/_data/links.json +++ b/eslint/docs/src/_data/links.json @@ -2,21 +2,18 @@ "github": "https://github.com/eslint/eslint", "twitter": "https://twitter.com/geteslint", "chat": "https://eslint.org/chat", - "group": "https://groups.google.com/group/eslint", - + "mastodon": "https://fosstodon.org/@eslint", "blog": "/blog", "docs": "/docs/latest/", "playground": "/play", - "getStarted": "/docs/latest/user-guide/getting-started", + "getStarted": "/docs/latest/use/getting-started", "sponsors": "/sponsors", "branding": "/branding", "store": "https://eslint.threadless.com", "team": "/team", - - "configuring": "https://eslint.org/docs/user-guide/configuring/", - "fixProblems": "https://eslint.org/docs/user-guide/command-line-interface#fixing-problems", - + "configuring": "https://eslint.org/docs/latest/use/configure/", + "fixProblems": "https://eslint.org/docs/latest/use/command-line-interface#fix-problems", "donate": "/donate", "openCollective": "https://opencollective.com/eslint", "githubSponsors": "https://github.com/sponsors/eslint" -} +} \ No newline at end of file diff --git a/eslint/docs/src/_data/rule_versions.json b/eslint/docs/src/_data/rule_versions.json index 98463d4..3e0b0ad 100644 --- a/eslint/docs/src/_data/rule_versions.json +++ b/eslint/docs/src/_data/rule_versions.json @@ -304,7 +304,10 @@ "wrap-iife": "0.0.9", "wrap-regex": "0.1.0", "yield-star-spacing": "2.0.0-alpha-1", - "yoda": "0.7.1" + "yoda": "0.7.1", + "logical-assignment-operators": "8.24.0", + "no-empty-static-block": "8.27.0", + "no-new-native-nonconstructor": "8.27.0" }, "removed": { "generator-star": "1.0.0-rc-1", diff --git a/eslint/docs/src/_data/rules.json b/eslint/docs/src/_data/rules.json index 81e0500..45caaa9 100644 --- a/eslint/docs/src/_data/rules.json +++ b/eslint/docs/src/_data/rules.json @@ -21,7 +21,7 @@ }, { "name": "for-direction", - "description": "Enforce \"for\" loop update clause moving the counter in the right direction.", + "description": "Enforce \"for\" loop update clause moving the counter in the right direction", "recommended": true, "fixable": false, "hasSuggestions": false @@ -229,6 +229,13 @@ "fixable": false, "hasSuggestions": true }, + { + "name": "no-new-native-nonconstructor", + "description": "Disallow `new` operators with global non-constructor functions", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, { "name": "no-new-symbol", "description": "Disallow `new` operators with the `Symbol` object", @@ -579,6 +586,13 @@ "fixable": false, "hasSuggestions": false }, + { + "name": "logical-assignment-operators", + "description": "Require or disallow logical assignment operator shorthand", + "recommended": false, + "fixable": true, + "hasSuggestions": true + }, { "name": "max-classes-per-file", "description": "Enforce a maximum number of classes per file", @@ -707,7 +721,7 @@ }, { "name": "no-div-regex", - "description": "Disallow division operators explicitly at the beginning of regular expressions", + "description": "Disallow equal signs explicitly at the beginning of regular expressions", "recommended": false, "fixable": true, "hasSuggestions": false @@ -724,7 +738,7 @@ "description": "Disallow empty block statements", "recommended": true, "fixable": false, - "hasSuggestions": false + "hasSuggestions": true }, { "name": "no-empty-function", @@ -733,6 +747,13 @@ "fixable": false, "hasSuggestions": false }, + { + "name": "no-empty-static-block", + "description": "Disallow empty static blocks", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, { "name": "no-eq-null", "description": "Disallow `null` comparisons without type-checking operators", @@ -1046,7 +1067,7 @@ "description": "Disallow unnecessary `return await`", "recommended": false, "fixable": false, - "hasSuggestions": false + "hasSuggestions": true }, { "name": "no-script-url", @@ -1277,7 +1298,7 @@ "description": "Enforce using named capture group in regular expression", "recommended": false, "fixable": false, - "hasSuggestions": false + "hasSuggestions": true }, { "name": "prefer-numeric-literals", @@ -1295,7 +1316,7 @@ }, { "name": "prefer-object-spread", - "description": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead.", + "description": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead", "recommended": false, "fixable": true, "hasSuggestions": false @@ -1361,7 +1382,7 @@ "description": "Enforce the use of `u` flag on RegExp", "recommended": false, "fixable": false, - "hasSuggestions": false + "hasSuggestions": true }, { "name": "require-yield", @@ -1872,7 +1893,7 @@ ], "deprecated": { "name": "Deprecated", - "description": "These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:", + "description": "These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:", "rules": [ { "name": "callback-return", @@ -1988,7 +2009,7 @@ }, "removed": { "name": "Removed", - "description": "These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:", + "description": "These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:", "rules": [ { "removed": "generator-star", diff --git a/eslint/docs/src/_data/rules_meta.json b/eslint/docs/src/_data/rules_meta.json index 56a9862..6d9fb3d 100644 --- a/eslint/docs/src/_data/rules_meta.json +++ b/eslint/docs/src/_data/rules_meta.json @@ -4,7 +4,7 @@ "docs": { "description": "Enforce getter and setter pairs in objects and classes", "recommended": false, - "url": "https://eslint.org/docs/rules/accessor-pairs" + "url": "https://eslint.org/docs/latest/rules/accessor-pairs" } }, "array-bracket-newline": { @@ -12,7 +12,7 @@ "docs": { "description": "Enforce linebreaks after opening and before closing array brackets", "recommended": false, - "url": "https://eslint.org/docs/rules/array-bracket-newline" + "url": "https://eslint.org/docs/latest/rules/array-bracket-newline" }, "fixable": "whitespace" }, @@ -21,7 +21,7 @@ "docs": { "description": "Enforce consistent spacing inside array brackets", "recommended": false, - "url": "https://eslint.org/docs/rules/array-bracket-spacing" + "url": "https://eslint.org/docs/latest/rules/array-bracket-spacing" }, "fixable": "whitespace" }, @@ -30,7 +30,7 @@ "docs": { "description": "Enforce `return` statements in callbacks of array methods", "recommended": false, - "url": "https://eslint.org/docs/rules/array-callback-return" + "url": "https://eslint.org/docs/latest/rules/array-callback-return" } }, "array-element-newline": { @@ -38,7 +38,7 @@ "docs": { "description": "Enforce line breaks after each array element", "recommended": false, - "url": "https://eslint.org/docs/rules/array-element-newline" + "url": "https://eslint.org/docs/latest/rules/array-element-newline" }, "fixable": "whitespace" }, @@ -47,7 +47,7 @@ "docs": { "description": "Require braces around arrow function bodies", "recommended": false, - "url": "https://eslint.org/docs/rules/arrow-body-style" + "url": "https://eslint.org/docs/latest/rules/arrow-body-style" }, "fixable": "code" }, @@ -56,7 +56,7 @@ "docs": { "description": "Require parentheses around arrow function arguments", "recommended": false, - "url": "https://eslint.org/docs/rules/arrow-parens" + "url": "https://eslint.org/docs/latest/rules/arrow-parens" }, "fixable": "code" }, @@ -65,7 +65,7 @@ "docs": { "description": "Enforce consistent spacing before and after the arrow in arrow functions", "recommended": false, - "url": "https://eslint.org/docs/rules/arrow-spacing" + "url": "https://eslint.org/docs/latest/rules/arrow-spacing" }, "fixable": "whitespace" }, @@ -74,7 +74,7 @@ "docs": { "description": "Enforce the use of variables within the scope they are defined", "recommended": false, - "url": "https://eslint.org/docs/rules/block-scoped-var" + "url": "https://eslint.org/docs/latest/rules/block-scoped-var" } }, "block-spacing": { @@ -82,7 +82,7 @@ "docs": { "description": "Disallow or enforce spaces inside of blocks after opening block and before closing block", "recommended": false, - "url": "https://eslint.org/docs/rules/block-spacing" + "url": "https://eslint.org/docs/latest/rules/block-spacing" }, "fixable": "whitespace" }, @@ -91,7 +91,7 @@ "docs": { "description": "Enforce consistent brace style for blocks", "recommended": false, - "url": "https://eslint.org/docs/rules/brace-style" + "url": "https://eslint.org/docs/latest/rules/brace-style" }, "fixable": "whitespace" }, @@ -102,7 +102,7 @@ "docs": { "description": "Require `return` statements after callbacks", "recommended": false, - "url": "https://eslint.org/docs/rules/callback-return" + "url": "https://eslint.org/docs/latest/rules/callback-return" } }, "camelcase": { @@ -110,7 +110,7 @@ "docs": { "description": "Enforce camelcase naming convention", "recommended": false, - "url": "https://eslint.org/docs/rules/camelcase" + "url": "https://eslint.org/docs/latest/rules/camelcase" } }, "capitalized-comments": { @@ -118,7 +118,7 @@ "docs": { "description": "Enforce or disallow capitalization of the first letter of a comment", "recommended": false, - "url": "https://eslint.org/docs/rules/capitalized-comments" + "url": "https://eslint.org/docs/latest/rules/capitalized-comments" }, "fixable": "code" }, @@ -127,7 +127,7 @@ "docs": { "description": "Enforce that class methods utilize `this`", "recommended": false, - "url": "https://eslint.org/docs/rules/class-methods-use-this" + "url": "https://eslint.org/docs/latest/rules/class-methods-use-this" } }, "comma-dangle": { @@ -135,7 +135,7 @@ "docs": { "description": "Require or disallow trailing commas", "recommended": false, - "url": "https://eslint.org/docs/rules/comma-dangle" + "url": "https://eslint.org/docs/latest/rules/comma-dangle" }, "fixable": "code" }, @@ -144,7 +144,7 @@ "docs": { "description": "Enforce consistent spacing before and after commas", "recommended": false, - "url": "https://eslint.org/docs/rules/comma-spacing" + "url": "https://eslint.org/docs/latest/rules/comma-spacing" }, "fixable": "whitespace" }, @@ -153,7 +153,7 @@ "docs": { "description": "Enforce consistent comma style", "recommended": false, - "url": "https://eslint.org/docs/rules/comma-style" + "url": "https://eslint.org/docs/latest/rules/comma-style" }, "fixable": "code" }, @@ -162,7 +162,7 @@ "docs": { "description": "Enforce a maximum cyclomatic complexity allowed in a program", "recommended": false, - "url": "https://eslint.org/docs/rules/complexity" + "url": "https://eslint.org/docs/latest/rules/complexity" } }, "computed-property-spacing": { @@ -170,7 +170,7 @@ "docs": { "description": "Enforce consistent spacing inside computed property brackets", "recommended": false, - "url": "https://eslint.org/docs/rules/computed-property-spacing" + "url": "https://eslint.org/docs/latest/rules/computed-property-spacing" }, "fixable": "whitespace" }, @@ -179,7 +179,7 @@ "docs": { "description": "Require `return` statements to either always or never specify values", "recommended": false, - "url": "https://eslint.org/docs/rules/consistent-return" + "url": "https://eslint.org/docs/latest/rules/consistent-return" } }, "consistent-this": { @@ -187,7 +187,7 @@ "docs": { "description": "Enforce consistent naming when capturing the current execution context", "recommended": false, - "url": "https://eslint.org/docs/rules/consistent-this" + "url": "https://eslint.org/docs/latest/rules/consistent-this" } }, "constructor-super": { @@ -195,7 +195,7 @@ "docs": { "description": "Require `super()` calls in constructors", "recommended": true, - "url": "https://eslint.org/docs/rules/constructor-super" + "url": "https://eslint.org/docs/latest/rules/constructor-super" } }, "curly": { @@ -203,7 +203,7 @@ "docs": { "description": "Enforce consistent brace style for all control statements", "recommended": false, - "url": "https://eslint.org/docs/rules/curly" + "url": "https://eslint.org/docs/latest/rules/curly" }, "fixable": "code" }, @@ -212,7 +212,7 @@ "docs": { "description": "Require `default` cases in `switch` statements", "recommended": false, - "url": "https://eslint.org/docs/rules/default-case" + "url": "https://eslint.org/docs/latest/rules/default-case" } }, "default-case-last": { @@ -220,7 +220,7 @@ "docs": { "description": "Enforce default clauses in switch statements to be last", "recommended": false, - "url": "https://eslint.org/docs/rules/default-case-last" + "url": "https://eslint.org/docs/latest/rules/default-case-last" } }, "default-param-last": { @@ -228,7 +228,7 @@ "docs": { "description": "Enforce default parameters to be last", "recommended": false, - "url": "https://eslint.org/docs/rules/default-param-last" + "url": "https://eslint.org/docs/latest/rules/default-param-last" } }, "dot-location": { @@ -236,7 +236,7 @@ "docs": { "description": "Enforce consistent newlines before and after dots", "recommended": false, - "url": "https://eslint.org/docs/rules/dot-location" + "url": "https://eslint.org/docs/latest/rules/dot-location" }, "fixable": "code" }, @@ -245,7 +245,7 @@ "docs": { "description": "Enforce dot notation whenever possible", "recommended": false, - "url": "https://eslint.org/docs/rules/dot-notation" + "url": "https://eslint.org/docs/latest/rules/dot-notation" }, "fixable": "code" }, @@ -254,7 +254,7 @@ "docs": { "description": "Require or disallow newline at the end of files", "recommended": false, - "url": "https://eslint.org/docs/rules/eol-last" + "url": "https://eslint.org/docs/latest/rules/eol-last" }, "fixable": "whitespace" }, @@ -263,16 +263,16 @@ "docs": { "description": "Require the use of `===` and `!==`", "recommended": false, - "url": "https://eslint.org/docs/rules/eqeqeq" + "url": "https://eslint.org/docs/latest/rules/eqeqeq" }, "fixable": "code" }, "for-direction": { "type": "problem", "docs": { - "description": "Enforce \"for\" loop update clause moving the counter in the right direction.", + "description": "Enforce \"for\" loop update clause moving the counter in the right direction", "recommended": true, - "url": "https://eslint.org/docs/rules/for-direction" + "url": "https://eslint.org/docs/latest/rules/for-direction" }, "fixable": null }, @@ -281,7 +281,7 @@ "docs": { "description": "Require or disallow spacing between function identifiers and their invocations", "recommended": false, - "url": "https://eslint.org/docs/rules/func-call-spacing" + "url": "https://eslint.org/docs/latest/rules/func-call-spacing" }, "fixable": "whitespace" }, @@ -290,7 +290,7 @@ "docs": { "description": "Require function names to match the name of the variable or property to which they are assigned", "recommended": false, - "url": "https://eslint.org/docs/rules/func-name-matching" + "url": "https://eslint.org/docs/latest/rules/func-name-matching" } }, "func-names": { @@ -298,7 +298,7 @@ "docs": { "description": "Require or disallow named `function` expressions", "recommended": false, - "url": "https://eslint.org/docs/rules/func-names" + "url": "https://eslint.org/docs/latest/rules/func-names" } }, "func-style": { @@ -306,7 +306,7 @@ "docs": { "description": "Enforce the consistent use of either `function` declarations or expressions", "recommended": false, - "url": "https://eslint.org/docs/rules/func-style" + "url": "https://eslint.org/docs/latest/rules/func-style" } }, "function-call-argument-newline": { @@ -314,7 +314,7 @@ "docs": { "description": "Enforce line breaks between arguments of a function call", "recommended": false, - "url": "https://eslint.org/docs/rules/function-call-argument-newline" + "url": "https://eslint.org/docs/latest/rules/function-call-argument-newline" }, "fixable": "whitespace" }, @@ -323,7 +323,7 @@ "docs": { "description": "Enforce consistent line breaks inside function parentheses", "recommended": false, - "url": "https://eslint.org/docs/rules/function-paren-newline" + "url": "https://eslint.org/docs/latest/rules/function-paren-newline" }, "fixable": "whitespace" }, @@ -332,7 +332,7 @@ "docs": { "description": "Enforce consistent spacing around `*` operators in generator functions", "recommended": false, - "url": "https://eslint.org/docs/rules/generator-star-spacing" + "url": "https://eslint.org/docs/latest/rules/generator-star-spacing" }, "fixable": "whitespace" }, @@ -341,7 +341,7 @@ "docs": { "description": "Enforce `return` statements in getters", "recommended": true, - "url": "https://eslint.org/docs/rules/getter-return" + "url": "https://eslint.org/docs/latest/rules/getter-return" }, "fixable": null }, @@ -352,7 +352,7 @@ "docs": { "description": "Require `require()` calls to be placed at top-level module scope", "recommended": false, - "url": "https://eslint.org/docs/rules/global-require" + "url": "https://eslint.org/docs/latest/rules/global-require" } }, "grouped-accessor-pairs": { @@ -360,7 +360,7 @@ "docs": { "description": "Require grouped accessor pairs in object literals and classes", "recommended": false, - "url": "https://eslint.org/docs/rules/grouped-accessor-pairs" + "url": "https://eslint.org/docs/latest/rules/grouped-accessor-pairs" } }, "guard-for-in": { @@ -368,7 +368,7 @@ "docs": { "description": "Require `for-in` loops to include an `if` statement", "recommended": false, - "url": "https://eslint.org/docs/rules/guard-for-in" + "url": "https://eslint.org/docs/latest/rules/guard-for-in" } }, "handle-callback-err": { @@ -378,7 +378,7 @@ "docs": { "description": "Require error handling in callbacks", "recommended": false, - "url": "https://eslint.org/docs/rules/handle-callback-err" + "url": "https://eslint.org/docs/latest/rules/handle-callback-err" } }, "id-blacklist": { @@ -390,7 +390,7 @@ "docs": { "description": "Disallow specified identifiers", "recommended": false, - "url": "https://eslint.org/docs/rules/id-blacklist" + "url": "https://eslint.org/docs/latest/rules/id-blacklist" } }, "id-denylist": { @@ -398,7 +398,7 @@ "docs": { "description": "Disallow specified identifiers", "recommended": false, - "url": "https://eslint.org/docs/rules/id-denylist" + "url": "https://eslint.org/docs/latest/rules/id-denylist" } }, "id-length": { @@ -406,7 +406,7 @@ "docs": { "description": "Enforce minimum and maximum identifier lengths", "recommended": false, - "url": "https://eslint.org/docs/rules/id-length" + "url": "https://eslint.org/docs/latest/rules/id-length" } }, "id-match": { @@ -414,7 +414,7 @@ "docs": { "description": "Require identifiers to match a specified regular expression", "recommended": false, - "url": "https://eslint.org/docs/rules/id-match" + "url": "https://eslint.org/docs/latest/rules/id-match" } }, "implicit-arrow-linebreak": { @@ -422,7 +422,7 @@ "docs": { "description": "Enforce the location of arrow function bodies", "recommended": false, - "url": "https://eslint.org/docs/rules/implicit-arrow-linebreak" + "url": "https://eslint.org/docs/latest/rules/implicit-arrow-linebreak" }, "fixable": "whitespace" }, @@ -431,7 +431,7 @@ "docs": { "description": "Enforce consistent indentation", "recommended": false, - "url": "https://eslint.org/docs/rules/indent" + "url": "https://eslint.org/docs/latest/rules/indent" }, "fixable": "whitespace" }, @@ -440,7 +440,7 @@ "docs": { "description": "Enforce consistent indentation", "recommended": false, - "url": "https://eslint.org/docs/rules/indent-legacy" + "url": "https://eslint.org/docs/latest/rules/indent-legacy" }, "deprecated": true, "replacedBy": [ @@ -453,7 +453,7 @@ "docs": { "description": "Require or disallow initialization in variable declarations", "recommended": false, - "url": "https://eslint.org/docs/rules/init-declarations" + "url": "https://eslint.org/docs/latest/rules/init-declarations" } }, "jsx-quotes": { @@ -461,7 +461,7 @@ "docs": { "description": "Enforce the consistent use of either double or single quotes in JSX attributes", "recommended": false, - "url": "https://eslint.org/docs/rules/jsx-quotes" + "url": "https://eslint.org/docs/latest/rules/jsx-quotes" }, "fixable": "whitespace" }, @@ -470,7 +470,7 @@ "docs": { "description": "Enforce consistent spacing between keys and values in object literal properties", "recommended": false, - "url": "https://eslint.org/docs/rules/key-spacing" + "url": "https://eslint.org/docs/latest/rules/key-spacing" }, "fixable": "whitespace" }, @@ -479,7 +479,7 @@ "docs": { "description": "Enforce consistent spacing before and after keywords", "recommended": false, - "url": "https://eslint.org/docs/rules/keyword-spacing" + "url": "https://eslint.org/docs/latest/rules/keyword-spacing" }, "fixable": "whitespace" }, @@ -488,7 +488,7 @@ "docs": { "description": "Enforce position of line comments", "recommended": false, - "url": "https://eslint.org/docs/rules/line-comment-position" + "url": "https://eslint.org/docs/latest/rules/line-comment-position" } }, "linebreak-style": { @@ -496,7 +496,7 @@ "docs": { "description": "Enforce consistent linebreak style", "recommended": false, - "url": "https://eslint.org/docs/rules/linebreak-style" + "url": "https://eslint.org/docs/latest/rules/linebreak-style" }, "fixable": "whitespace" }, @@ -505,7 +505,7 @@ "docs": { "description": "Require empty lines around comments", "recommended": false, - "url": "https://eslint.org/docs/rules/lines-around-comment" + "url": "https://eslint.org/docs/latest/rules/lines-around-comment" }, "fixable": "whitespace" }, @@ -514,7 +514,7 @@ "docs": { "description": "Require or disallow newlines around directives", "recommended": false, - "url": "https://eslint.org/docs/rules/lines-around-directive" + "url": "https://eslint.org/docs/latest/rules/lines-around-directive" }, "fixable": "whitespace", "deprecated": true, @@ -527,16 +527,26 @@ "docs": { "description": "Require or disallow an empty line between class members", "recommended": false, - "url": "https://eslint.org/docs/rules/lines-between-class-members" + "url": "https://eslint.org/docs/latest/rules/lines-between-class-members" }, "fixable": "whitespace" }, + "logical-assignment-operators": { + "type": "suggestion", + "docs": { + "description": "Require or disallow logical assignment operator shorthand", + "recommended": false, + "url": "https://eslint.org/docs/latest/rules/logical-assignment-operators" + }, + "fixable": "code", + "hasSuggestions": true + }, "max-classes-per-file": { "type": "suggestion", "docs": { "description": "Enforce a maximum number of classes per file", "recommended": false, - "url": "https://eslint.org/docs/rules/max-classes-per-file" + "url": "https://eslint.org/docs/latest/rules/max-classes-per-file" } }, "max-depth": { @@ -544,7 +554,7 @@ "docs": { "description": "Enforce a maximum depth that blocks can be nested", "recommended": false, - "url": "https://eslint.org/docs/rules/max-depth" + "url": "https://eslint.org/docs/latest/rules/max-depth" } }, "max-len": { @@ -552,7 +562,7 @@ "docs": { "description": "Enforce a maximum line length", "recommended": false, - "url": "https://eslint.org/docs/rules/max-len" + "url": "https://eslint.org/docs/latest/rules/max-len" } }, "max-lines": { @@ -560,7 +570,7 @@ "docs": { "description": "Enforce a maximum number of lines per file", "recommended": false, - "url": "https://eslint.org/docs/rules/max-lines" + "url": "https://eslint.org/docs/latest/rules/max-lines" } }, "max-lines-per-function": { @@ -568,7 +578,7 @@ "docs": { "description": "Enforce a maximum number of lines of code in a function", "recommended": false, - "url": "https://eslint.org/docs/rules/max-lines-per-function" + "url": "https://eslint.org/docs/latest/rules/max-lines-per-function" } }, "max-nested-callbacks": { @@ -576,7 +586,7 @@ "docs": { "description": "Enforce a maximum depth that callbacks can be nested", "recommended": false, - "url": "https://eslint.org/docs/rules/max-nested-callbacks" + "url": "https://eslint.org/docs/latest/rules/max-nested-callbacks" } }, "max-params": { @@ -584,7 +594,7 @@ "docs": { "description": "Enforce a maximum number of parameters in function definitions", "recommended": false, - "url": "https://eslint.org/docs/rules/max-params" + "url": "https://eslint.org/docs/latest/rules/max-params" } }, "max-statements": { @@ -592,7 +602,7 @@ "docs": { "description": "Enforce a maximum number of statements allowed in function blocks", "recommended": false, - "url": "https://eslint.org/docs/rules/max-statements" + "url": "https://eslint.org/docs/latest/rules/max-statements" } }, "max-statements-per-line": { @@ -600,7 +610,7 @@ "docs": { "description": "Enforce a maximum number of statements allowed per line", "recommended": false, - "url": "https://eslint.org/docs/rules/max-statements-per-line" + "url": "https://eslint.org/docs/latest/rules/max-statements-per-line" } }, "multiline-comment-style": { @@ -608,7 +618,7 @@ "docs": { "description": "Enforce a particular style for multiline comments", "recommended": false, - "url": "https://eslint.org/docs/rules/multiline-comment-style" + "url": "https://eslint.org/docs/latest/rules/multiline-comment-style" }, "fixable": "whitespace" }, @@ -617,7 +627,7 @@ "docs": { "description": "Enforce newlines between operands of ternary expressions", "recommended": false, - "url": "https://eslint.org/docs/rules/multiline-ternary" + "url": "https://eslint.org/docs/latest/rules/multiline-ternary" }, "fixable": "whitespace" }, @@ -626,7 +636,7 @@ "docs": { "description": "Require constructor names to begin with a capital letter", "recommended": false, - "url": "https://eslint.org/docs/rules/new-cap" + "url": "https://eslint.org/docs/latest/rules/new-cap" } }, "new-parens": { @@ -634,7 +644,7 @@ "docs": { "description": "Enforce or disallow parentheses when invoking a constructor with no arguments", "recommended": false, - "url": "https://eslint.org/docs/rules/new-parens" + "url": "https://eslint.org/docs/latest/rules/new-parens" }, "fixable": "code" }, @@ -643,7 +653,7 @@ "docs": { "description": "Require or disallow an empty line after variable declarations", "recommended": false, - "url": "https://eslint.org/docs/rules/newline-after-var" + "url": "https://eslint.org/docs/latest/rules/newline-after-var" }, "fixable": "whitespace", "deprecated": true, @@ -656,7 +666,7 @@ "docs": { "description": "Require an empty line before `return` statements", "recommended": false, - "url": "https://eslint.org/docs/rules/newline-before-return" + "url": "https://eslint.org/docs/latest/rules/newline-before-return" }, "fixable": "whitespace", "deprecated": true, @@ -669,7 +679,7 @@ "docs": { "description": "Require a newline after each call in a method chain", "recommended": false, - "url": "https://eslint.org/docs/rules/newline-per-chained-call" + "url": "https://eslint.org/docs/latest/rules/newline-per-chained-call" }, "fixable": "whitespace" }, @@ -678,7 +688,7 @@ "docs": { "description": "Disallow the use of `alert`, `confirm`, and `prompt`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-alert" + "url": "https://eslint.org/docs/latest/rules/no-alert" } }, "no-array-constructor": { @@ -686,7 +696,7 @@ "docs": { "description": "Disallow `Array` constructors", "recommended": false, - "url": "https://eslint.org/docs/rules/no-array-constructor" + "url": "https://eslint.org/docs/latest/rules/no-array-constructor" } }, "no-async-promise-executor": { @@ -694,7 +704,7 @@ "docs": { "description": "Disallow using an async function as a Promise executor", "recommended": true, - "url": "https://eslint.org/docs/rules/no-async-promise-executor" + "url": "https://eslint.org/docs/latest/rules/no-async-promise-executor" }, "fixable": null }, @@ -703,7 +713,7 @@ "docs": { "description": "Disallow `await` inside of loops", "recommended": false, - "url": "https://eslint.org/docs/rules/no-await-in-loop" + "url": "https://eslint.org/docs/latest/rules/no-await-in-loop" } }, "no-bitwise": { @@ -711,7 +721,7 @@ "docs": { "description": "Disallow bitwise operators", "recommended": false, - "url": "https://eslint.org/docs/rules/no-bitwise" + "url": "https://eslint.org/docs/latest/rules/no-bitwise" } }, "no-buffer-constructor": { @@ -721,7 +731,7 @@ "docs": { "description": "Disallow use of the `Buffer()` constructor", "recommended": false, - "url": "https://eslint.org/docs/rules/no-buffer-constructor" + "url": "https://eslint.org/docs/latest/rules/no-buffer-constructor" } }, "no-caller": { @@ -729,7 +739,7 @@ "docs": { "description": "Disallow the use of `arguments.caller` or `arguments.callee`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-caller" + "url": "https://eslint.org/docs/latest/rules/no-caller" } }, "no-case-declarations": { @@ -737,7 +747,7 @@ "docs": { "description": "Disallow lexical declarations in case clauses", "recommended": true, - "url": "https://eslint.org/docs/rules/no-case-declarations" + "url": "https://eslint.org/docs/latest/rules/no-case-declarations" } }, "no-catch-shadow": { @@ -745,7 +755,7 @@ "docs": { "description": "Disallow `catch` clause parameters from shadowing variables in the outer scope", "recommended": false, - "url": "https://eslint.org/docs/rules/no-catch-shadow" + "url": "https://eslint.org/docs/latest/rules/no-catch-shadow" }, "replacedBy": [ "no-shadow" @@ -757,7 +767,7 @@ "docs": { "description": "Disallow reassigning class members", "recommended": true, - "url": "https://eslint.org/docs/rules/no-class-assign" + "url": "https://eslint.org/docs/latest/rules/no-class-assign" } }, "no-compare-neg-zero": { @@ -765,7 +775,7 @@ "docs": { "description": "Disallow comparing against -0", "recommended": true, - "url": "https://eslint.org/docs/rules/no-compare-neg-zero" + "url": "https://eslint.org/docs/latest/rules/no-compare-neg-zero" }, "fixable": null }, @@ -774,7 +784,7 @@ "docs": { "description": "Disallow assignment operators in conditional expressions", "recommended": true, - "url": "https://eslint.org/docs/rules/no-cond-assign" + "url": "https://eslint.org/docs/latest/rules/no-cond-assign" } }, "no-confusing-arrow": { @@ -782,7 +792,7 @@ "docs": { "description": "Disallow arrow functions where they could be confused with comparisons", "recommended": false, - "url": "https://eslint.org/docs/rules/no-confusing-arrow" + "url": "https://eslint.org/docs/latest/rules/no-confusing-arrow" }, "fixable": "code" }, @@ -791,7 +801,7 @@ "docs": { "description": "Disallow the use of `console`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-console" + "url": "https://eslint.org/docs/latest/rules/no-console" } }, "no-const-assign": { @@ -799,7 +809,7 @@ "docs": { "description": "Disallow reassigning `const` variables", "recommended": true, - "url": "https://eslint.org/docs/rules/no-const-assign" + "url": "https://eslint.org/docs/latest/rules/no-const-assign" } }, "no-constant-binary-expression": { @@ -807,7 +817,7 @@ "docs": { "description": "Disallow expressions where the operation doesn't affect the value", "recommended": false, - "url": "https://eslint.org/docs/rules/no-constant-binary-expression" + "url": "https://eslint.org/docs/latest/rules/no-constant-binary-expression" } }, "no-constant-condition": { @@ -815,7 +825,7 @@ "docs": { "description": "Disallow constant expressions in conditions", "recommended": true, - "url": "https://eslint.org/docs/rules/no-constant-condition" + "url": "https://eslint.org/docs/latest/rules/no-constant-condition" } }, "no-constructor-return": { @@ -823,7 +833,7 @@ "docs": { "description": "Disallow returning value from constructor", "recommended": false, - "url": "https://eslint.org/docs/rules/no-constructor-return" + "url": "https://eslint.org/docs/latest/rules/no-constructor-return" }, "fixable": null }, @@ -832,7 +842,7 @@ "docs": { "description": "Disallow `continue` statements", "recommended": false, - "url": "https://eslint.org/docs/rules/no-continue" + "url": "https://eslint.org/docs/latest/rules/no-continue" } }, "no-control-regex": { @@ -840,7 +850,7 @@ "docs": { "description": "Disallow control characters in regular expressions", "recommended": true, - "url": "https://eslint.org/docs/rules/no-control-regex" + "url": "https://eslint.org/docs/latest/rules/no-control-regex" } }, "no-debugger": { @@ -848,7 +858,7 @@ "docs": { "description": "Disallow the use of `debugger`", "recommended": true, - "url": "https://eslint.org/docs/rules/no-debugger" + "url": "https://eslint.org/docs/latest/rules/no-debugger" }, "fixable": null }, @@ -857,15 +867,15 @@ "docs": { "description": "Disallow deleting variables", "recommended": true, - "url": "https://eslint.org/docs/rules/no-delete-var" + "url": "https://eslint.org/docs/latest/rules/no-delete-var" } }, "no-div-regex": { "type": "suggestion", "docs": { - "description": "Disallow division operators explicitly at the beginning of regular expressions", + "description": "Disallow equal signs explicitly at the beginning of regular expressions", "recommended": false, - "url": "https://eslint.org/docs/rules/no-div-regex" + "url": "https://eslint.org/docs/latest/rules/no-div-regex" }, "fixable": "code" }, @@ -874,7 +884,7 @@ "docs": { "description": "Disallow duplicate arguments in `function` definitions", "recommended": true, - "url": "https://eslint.org/docs/rules/no-dupe-args" + "url": "https://eslint.org/docs/latest/rules/no-dupe-args" } }, "no-dupe-class-members": { @@ -882,7 +892,7 @@ "docs": { "description": "Disallow duplicate class members", "recommended": true, - "url": "https://eslint.org/docs/rules/no-dupe-class-members" + "url": "https://eslint.org/docs/latest/rules/no-dupe-class-members" } }, "no-dupe-else-if": { @@ -890,7 +900,7 @@ "docs": { "description": "Disallow duplicate conditions in if-else-if chains", "recommended": true, - "url": "https://eslint.org/docs/rules/no-dupe-else-if" + "url": "https://eslint.org/docs/latest/rules/no-dupe-else-if" } }, "no-dupe-keys": { @@ -898,7 +908,7 @@ "docs": { "description": "Disallow duplicate keys in object literals", "recommended": true, - "url": "https://eslint.org/docs/rules/no-dupe-keys" + "url": "https://eslint.org/docs/latest/rules/no-dupe-keys" } }, "no-duplicate-case": { @@ -906,7 +916,7 @@ "docs": { "description": "Disallow duplicate case labels", "recommended": true, - "url": "https://eslint.org/docs/rules/no-duplicate-case" + "url": "https://eslint.org/docs/latest/rules/no-duplicate-case" } }, "no-duplicate-imports": { @@ -914,7 +924,7 @@ "docs": { "description": "Disallow duplicate module imports", "recommended": false, - "url": "https://eslint.org/docs/rules/no-duplicate-imports" + "url": "https://eslint.org/docs/latest/rules/no-duplicate-imports" } }, "no-else-return": { @@ -922,16 +932,17 @@ "docs": { "description": "Disallow `else` blocks after `return` statements in `if` statements", "recommended": false, - "url": "https://eslint.org/docs/rules/no-else-return" + "url": "https://eslint.org/docs/latest/rules/no-else-return" }, "fixable": "code" }, "no-empty": { + "hasSuggestions": true, "type": "suggestion", "docs": { "description": "Disallow empty block statements", "recommended": true, - "url": "https://eslint.org/docs/rules/no-empty" + "url": "https://eslint.org/docs/latest/rules/no-empty" } }, "no-empty-character-class": { @@ -939,7 +950,7 @@ "docs": { "description": "Disallow empty character classes in regular expressions", "recommended": true, - "url": "https://eslint.org/docs/rules/no-empty-character-class" + "url": "https://eslint.org/docs/latest/rules/no-empty-character-class" } }, "no-empty-function": { @@ -947,7 +958,7 @@ "docs": { "description": "Disallow empty functions", "recommended": false, - "url": "https://eslint.org/docs/rules/no-empty-function" + "url": "https://eslint.org/docs/latest/rules/no-empty-function" } }, "no-empty-pattern": { @@ -955,7 +966,15 @@ "docs": { "description": "Disallow empty destructuring patterns", "recommended": true, - "url": "https://eslint.org/docs/rules/no-empty-pattern" + "url": "https://eslint.org/docs/latest/rules/no-empty-pattern" + } + }, + "no-empty-static-block": { + "type": "suggestion", + "docs": { + "description": "Disallow empty static blocks", + "recommended": false, + "url": "https://eslint.org/docs/latest/rules/no-empty-static-block" } }, "no-eq-null": { @@ -963,7 +982,7 @@ "docs": { "description": "Disallow `null` comparisons without type-checking operators", "recommended": false, - "url": "https://eslint.org/docs/rules/no-eq-null" + "url": "https://eslint.org/docs/latest/rules/no-eq-null" } }, "no-eval": { @@ -971,7 +990,7 @@ "docs": { "description": "Disallow the use of `eval()`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-eval" + "url": "https://eslint.org/docs/latest/rules/no-eval" } }, "no-ex-assign": { @@ -979,7 +998,7 @@ "docs": { "description": "Disallow reassigning exceptions in `catch` clauses", "recommended": true, - "url": "https://eslint.org/docs/rules/no-ex-assign" + "url": "https://eslint.org/docs/latest/rules/no-ex-assign" } }, "no-extend-native": { @@ -987,7 +1006,7 @@ "docs": { "description": "Disallow extending native types", "recommended": false, - "url": "https://eslint.org/docs/rules/no-extend-native" + "url": "https://eslint.org/docs/latest/rules/no-extend-native" } }, "no-extra-bind": { @@ -995,7 +1014,7 @@ "docs": { "description": "Disallow unnecessary calls to `.bind()`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-extra-bind" + "url": "https://eslint.org/docs/latest/rules/no-extra-bind" }, "fixable": "code" }, @@ -1004,7 +1023,7 @@ "docs": { "description": "Disallow unnecessary boolean casts", "recommended": true, - "url": "https://eslint.org/docs/rules/no-extra-boolean-cast" + "url": "https://eslint.org/docs/latest/rules/no-extra-boolean-cast" }, "fixable": "code" }, @@ -1013,7 +1032,7 @@ "docs": { "description": "Disallow unnecessary labels", "recommended": false, - "url": "https://eslint.org/docs/rules/no-extra-label" + "url": "https://eslint.org/docs/latest/rules/no-extra-label" }, "fixable": "code" }, @@ -1022,7 +1041,7 @@ "docs": { "description": "Disallow unnecessary parentheses", "recommended": false, - "url": "https://eslint.org/docs/rules/no-extra-parens" + "url": "https://eslint.org/docs/latest/rules/no-extra-parens" }, "fixable": "code" }, @@ -1031,7 +1050,7 @@ "docs": { "description": "Disallow unnecessary semicolons", "recommended": true, - "url": "https://eslint.org/docs/rules/no-extra-semi" + "url": "https://eslint.org/docs/latest/rules/no-extra-semi" }, "fixable": "code" }, @@ -1040,7 +1059,7 @@ "docs": { "description": "Disallow fallthrough of `case` statements", "recommended": true, - "url": "https://eslint.org/docs/rules/no-fallthrough" + "url": "https://eslint.org/docs/latest/rules/no-fallthrough" } }, "no-floating-decimal": { @@ -1048,7 +1067,7 @@ "docs": { "description": "Disallow leading or trailing decimal points in numeric literals", "recommended": false, - "url": "https://eslint.org/docs/rules/no-floating-decimal" + "url": "https://eslint.org/docs/latest/rules/no-floating-decimal" }, "fixable": "code" }, @@ -1057,7 +1076,7 @@ "docs": { "description": "Disallow reassigning `function` declarations", "recommended": true, - "url": "https://eslint.org/docs/rules/no-func-assign" + "url": "https://eslint.org/docs/latest/rules/no-func-assign" } }, "no-global-assign": { @@ -1065,7 +1084,7 @@ "docs": { "description": "Disallow assignments to native objects or read-only global variables", "recommended": true, - "url": "https://eslint.org/docs/rules/no-global-assign" + "url": "https://eslint.org/docs/latest/rules/no-global-assign" } }, "no-implicit-coercion": { @@ -1073,7 +1092,7 @@ "docs": { "description": "Disallow shorthand type conversions", "recommended": false, - "url": "https://eslint.org/docs/rules/no-implicit-coercion" + "url": "https://eslint.org/docs/latest/rules/no-implicit-coercion" }, "fixable": "code" }, @@ -1082,7 +1101,7 @@ "docs": { "description": "Disallow declarations in the global scope", "recommended": false, - "url": "https://eslint.org/docs/rules/no-implicit-globals" + "url": "https://eslint.org/docs/latest/rules/no-implicit-globals" } }, "no-implied-eval": { @@ -1090,7 +1109,7 @@ "docs": { "description": "Disallow the use of `eval()`-like methods", "recommended": false, - "url": "https://eslint.org/docs/rules/no-implied-eval" + "url": "https://eslint.org/docs/latest/rules/no-implied-eval" } }, "no-import-assign": { @@ -1098,7 +1117,7 @@ "docs": { "description": "Disallow assigning to imported bindings", "recommended": true, - "url": "https://eslint.org/docs/rules/no-import-assign" + "url": "https://eslint.org/docs/latest/rules/no-import-assign" } }, "no-inline-comments": { @@ -1106,7 +1125,7 @@ "docs": { "description": "Disallow inline comments after code", "recommended": false, - "url": "https://eslint.org/docs/rules/no-inline-comments" + "url": "https://eslint.org/docs/latest/rules/no-inline-comments" } }, "no-inner-declarations": { @@ -1114,7 +1133,7 @@ "docs": { "description": "Disallow variable or `function` declarations in nested blocks", "recommended": true, - "url": "https://eslint.org/docs/rules/no-inner-declarations" + "url": "https://eslint.org/docs/latest/rules/no-inner-declarations" } }, "no-invalid-regexp": { @@ -1122,7 +1141,7 @@ "docs": { "description": "Disallow invalid regular expression strings in `RegExp` constructors", "recommended": true, - "url": "https://eslint.org/docs/rules/no-invalid-regexp" + "url": "https://eslint.org/docs/latest/rules/no-invalid-regexp" } }, "no-invalid-this": { @@ -1130,7 +1149,7 @@ "docs": { "description": "Disallow use of `this` in contexts where the value of `this` is `undefined`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-invalid-this" + "url": "https://eslint.org/docs/latest/rules/no-invalid-this" } }, "no-irregular-whitespace": { @@ -1138,7 +1157,7 @@ "docs": { "description": "Disallow irregular whitespace", "recommended": true, - "url": "https://eslint.org/docs/rules/no-irregular-whitespace" + "url": "https://eslint.org/docs/latest/rules/no-irregular-whitespace" } }, "no-iterator": { @@ -1146,7 +1165,7 @@ "docs": { "description": "Disallow the use of the `__iterator__` property", "recommended": false, - "url": "https://eslint.org/docs/rules/no-iterator" + "url": "https://eslint.org/docs/latest/rules/no-iterator" } }, "no-label-var": { @@ -1154,7 +1173,7 @@ "docs": { "description": "Disallow labels that share a name with a variable", "recommended": false, - "url": "https://eslint.org/docs/rules/no-label-var" + "url": "https://eslint.org/docs/latest/rules/no-label-var" } }, "no-labels": { @@ -1162,7 +1181,7 @@ "docs": { "description": "Disallow labeled statements", "recommended": false, - "url": "https://eslint.org/docs/rules/no-labels" + "url": "https://eslint.org/docs/latest/rules/no-labels" } }, "no-lone-blocks": { @@ -1170,7 +1189,7 @@ "docs": { "description": "Disallow unnecessary nested blocks", "recommended": false, - "url": "https://eslint.org/docs/rules/no-lone-blocks" + "url": "https://eslint.org/docs/latest/rules/no-lone-blocks" } }, "no-lonely-if": { @@ -1178,7 +1197,7 @@ "docs": { "description": "Disallow `if` statements as the only statement in `else` blocks", "recommended": false, - "url": "https://eslint.org/docs/rules/no-lonely-if" + "url": "https://eslint.org/docs/latest/rules/no-lonely-if" }, "fixable": "code" }, @@ -1187,7 +1206,7 @@ "docs": { "description": "Disallow function declarations that contain unsafe references inside loop statements", "recommended": false, - "url": "https://eslint.org/docs/rules/no-loop-func" + "url": "https://eslint.org/docs/latest/rules/no-loop-func" } }, "no-loss-of-precision": { @@ -1195,7 +1214,7 @@ "docs": { "description": "Disallow literal numbers that lose precision", "recommended": true, - "url": "https://eslint.org/docs/rules/no-loss-of-precision" + "url": "https://eslint.org/docs/latest/rules/no-loss-of-precision" } }, "no-magic-numbers": { @@ -1203,7 +1222,7 @@ "docs": { "description": "Disallow magic numbers", "recommended": false, - "url": "https://eslint.org/docs/rules/no-magic-numbers" + "url": "https://eslint.org/docs/latest/rules/no-magic-numbers" } }, "no-misleading-character-class": { @@ -1211,7 +1230,7 @@ "docs": { "description": "Disallow characters which are made with multiple code points in character class syntax", "recommended": true, - "url": "https://eslint.org/docs/rules/no-misleading-character-class" + "url": "https://eslint.org/docs/latest/rules/no-misleading-character-class" }, "hasSuggestions": true }, @@ -1220,7 +1239,7 @@ "docs": { "description": "Disallow mixed binary operators", "recommended": false, - "url": "https://eslint.org/docs/rules/no-mixed-operators" + "url": "https://eslint.org/docs/latest/rules/no-mixed-operators" } }, "no-mixed-requires": { @@ -1230,7 +1249,7 @@ "docs": { "description": "Disallow `require` calls to be mixed with regular variable declarations", "recommended": false, - "url": "https://eslint.org/docs/rules/no-mixed-requires" + "url": "https://eslint.org/docs/latest/rules/no-mixed-requires" } }, "no-mixed-spaces-and-tabs": { @@ -1238,7 +1257,7 @@ "docs": { "description": "Disallow mixed spaces and tabs for indentation", "recommended": true, - "url": "https://eslint.org/docs/rules/no-mixed-spaces-and-tabs" + "url": "https://eslint.org/docs/latest/rules/no-mixed-spaces-and-tabs" } }, "no-multi-assign": { @@ -1246,7 +1265,7 @@ "docs": { "description": "Disallow use of chained assignment expressions", "recommended": false, - "url": "https://eslint.org/docs/rules/no-multi-assign" + "url": "https://eslint.org/docs/latest/rules/no-multi-assign" } }, "no-multi-spaces": { @@ -1254,7 +1273,7 @@ "docs": { "description": "Disallow multiple spaces", "recommended": false, - "url": "https://eslint.org/docs/rules/no-multi-spaces" + "url": "https://eslint.org/docs/latest/rules/no-multi-spaces" }, "fixable": "whitespace" }, @@ -1263,7 +1282,7 @@ "docs": { "description": "Disallow multiline strings", "recommended": false, - "url": "https://eslint.org/docs/rules/no-multi-str" + "url": "https://eslint.org/docs/latest/rules/no-multi-str" } }, "no-multiple-empty-lines": { @@ -1271,7 +1290,7 @@ "docs": { "description": "Disallow multiple empty lines", "recommended": false, - "url": "https://eslint.org/docs/rules/no-multiple-empty-lines" + "url": "https://eslint.org/docs/latest/rules/no-multiple-empty-lines" }, "fixable": "whitespace" }, @@ -1280,7 +1299,7 @@ "docs": { "description": "Disallow assignments to native objects or read-only global variables", "recommended": false, - "url": "https://eslint.org/docs/rules/no-native-reassign" + "url": "https://eslint.org/docs/latest/rules/no-native-reassign" }, "deprecated": true, "replacedBy": [ @@ -1292,7 +1311,7 @@ "docs": { "description": "Disallow negated conditions", "recommended": false, - "url": "https://eslint.org/docs/rules/no-negated-condition" + "url": "https://eslint.org/docs/latest/rules/no-negated-condition" } }, "no-negated-in-lhs": { @@ -1300,7 +1319,7 @@ "docs": { "description": "Disallow negating the left operand in `in` expressions", "recommended": false, - "url": "https://eslint.org/docs/rules/no-negated-in-lhs" + "url": "https://eslint.org/docs/latest/rules/no-negated-in-lhs" }, "replacedBy": [ "no-unsafe-negation" @@ -1312,7 +1331,7 @@ "docs": { "description": "Disallow nested ternary expressions", "recommended": false, - "url": "https://eslint.org/docs/rules/no-nested-ternary" + "url": "https://eslint.org/docs/latest/rules/no-nested-ternary" } }, "no-new": { @@ -1320,7 +1339,7 @@ "docs": { "description": "Disallow `new` operators outside of assignments or comparisons", "recommended": false, - "url": "https://eslint.org/docs/rules/no-new" + "url": "https://eslint.org/docs/latest/rules/no-new" } }, "no-new-func": { @@ -1328,7 +1347,15 @@ "docs": { "description": "Disallow `new` operators with the `Function` object", "recommended": false, - "url": "https://eslint.org/docs/rules/no-new-func" + "url": "https://eslint.org/docs/latest/rules/no-new-func" + } + }, + "no-new-native-nonconstructor": { + "type": "problem", + "docs": { + "description": "Disallow `new` operators with global non-constructor functions", + "recommended": false, + "url": "https://eslint.org/docs/latest/rules/no-new-native-nonconstructor" } }, "no-new-object": { @@ -1336,7 +1363,7 @@ "docs": { "description": "Disallow `Object` constructors", "recommended": false, - "url": "https://eslint.org/docs/rules/no-new-object" + "url": "https://eslint.org/docs/latest/rules/no-new-object" } }, "no-new-require": { @@ -1346,7 +1373,7 @@ "docs": { "description": "Disallow `new` operators with calls to `require`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-new-require" + "url": "https://eslint.org/docs/latest/rules/no-new-require" } }, "no-new-symbol": { @@ -1354,7 +1381,7 @@ "docs": { "description": "Disallow `new` operators with the `Symbol` object", "recommended": true, - "url": "https://eslint.org/docs/rules/no-new-symbol" + "url": "https://eslint.org/docs/latest/rules/no-new-symbol" } }, "no-new-wrappers": { @@ -1362,7 +1389,7 @@ "docs": { "description": "Disallow `new` operators with the `String`, `Number`, and `Boolean` objects", "recommended": false, - "url": "https://eslint.org/docs/rules/no-new-wrappers" + "url": "https://eslint.org/docs/latest/rules/no-new-wrappers" } }, "no-nonoctal-decimal-escape": { @@ -1370,7 +1397,7 @@ "docs": { "description": "Disallow `\\8` and `\\9` escape sequences in string literals", "recommended": true, - "url": "https://eslint.org/docs/rules/no-nonoctal-decimal-escape" + "url": "https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape" }, "hasSuggestions": true }, @@ -1379,7 +1406,7 @@ "docs": { "description": "Disallow calling global object properties as functions", "recommended": true, - "url": "https://eslint.org/docs/rules/no-obj-calls" + "url": "https://eslint.org/docs/latest/rules/no-obj-calls" } }, "no-octal": { @@ -1387,7 +1414,7 @@ "docs": { "description": "Disallow octal literals", "recommended": true, - "url": "https://eslint.org/docs/rules/no-octal" + "url": "https://eslint.org/docs/latest/rules/no-octal" } }, "no-octal-escape": { @@ -1395,7 +1422,7 @@ "docs": { "description": "Disallow octal escape sequences in string literals", "recommended": false, - "url": "https://eslint.org/docs/rules/no-octal-escape" + "url": "https://eslint.org/docs/latest/rules/no-octal-escape" } }, "no-param-reassign": { @@ -1403,7 +1430,7 @@ "docs": { "description": "Disallow reassigning `function` parameters", "recommended": false, - "url": "https://eslint.org/docs/rules/no-param-reassign" + "url": "https://eslint.org/docs/latest/rules/no-param-reassign" } }, "no-path-concat": { @@ -1413,7 +1440,7 @@ "docs": { "description": "Disallow string concatenation with `__dirname` and `__filename`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-path-concat" + "url": "https://eslint.org/docs/latest/rules/no-path-concat" } }, "no-plusplus": { @@ -1421,7 +1448,7 @@ "docs": { "description": "Disallow the unary operators `++` and `--`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-plusplus" + "url": "https://eslint.org/docs/latest/rules/no-plusplus" } }, "no-process-env": { @@ -1431,7 +1458,7 @@ "docs": { "description": "Disallow the use of `process.env`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-process-env" + "url": "https://eslint.org/docs/latest/rules/no-process-env" } }, "no-process-exit": { @@ -1441,7 +1468,7 @@ "docs": { "description": "Disallow the use of `process.exit()`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-process-exit" + "url": "https://eslint.org/docs/latest/rules/no-process-exit" } }, "no-promise-executor-return": { @@ -1449,7 +1476,7 @@ "docs": { "description": "Disallow returning values from Promise executor functions", "recommended": false, - "url": "https://eslint.org/docs/rules/no-promise-executor-return" + "url": "https://eslint.org/docs/latest/rules/no-promise-executor-return" } }, "no-proto": { @@ -1457,7 +1484,7 @@ "docs": { "description": "Disallow the use of the `__proto__` property", "recommended": false, - "url": "https://eslint.org/docs/rules/no-proto" + "url": "https://eslint.org/docs/latest/rules/no-proto" } }, "no-prototype-builtins": { @@ -1465,7 +1492,7 @@ "docs": { "description": "Disallow calling some `Object.prototype` methods directly on objects", "recommended": true, - "url": "https://eslint.org/docs/rules/no-prototype-builtins" + "url": "https://eslint.org/docs/latest/rules/no-prototype-builtins" } }, "no-redeclare": { @@ -1473,7 +1500,7 @@ "docs": { "description": "Disallow variable redeclaration", "recommended": true, - "url": "https://eslint.org/docs/rules/no-redeclare" + "url": "https://eslint.org/docs/latest/rules/no-redeclare" } }, "no-regex-spaces": { @@ -1481,7 +1508,7 @@ "docs": { "description": "Disallow multiple spaces in regular expressions", "recommended": true, - "url": "https://eslint.org/docs/rules/no-regex-spaces" + "url": "https://eslint.org/docs/latest/rules/no-regex-spaces" }, "fixable": "code" }, @@ -1490,7 +1517,7 @@ "docs": { "description": "Disallow specified names in exports", "recommended": false, - "url": "https://eslint.org/docs/rules/no-restricted-exports" + "url": "https://eslint.org/docs/latest/rules/no-restricted-exports" } }, "no-restricted-globals": { @@ -1498,7 +1525,7 @@ "docs": { "description": "Disallow specified global variables", "recommended": false, - "url": "https://eslint.org/docs/rules/no-restricted-globals" + "url": "https://eslint.org/docs/latest/rules/no-restricted-globals" } }, "no-restricted-imports": { @@ -1506,7 +1533,7 @@ "docs": { "description": "Disallow specified modules when loaded by `import`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-restricted-imports" + "url": "https://eslint.org/docs/latest/rules/no-restricted-imports" } }, "no-restricted-modules": { @@ -1516,7 +1543,7 @@ "docs": { "description": "Disallow specified modules when loaded by `require`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-restricted-modules" + "url": "https://eslint.org/docs/latest/rules/no-restricted-modules" } }, "no-restricted-properties": { @@ -1524,7 +1551,7 @@ "docs": { "description": "Disallow certain properties on certain objects", "recommended": false, - "url": "https://eslint.org/docs/rules/no-restricted-properties" + "url": "https://eslint.org/docs/latest/rules/no-restricted-properties" } }, "no-restricted-syntax": { @@ -1532,7 +1559,7 @@ "docs": { "description": "Disallow specified syntax", "recommended": false, - "url": "https://eslint.org/docs/rules/no-restricted-syntax" + "url": "https://eslint.org/docs/latest/rules/no-restricted-syntax" } }, "no-return-assign": { @@ -1540,15 +1567,16 @@ "docs": { "description": "Disallow assignment operators in `return` statements", "recommended": false, - "url": "https://eslint.org/docs/rules/no-return-assign" + "url": "https://eslint.org/docs/latest/rules/no-return-assign" } }, "no-return-await": { + "hasSuggestions": true, "type": "suggestion", "docs": { "description": "Disallow unnecessary `return await`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-return-await" + "url": "https://eslint.org/docs/latest/rules/no-return-await" }, "fixable": null }, @@ -1557,7 +1585,7 @@ "docs": { "description": "Disallow `javascript:` urls", "recommended": false, - "url": "https://eslint.org/docs/rules/no-script-url" + "url": "https://eslint.org/docs/latest/rules/no-script-url" } }, "no-self-assign": { @@ -1565,7 +1593,7 @@ "docs": { "description": "Disallow assignments where both sides are exactly the same", "recommended": true, - "url": "https://eslint.org/docs/rules/no-self-assign" + "url": "https://eslint.org/docs/latest/rules/no-self-assign" } }, "no-self-compare": { @@ -1573,7 +1601,7 @@ "docs": { "description": "Disallow comparisons where both sides are exactly the same", "recommended": false, - "url": "https://eslint.org/docs/rules/no-self-compare" + "url": "https://eslint.org/docs/latest/rules/no-self-compare" } }, "no-sequences": { @@ -1581,7 +1609,7 @@ "docs": { "description": "Disallow comma operators", "recommended": false, - "url": "https://eslint.org/docs/rules/no-sequences" + "url": "https://eslint.org/docs/latest/rules/no-sequences" } }, "no-setter-return": { @@ -1589,7 +1617,7 @@ "docs": { "description": "Disallow returning values from setters", "recommended": true, - "url": "https://eslint.org/docs/rules/no-setter-return" + "url": "https://eslint.org/docs/latest/rules/no-setter-return" } }, "no-shadow": { @@ -1597,7 +1625,7 @@ "docs": { "description": "Disallow variable declarations from shadowing variables declared in the outer scope", "recommended": false, - "url": "https://eslint.org/docs/rules/no-shadow" + "url": "https://eslint.org/docs/latest/rules/no-shadow" } }, "no-shadow-restricted-names": { @@ -1605,7 +1633,7 @@ "docs": { "description": "Disallow identifiers from shadowing restricted names", "recommended": true, - "url": "https://eslint.org/docs/rules/no-shadow-restricted-names" + "url": "https://eslint.org/docs/latest/rules/no-shadow-restricted-names" } }, "no-spaced-func": { @@ -1613,7 +1641,7 @@ "docs": { "description": "Disallow spacing between function identifiers and their applications (deprecated)", "recommended": false, - "url": "https://eslint.org/docs/rules/no-spaced-func" + "url": "https://eslint.org/docs/latest/rules/no-spaced-func" }, "deprecated": true, "replacedBy": [ @@ -1626,7 +1654,7 @@ "docs": { "description": "Disallow sparse arrays", "recommended": true, - "url": "https://eslint.org/docs/rules/no-sparse-arrays" + "url": "https://eslint.org/docs/latest/rules/no-sparse-arrays" } }, "no-sync": { @@ -1636,7 +1664,7 @@ "docs": { "description": "Disallow synchronous methods", "recommended": false, - "url": "https://eslint.org/docs/rules/no-sync" + "url": "https://eslint.org/docs/latest/rules/no-sync" } }, "no-tabs": { @@ -1644,7 +1672,7 @@ "docs": { "description": "Disallow all tabs", "recommended": false, - "url": "https://eslint.org/docs/rules/no-tabs" + "url": "https://eslint.org/docs/latest/rules/no-tabs" } }, "no-template-curly-in-string": { @@ -1652,7 +1680,7 @@ "docs": { "description": "Disallow template literal placeholder syntax in regular strings", "recommended": false, - "url": "https://eslint.org/docs/rules/no-template-curly-in-string" + "url": "https://eslint.org/docs/latest/rules/no-template-curly-in-string" } }, "no-ternary": { @@ -1660,7 +1688,7 @@ "docs": { "description": "Disallow ternary operators", "recommended": false, - "url": "https://eslint.org/docs/rules/no-ternary" + "url": "https://eslint.org/docs/latest/rules/no-ternary" } }, "no-this-before-super": { @@ -1668,7 +1696,7 @@ "docs": { "description": "Disallow `this`/`super` before calling `super()` in constructors", "recommended": true, - "url": "https://eslint.org/docs/rules/no-this-before-super" + "url": "https://eslint.org/docs/latest/rules/no-this-before-super" } }, "no-throw-literal": { @@ -1676,7 +1704,7 @@ "docs": { "description": "Disallow throwing literals as exceptions", "recommended": false, - "url": "https://eslint.org/docs/rules/no-throw-literal" + "url": "https://eslint.org/docs/latest/rules/no-throw-literal" } }, "no-trailing-spaces": { @@ -1684,7 +1712,7 @@ "docs": { "description": "Disallow trailing whitespace at the end of lines", "recommended": false, - "url": "https://eslint.org/docs/rules/no-trailing-spaces" + "url": "https://eslint.org/docs/latest/rules/no-trailing-spaces" }, "fixable": "whitespace" }, @@ -1693,7 +1721,7 @@ "docs": { "description": "Disallow the use of undeclared variables unless mentioned in `/*global */` comments", "recommended": true, - "url": "https://eslint.org/docs/rules/no-undef" + "url": "https://eslint.org/docs/latest/rules/no-undef" } }, "no-undef-init": { @@ -1701,7 +1729,7 @@ "docs": { "description": "Disallow initializing variables to `undefined`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-undef-init" + "url": "https://eslint.org/docs/latest/rules/no-undef-init" }, "fixable": "code" }, @@ -1710,7 +1738,7 @@ "docs": { "description": "Disallow the use of `undefined` as an identifier", "recommended": false, - "url": "https://eslint.org/docs/rules/no-undefined" + "url": "https://eslint.org/docs/latest/rules/no-undefined" } }, "no-underscore-dangle": { @@ -1718,7 +1746,7 @@ "docs": { "description": "Disallow dangling underscores in identifiers", "recommended": false, - "url": "https://eslint.org/docs/rules/no-underscore-dangle" + "url": "https://eslint.org/docs/latest/rules/no-underscore-dangle" } }, "no-unexpected-multiline": { @@ -1726,7 +1754,7 @@ "docs": { "description": "Disallow confusing multiline expressions", "recommended": true, - "url": "https://eslint.org/docs/rules/no-unexpected-multiline" + "url": "https://eslint.org/docs/latest/rules/no-unexpected-multiline" } }, "no-unmodified-loop-condition": { @@ -1734,7 +1762,7 @@ "docs": { "description": "Disallow unmodified loop conditions", "recommended": false, - "url": "https://eslint.org/docs/rules/no-unmodified-loop-condition" + "url": "https://eslint.org/docs/latest/rules/no-unmodified-loop-condition" } }, "no-unneeded-ternary": { @@ -1742,7 +1770,7 @@ "docs": { "description": "Disallow ternary operators when simpler alternatives exist", "recommended": false, - "url": "https://eslint.org/docs/rules/no-unneeded-ternary" + "url": "https://eslint.org/docs/latest/rules/no-unneeded-ternary" }, "fixable": "code" }, @@ -1751,7 +1779,7 @@ "docs": { "description": "Disallow unreachable code after `return`, `throw`, `continue`, and `break` statements", "recommended": true, - "url": "https://eslint.org/docs/rules/no-unreachable" + "url": "https://eslint.org/docs/latest/rules/no-unreachable" } }, "no-unreachable-loop": { @@ -1759,7 +1787,7 @@ "docs": { "description": "Disallow loops with a body that allows only one iteration", "recommended": false, - "url": "https://eslint.org/docs/rules/no-unreachable-loop" + "url": "https://eslint.org/docs/latest/rules/no-unreachable-loop" } }, "no-unsafe-finally": { @@ -1767,7 +1795,7 @@ "docs": { "description": "Disallow control flow statements in `finally` blocks", "recommended": true, - "url": "https://eslint.org/docs/rules/no-unsafe-finally" + "url": "https://eslint.org/docs/latest/rules/no-unsafe-finally" } }, "no-unsafe-negation": { @@ -1775,7 +1803,7 @@ "docs": { "description": "Disallow negating the left operand of relational operators", "recommended": true, - "url": "https://eslint.org/docs/rules/no-unsafe-negation" + "url": "https://eslint.org/docs/latest/rules/no-unsafe-negation" }, "hasSuggestions": true, "fixable": null @@ -1785,7 +1813,7 @@ "docs": { "description": "Disallow use of optional chaining in contexts where the `undefined` value is not allowed", "recommended": true, - "url": "https://eslint.org/docs/rules/no-unsafe-optional-chaining" + "url": "https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining" }, "fixable": null }, @@ -1794,7 +1822,7 @@ "docs": { "description": "Disallow unused expressions", "recommended": false, - "url": "https://eslint.org/docs/rules/no-unused-expressions" + "url": "https://eslint.org/docs/latest/rules/no-unused-expressions" } }, "no-unused-labels": { @@ -1802,7 +1830,7 @@ "docs": { "description": "Disallow unused labels", "recommended": true, - "url": "https://eslint.org/docs/rules/no-unused-labels" + "url": "https://eslint.org/docs/latest/rules/no-unused-labels" }, "fixable": "code" }, @@ -1811,7 +1839,7 @@ "docs": { "description": "Disallow unused private class members", "recommended": false, - "url": "https://eslint.org/docs/rules/no-unused-private-class-members" + "url": "https://eslint.org/docs/latest/rules/no-unused-private-class-members" } }, "no-unused-vars": { @@ -1819,7 +1847,7 @@ "docs": { "description": "Disallow unused variables", "recommended": true, - "url": "https://eslint.org/docs/rules/no-unused-vars" + "url": "https://eslint.org/docs/latest/rules/no-unused-vars" } }, "no-use-before-define": { @@ -1827,7 +1855,7 @@ "docs": { "description": "Disallow the use of variables before they are defined", "recommended": false, - "url": "https://eslint.org/docs/rules/no-use-before-define" + "url": "https://eslint.org/docs/latest/rules/no-use-before-define" } }, "no-useless-backreference": { @@ -1835,7 +1863,7 @@ "docs": { "description": "Disallow useless backreferences in regular expressions", "recommended": true, - "url": "https://eslint.org/docs/rules/no-useless-backreference" + "url": "https://eslint.org/docs/latest/rules/no-useless-backreference" } }, "no-useless-call": { @@ -1843,7 +1871,7 @@ "docs": { "description": "Disallow unnecessary calls to `.call()` and `.apply()`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-useless-call" + "url": "https://eslint.org/docs/latest/rules/no-useless-call" } }, "no-useless-catch": { @@ -1851,7 +1879,7 @@ "docs": { "description": "Disallow unnecessary `catch` clauses", "recommended": true, - "url": "https://eslint.org/docs/rules/no-useless-catch" + "url": "https://eslint.org/docs/latest/rules/no-useless-catch" } }, "no-useless-computed-key": { @@ -1859,7 +1887,7 @@ "docs": { "description": "Disallow unnecessary computed property keys in objects and classes", "recommended": false, - "url": "https://eslint.org/docs/rules/no-useless-computed-key" + "url": "https://eslint.org/docs/latest/rules/no-useless-computed-key" }, "fixable": "code" }, @@ -1868,7 +1896,7 @@ "docs": { "description": "Disallow unnecessary concatenation of literals or template literals", "recommended": false, - "url": "https://eslint.org/docs/rules/no-useless-concat" + "url": "https://eslint.org/docs/latest/rules/no-useless-concat" } }, "no-useless-constructor": { @@ -1876,7 +1904,7 @@ "docs": { "description": "Disallow unnecessary constructors", "recommended": false, - "url": "https://eslint.org/docs/rules/no-useless-constructor" + "url": "https://eslint.org/docs/latest/rules/no-useless-constructor" } }, "no-useless-escape": { @@ -1884,7 +1912,7 @@ "docs": { "description": "Disallow unnecessary escape characters", "recommended": true, - "url": "https://eslint.org/docs/rules/no-useless-escape" + "url": "https://eslint.org/docs/latest/rules/no-useless-escape" }, "hasSuggestions": true }, @@ -1893,7 +1921,7 @@ "docs": { "description": "Disallow renaming import, export, and destructured assignments to the same name", "recommended": false, - "url": "https://eslint.org/docs/rules/no-useless-rename" + "url": "https://eslint.org/docs/latest/rules/no-useless-rename" }, "fixable": "code" }, @@ -1902,7 +1930,7 @@ "docs": { "description": "Disallow redundant return statements", "recommended": false, - "url": "https://eslint.org/docs/rules/no-useless-return" + "url": "https://eslint.org/docs/latest/rules/no-useless-return" }, "fixable": "code" }, @@ -1911,7 +1939,7 @@ "docs": { "description": "Require `let` or `const` instead of `var`", "recommended": false, - "url": "https://eslint.org/docs/rules/no-var" + "url": "https://eslint.org/docs/latest/rules/no-var" }, "fixable": "code" }, @@ -1920,7 +1948,7 @@ "docs": { "description": "Disallow `void` operators", "recommended": false, - "url": "https://eslint.org/docs/rules/no-void" + "url": "https://eslint.org/docs/latest/rules/no-void" } }, "no-warning-comments": { @@ -1928,7 +1956,7 @@ "docs": { "description": "Disallow specified warning terms in comments", "recommended": false, - "url": "https://eslint.org/docs/rules/no-warning-comments" + "url": "https://eslint.org/docs/latest/rules/no-warning-comments" } }, "no-whitespace-before-property": { @@ -1936,7 +1964,7 @@ "docs": { "description": "Disallow whitespace before properties", "recommended": false, - "url": "https://eslint.org/docs/rules/no-whitespace-before-property" + "url": "https://eslint.org/docs/latest/rules/no-whitespace-before-property" }, "fixable": "whitespace" }, @@ -1945,7 +1973,7 @@ "docs": { "description": "Disallow `with` statements", "recommended": true, - "url": "https://eslint.org/docs/rules/no-with" + "url": "https://eslint.org/docs/latest/rules/no-with" } }, "nonblock-statement-body-position": { @@ -1953,7 +1981,7 @@ "docs": { "description": "Enforce the location of single-line statements", "recommended": false, - "url": "https://eslint.org/docs/rules/nonblock-statement-body-position" + "url": "https://eslint.org/docs/latest/rules/nonblock-statement-body-position" }, "fixable": "whitespace" }, @@ -1962,7 +1990,7 @@ "docs": { "description": "Enforce consistent line breaks after opening and before closing braces", "recommended": false, - "url": "https://eslint.org/docs/rules/object-curly-newline" + "url": "https://eslint.org/docs/latest/rules/object-curly-newline" }, "fixable": "whitespace" }, @@ -1971,7 +1999,7 @@ "docs": { "description": "Enforce consistent spacing inside braces", "recommended": false, - "url": "https://eslint.org/docs/rules/object-curly-spacing" + "url": "https://eslint.org/docs/latest/rules/object-curly-spacing" }, "fixable": "whitespace" }, @@ -1980,7 +2008,7 @@ "docs": { "description": "Enforce placing object properties on separate lines", "recommended": false, - "url": "https://eslint.org/docs/rules/object-property-newline" + "url": "https://eslint.org/docs/latest/rules/object-property-newline" }, "fixable": "whitespace" }, @@ -1989,7 +2017,7 @@ "docs": { "description": "Require or disallow method and property shorthand syntax for object literals", "recommended": false, - "url": "https://eslint.org/docs/rules/object-shorthand" + "url": "https://eslint.org/docs/latest/rules/object-shorthand" }, "fixable": "code" }, @@ -1998,7 +2026,7 @@ "docs": { "description": "Enforce variables to be declared either together or separately in functions", "recommended": false, - "url": "https://eslint.org/docs/rules/one-var" + "url": "https://eslint.org/docs/latest/rules/one-var" }, "fixable": "code" }, @@ -2007,7 +2035,7 @@ "docs": { "description": "Require or disallow newlines around variable declarations", "recommended": false, - "url": "https://eslint.org/docs/rules/one-var-declaration-per-line" + "url": "https://eslint.org/docs/latest/rules/one-var-declaration-per-line" }, "fixable": "whitespace" }, @@ -2016,7 +2044,7 @@ "docs": { "description": "Require or disallow assignment operator shorthand where possible", "recommended": false, - "url": "https://eslint.org/docs/rules/operator-assignment" + "url": "https://eslint.org/docs/latest/rules/operator-assignment" }, "fixable": "code" }, @@ -2025,7 +2053,7 @@ "docs": { "description": "Enforce consistent linebreak style for operators", "recommended": false, - "url": "https://eslint.org/docs/rules/operator-linebreak" + "url": "https://eslint.org/docs/latest/rules/operator-linebreak" }, "fixable": "code" }, @@ -2034,7 +2062,7 @@ "docs": { "description": "Require or disallow padding within blocks", "recommended": false, - "url": "https://eslint.org/docs/rules/padded-blocks" + "url": "https://eslint.org/docs/latest/rules/padded-blocks" }, "fixable": "whitespace" }, @@ -2043,7 +2071,7 @@ "docs": { "description": "Require or disallow padding lines between statements", "recommended": false, - "url": "https://eslint.org/docs/rules/padding-line-between-statements" + "url": "https://eslint.org/docs/latest/rules/padding-line-between-statements" }, "fixable": "whitespace" }, @@ -2052,7 +2080,7 @@ "docs": { "description": "Require using arrow functions for callbacks", "recommended": false, - "url": "https://eslint.org/docs/rules/prefer-arrow-callback" + "url": "https://eslint.org/docs/latest/rules/prefer-arrow-callback" }, "fixable": "code" }, @@ -2061,7 +2089,7 @@ "docs": { "description": "Require `const` declarations for variables that are never reassigned after declared", "recommended": false, - "url": "https://eslint.org/docs/rules/prefer-const" + "url": "https://eslint.org/docs/latest/rules/prefer-const" }, "fixable": "code" }, @@ -2070,7 +2098,7 @@ "docs": { "description": "Require destructuring from arrays and/or objects", "recommended": false, - "url": "https://eslint.org/docs/rules/prefer-destructuring" + "url": "https://eslint.org/docs/latest/rules/prefer-destructuring" }, "fixable": "code" }, @@ -2079,7 +2107,7 @@ "docs": { "description": "Disallow the use of `Math.pow` in favor of the `**` operator", "recommended": false, - "url": "https://eslint.org/docs/rules/prefer-exponentiation-operator" + "url": "https://eslint.org/docs/latest/rules/prefer-exponentiation-operator" }, "fixable": "code" }, @@ -2088,15 +2116,16 @@ "docs": { "description": "Enforce using named capture group in regular expression", "recommended": false, - "url": "https://eslint.org/docs/rules/prefer-named-capture-group" - } + "url": "https://eslint.org/docs/latest/rules/prefer-named-capture-group" + }, + "hasSuggestions": true }, "prefer-numeric-literals": { "type": "suggestion", "docs": { "description": "Disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals", "recommended": false, - "url": "https://eslint.org/docs/rules/prefer-numeric-literals" + "url": "https://eslint.org/docs/latest/rules/prefer-numeric-literals" }, "fixable": "code" }, @@ -2105,16 +2134,16 @@ "docs": { "description": "Disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`", "recommended": false, - "url": "https://eslint.org/docs/rules/prefer-object-has-own" + "url": "https://eslint.org/docs/latest/rules/prefer-object-has-own" }, "fixable": "code" }, "prefer-object-spread": { "type": "suggestion", "docs": { - "description": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead.", + "description": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead", "recommended": false, - "url": "https://eslint.org/docs/rules/prefer-object-spread" + "url": "https://eslint.org/docs/latest/rules/prefer-object-spread" }, "fixable": "code" }, @@ -2123,7 +2152,7 @@ "docs": { "description": "Require using Error objects as Promise rejection reasons", "recommended": false, - "url": "https://eslint.org/docs/rules/prefer-promise-reject-errors" + "url": "https://eslint.org/docs/latest/rules/prefer-promise-reject-errors" }, "fixable": null }, @@ -2132,7 +2161,7 @@ "docs": { "description": "Require `Reflect` methods where applicable", "recommended": false, - "url": "https://eslint.org/docs/rules/prefer-reflect" + "url": "https://eslint.org/docs/latest/rules/prefer-reflect" }, "deprecated": true, "replacedBy": [] @@ -2142,7 +2171,7 @@ "docs": { "description": "Disallow use of the `RegExp` constructor in favor of regular expression literals", "recommended": false, - "url": "https://eslint.org/docs/rules/prefer-regex-literals" + "url": "https://eslint.org/docs/latest/rules/prefer-regex-literals" }, "hasSuggestions": true }, @@ -2151,7 +2180,7 @@ "docs": { "description": "Require rest parameters instead of `arguments`", "recommended": false, - "url": "https://eslint.org/docs/rules/prefer-rest-params" + "url": "https://eslint.org/docs/latest/rules/prefer-rest-params" } }, "prefer-spread": { @@ -2159,7 +2188,7 @@ "docs": { "description": "Require spread operators instead of `.apply()`", "recommended": false, - "url": "https://eslint.org/docs/rules/prefer-spread" + "url": "https://eslint.org/docs/latest/rules/prefer-spread" }, "fixable": null }, @@ -2168,7 +2197,7 @@ "docs": { "description": "Require template literals instead of string concatenation", "recommended": false, - "url": "https://eslint.org/docs/rules/prefer-template" + "url": "https://eslint.org/docs/latest/rules/prefer-template" }, "fixable": "code" }, @@ -2177,7 +2206,7 @@ "docs": { "description": "Require quotes around object literal property names", "recommended": false, - "url": "https://eslint.org/docs/rules/quote-props" + "url": "https://eslint.org/docs/latest/rules/quote-props" }, "fixable": "code" }, @@ -2186,7 +2215,7 @@ "docs": { "description": "Enforce the consistent use of either backticks, double, or single quotes", "recommended": false, - "url": "https://eslint.org/docs/rules/quotes" + "url": "https://eslint.org/docs/latest/rules/quotes" }, "fixable": "code" }, @@ -2195,7 +2224,7 @@ "docs": { "description": "Enforce the consistent use of the radix argument when using `parseInt()`", "recommended": false, - "url": "https://eslint.org/docs/rules/radix" + "url": "https://eslint.org/docs/latest/rules/radix" }, "hasSuggestions": true }, @@ -2204,7 +2233,7 @@ "docs": { "description": "Disallow assignments that can lead to race conditions due to usage of `await` or `yield`", "recommended": false, - "url": "https://eslint.org/docs/rules/require-atomic-updates" + "url": "https://eslint.org/docs/latest/rules/require-atomic-updates" }, "fixable": null }, @@ -2213,7 +2242,7 @@ "docs": { "description": "Disallow async functions which have no `await` expression", "recommended": false, - "url": "https://eslint.org/docs/rules/require-await" + "url": "https://eslint.org/docs/latest/rules/require-await" } }, "require-jsdoc": { @@ -2221,7 +2250,7 @@ "docs": { "description": "Require JSDoc comments", "recommended": false, - "url": "https://eslint.org/docs/rules/require-jsdoc" + "url": "https://eslint.org/docs/latest/rules/require-jsdoc" }, "deprecated": true, "replacedBy": [] @@ -2231,15 +2260,16 @@ "docs": { "description": "Enforce the use of `u` flag on RegExp", "recommended": false, - "url": "https://eslint.org/docs/rules/require-unicode-regexp" - } + "url": "https://eslint.org/docs/latest/rules/require-unicode-regexp" + }, + "hasSuggestions": true }, "require-yield": { "type": "suggestion", "docs": { "description": "Require generator functions to contain `yield`", "recommended": true, - "url": "https://eslint.org/docs/rules/require-yield" + "url": "https://eslint.org/docs/latest/rules/require-yield" } }, "rest-spread-spacing": { @@ -2247,7 +2277,7 @@ "docs": { "description": "Enforce spacing between rest and spread operators and their expressions", "recommended": false, - "url": "https://eslint.org/docs/rules/rest-spread-spacing" + "url": "https://eslint.org/docs/latest/rules/rest-spread-spacing" }, "fixable": "whitespace" }, @@ -2256,7 +2286,7 @@ "docs": { "description": "Require or disallow semicolons instead of ASI", "recommended": false, - "url": "https://eslint.org/docs/rules/semi" + "url": "https://eslint.org/docs/latest/rules/semi" }, "fixable": "code" }, @@ -2265,7 +2295,7 @@ "docs": { "description": "Enforce consistent spacing before and after semicolons", "recommended": false, - "url": "https://eslint.org/docs/rules/semi-spacing" + "url": "https://eslint.org/docs/latest/rules/semi-spacing" }, "fixable": "whitespace" }, @@ -2274,7 +2304,7 @@ "docs": { "description": "Enforce location of semicolons", "recommended": false, - "url": "https://eslint.org/docs/rules/semi-style" + "url": "https://eslint.org/docs/latest/rules/semi-style" }, "fixable": "whitespace" }, @@ -2283,7 +2313,7 @@ "docs": { "description": "Enforce sorted import declarations within modules", "recommended": false, - "url": "https://eslint.org/docs/rules/sort-imports" + "url": "https://eslint.org/docs/latest/rules/sort-imports" }, "fixable": "code" }, @@ -2292,7 +2322,7 @@ "docs": { "description": "Require object keys to be sorted", "recommended": false, - "url": "https://eslint.org/docs/rules/sort-keys" + "url": "https://eslint.org/docs/latest/rules/sort-keys" } }, "sort-vars": { @@ -2300,7 +2330,7 @@ "docs": { "description": "Require variables within the same declaration block to be sorted", "recommended": false, - "url": "https://eslint.org/docs/rules/sort-vars" + "url": "https://eslint.org/docs/latest/rules/sort-vars" }, "fixable": "code" }, @@ -2309,7 +2339,7 @@ "docs": { "description": "Enforce consistent spacing before blocks", "recommended": false, - "url": "https://eslint.org/docs/rules/space-before-blocks" + "url": "https://eslint.org/docs/latest/rules/space-before-blocks" }, "fixable": "whitespace" }, @@ -2318,7 +2348,7 @@ "docs": { "description": "Enforce consistent spacing before `function` definition opening parenthesis", "recommended": false, - "url": "https://eslint.org/docs/rules/space-before-function-paren" + "url": "https://eslint.org/docs/latest/rules/space-before-function-paren" }, "fixable": "whitespace" }, @@ -2327,7 +2357,7 @@ "docs": { "description": "Enforce consistent spacing inside parentheses", "recommended": false, - "url": "https://eslint.org/docs/rules/space-in-parens" + "url": "https://eslint.org/docs/latest/rules/space-in-parens" }, "fixable": "whitespace" }, @@ -2336,7 +2366,7 @@ "docs": { "description": "Require spacing around infix operators", "recommended": false, - "url": "https://eslint.org/docs/rules/space-infix-ops" + "url": "https://eslint.org/docs/latest/rules/space-infix-ops" }, "fixable": "whitespace" }, @@ -2345,7 +2375,7 @@ "docs": { "description": "Enforce consistent spacing before or after unary operators", "recommended": false, - "url": "https://eslint.org/docs/rules/space-unary-ops" + "url": "https://eslint.org/docs/latest/rules/space-unary-ops" }, "fixable": "whitespace" }, @@ -2354,7 +2384,7 @@ "docs": { "description": "Enforce consistent spacing after the `//` or `/*` in a comment", "recommended": false, - "url": "https://eslint.org/docs/rules/spaced-comment" + "url": "https://eslint.org/docs/latest/rules/spaced-comment" }, "fixable": "whitespace" }, @@ -2363,7 +2393,7 @@ "docs": { "description": "Require or disallow strict mode directives", "recommended": false, - "url": "https://eslint.org/docs/rules/strict" + "url": "https://eslint.org/docs/latest/rules/strict" }, "fixable": "code" }, @@ -2372,7 +2402,7 @@ "docs": { "description": "Enforce spacing around colons of switch statements", "recommended": false, - "url": "https://eslint.org/docs/rules/switch-colon-spacing" + "url": "https://eslint.org/docs/latest/rules/switch-colon-spacing" }, "fixable": "whitespace" }, @@ -2381,7 +2411,7 @@ "docs": { "description": "Require symbol descriptions", "recommended": false, - "url": "https://eslint.org/docs/rules/symbol-description" + "url": "https://eslint.org/docs/latest/rules/symbol-description" }, "fixable": null }, @@ -2390,7 +2420,7 @@ "docs": { "description": "Require or disallow spacing around embedded expressions of template strings", "recommended": false, - "url": "https://eslint.org/docs/rules/template-curly-spacing" + "url": "https://eslint.org/docs/latest/rules/template-curly-spacing" }, "fixable": "whitespace" }, @@ -2399,7 +2429,7 @@ "docs": { "description": "Require or disallow spacing between template tags and their literals", "recommended": false, - "url": "https://eslint.org/docs/rules/template-tag-spacing" + "url": "https://eslint.org/docs/latest/rules/template-tag-spacing" }, "fixable": "whitespace" }, @@ -2408,7 +2438,7 @@ "docs": { "description": "Require or disallow Unicode byte order mark (BOM)", "recommended": false, - "url": "https://eslint.org/docs/rules/unicode-bom" + "url": "https://eslint.org/docs/latest/rules/unicode-bom" }, "fixable": "whitespace" }, @@ -2417,7 +2447,7 @@ "docs": { "description": "Require calls to `isNaN()` when checking for `NaN`", "recommended": true, - "url": "https://eslint.org/docs/rules/use-isnan" + "url": "https://eslint.org/docs/latest/rules/use-isnan" } }, "valid-jsdoc": { @@ -2425,7 +2455,7 @@ "docs": { "description": "Enforce valid JSDoc comments", "recommended": false, - "url": "https://eslint.org/docs/rules/valid-jsdoc" + "url": "https://eslint.org/docs/latest/rules/valid-jsdoc" }, "fixable": "code", "deprecated": true, @@ -2436,7 +2466,7 @@ "docs": { "description": "Enforce comparing `typeof` expressions against valid strings", "recommended": true, - "url": "https://eslint.org/docs/rules/valid-typeof" + "url": "https://eslint.org/docs/latest/rules/valid-typeof" }, "hasSuggestions": true }, @@ -2445,7 +2475,7 @@ "docs": { "description": "Require `var` declarations be placed at the top of their containing scope", "recommended": false, - "url": "https://eslint.org/docs/rules/vars-on-top" + "url": "https://eslint.org/docs/latest/rules/vars-on-top" } }, "wrap-iife": { @@ -2453,7 +2483,7 @@ "docs": { "description": "Require parentheses around immediate `function` invocations", "recommended": false, - "url": "https://eslint.org/docs/rules/wrap-iife" + "url": "https://eslint.org/docs/latest/rules/wrap-iife" }, "fixable": "code" }, @@ -2462,7 +2492,7 @@ "docs": { "description": "Require parenthesis around regex literals", "recommended": false, - "url": "https://eslint.org/docs/rules/wrap-regex" + "url": "https://eslint.org/docs/latest/rules/wrap-regex" }, "fixable": "code" }, @@ -2471,7 +2501,7 @@ "docs": { "description": "Require or disallow spacing around the `*` in `yield*` expressions", "recommended": false, - "url": "https://eslint.org/docs/rules/yield-star-spacing" + "url": "https://eslint.org/docs/latest/rules/yield-star-spacing" }, "fixable": "whitespace" }, @@ -2480,7 +2510,7 @@ "docs": { "description": "Require or disallow \"Yoda\" conditions", "recommended": false, - "url": "https://eslint.org/docs/rules/yoda" + "url": "https://eslint.org/docs/latest/rules/yoda" }, "fixable": "code" } diff --git a/eslint/docs/src/_data/sites/en.yml b/eslint/docs/src/_data/sites/en.yml index 43e112f..ccd87ad 100644 --- a/eslint/docs/src/_data/sites/en.yml +++ b/eslint/docs/src/_data/sites/en.yml @@ -74,8 +74,8 @@ footer: title: Social Media twitter: Twitter chat: Discord - mailing_list: Google Group github: GitHub + mastodon: Mastodon theme_switcher: title: Theme Switcher light: Light @@ -85,8 +85,9 @@ footer: description: Selecting a language will take you to the ESLint website in that language. change_language: Change Language language: Language + latest: Latest copyright: > - © OpenJS Foundation and ESLint contributors, www.openjsf.org + © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. links: open_jsf: The OpenJS Foundation terms: Terms of Use diff --git a/eslint/docs/src/_data/sites/zh-hans.yml b/eslint/docs/src/_data/sites/zh-hans.yml new file mode 100644 index 0000000..efa9474 --- /dev/null +++ b/eslint/docs/src/_data/sites/zh-hans.yml @@ -0,0 +1,116 @@ +#------------------------------------------------------------------------------ +# Simplified Chinese Site Details +# The documentation site that is hosted at zh-hans.eslint.org/docs +# Author: Percy Ma +#------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------ +# Global Settings +#------------------------------------------------------------------------------ + +language: + code: zh-hans + flag: 🇨🇳 + name: 简体中文 +locale: zh-hans +hostname: zh-hans.eslint.org + +#------------------------------------------------------------------------------ +# Analytics +#------------------------------------------------------------------------------ + +google_analytics: + code: "G-6ELXTK7GZR" + +#------------------------------------------------------------------------------ +# Ads +#------------------------------------------------------------------------------ + +carbon_ads: + serve: "" + placement: "" + +#------------------------------------------------------------------------------ +# Shared +#------------------------------------------------------------------------------ + +shared: + get_started: 开始 + become_a_sponsor: 捐赠 + eslint_logo_alt: ESLint 图标 + description: > + 插件化、可配置的 JavaScript 代码检查工具,让你轻松地提高代码质量。 + title_format: PAGE_TITLE - ESLint - 插件化的 JavaScript 代码检查工具 + skip_to_content: 跳转到正文 + donate: 捐赠 + +#------------------------------------------------------------------------------ +# Navigation +#------------------------------------------------------------------------------ + +navigation: +- text: 团队 + link: team +- text: 博客 + link: blog +- text: 文档 + link: docs +- text: 商店 + link: store + target: _blank +- text: 演练场 + link: playground + +#------------------------------------------------------------------------------ +# Footer +#------------------------------------------------------------------------------ + +footer: + title: 准备修复你的 JavaScript 代码了吗? + description: 从 npm 安装或捐赠开始。 + secondary: 次要 + social_icons: + title: 社交媒体 + twitter: Twitter + chat: Discord + github: GitHub + theme_switcher: + title: 主题切换 + light: 浅色 + dark: 深色 + language_switcher: + title: 语言切换 + description: 切换到你所选择语言版本对应的 ESLint 网站。 + change_language: 更改语言 + language: 语言 + latest: 最新 + copyright: > + © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + links: + open_jsf: OpenJS 基金会 + terms: 使用条款 + privacy: 隐私策略 + bylaws: OpenJS 基金会章程 + trademark: 商标策略 + trademark_list: 商标列表 + cookies: Cookie 策略 + +#------------------------------------------------------------------------------ +# 404 Page +#------------------------------------------------------------------------------ + +404_page: + title: 404 错误 + subtitle: 页面未找到 + description: 对不起,你正在寻找的页面不存在或已被移动。 + actions: + back_to_home: 回到主页 + browse_docs: 浏览文档 + +#------------------------------------------------------------------------------ +# Edit link +#------------------------------------------------------------------------------ + +edit_link: + start_with: https://github.com/eslint/eslint/edit/main/docs/ + text: 编辑此页 diff --git a/eslint/docs/src/_includes/components/language-switcher.html b/eslint/docs/src/_includes/components/language-switcher.html index aef1c2a..629b9dd 100644 --- a/eslint/docs/src/_includes/components/language-switcher.html +++ b/eslint/docs/src/_includes/components/language-switcher.html @@ -14,9 +14,13 @@ diff --git a/eslint/docs/src/_includes/components/logo.html b/eslint/docs/src/_includes/components/logo.html index 1b874fe..5e422b6 100644 --- a/eslint/docs/src/_includes/components/logo.html +++ b/eslint/docs/src/_includes/components/logo.html @@ -5,7 +5,7 @@ } [data-theme="dark"] #logo-center { - opacity: 60%; + opacity: 0.6; } Version - + + {% if config.showNextVersion == true %} + + {% endif %} {% for version in versions.items %}
+ + + diff --git a/eslint/docs/src/_includes/partials/versions-list.html b/eslint/docs/src/_includes/partials/versions-list.html index 2e2fb2f..7f07e4f 100644 --- a/eslint/docs/src/_includes/partials/versions-list.html +++ b/eslint/docs/src/_includes/partials/versions-list.html @@ -1,5 +1,8 @@
    -
  • HEAD
  • +
  • HEAD
  • + {% if config.showNextVersion == true %} +
  • NEXT
  • + {% endif %}
  • v{{ eslintVersion }}
  • {%- for version in versions.items -%}
  • v{{ version.number }}
  • diff --git a/eslint/docs/src/_plugins/md-syntax-highlighter.js b/eslint/docs/src/_plugins/md-syntax-highlighter.js new file mode 100644 index 0000000..9ae0dcd --- /dev/null +++ b/eslint/docs/src/_plugins/md-syntax-highlighter.js @@ -0,0 +1,91 @@ +/** + * MIT License + +Copyright (c) 2019-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +/** @typedef {import("markdown-it")} MarkdownIt */ + +const Prism = require("prismjs"); +const loadLanguages = require("prismjs/components/"); + +/** + * + * @param {MarkdownIt} md markdown-it + * @param {string} str code + * @param {string} lang code language + * @returns {string} highlighted result wrapped in pre + */ +const highlighter = function (md, str, lang) { + let result = ""; + if (lang) { + try { + loadLanguages([lang]); + result = Prism.highlight(str, Prism.languages[lang], lang); + } catch (err) { + console.log(lang, err); + // we still want to wrap the result later + result = md.utils.escapeHtml(str); + } + } else { + result = md.utils.escapeHtml(str); + } + + return `
    ${result}
    `; +}; + +/** + * + * modified from https://github.com/vuejs/vitepress/blob/main/src/node/markdown/plugins/lineNumbers.ts + * @param {MarkdownIt} md + * @license MIT License. See file header. + */ +const lineNumberPlugin = (md) => { + const fence = md.renderer.rules.fence; + md.renderer.rules.fence = (...args) => { + const [tokens, idx] = args; + const lang = tokens[idx].info.trim(); + const rawCode = fence(...args); + const code = rawCode.slice( + rawCode.indexOf(""), + rawCode.indexOf("") + ); + const lines = code.split("\n"); + const lineNumbersCode = [...Array(lines.length - 1)] + .map( + (line, index) => + `${index + 1}
    ` + ) + .join(""); + + const lineNumbersWrapperCode = ``; + + const finalCode = rawCode + .replace(/<\/pre>\n/, `${lineNumbersWrapperCode}`) + .replace(/"(language-\S*?)"/, '"$1 line-numbers-mode"') + .replace(//, ``) + + return finalCode; + }; +}; + +module.exports.highlighter = highlighter; +module.exports.lineNumberPlugin = lineNumberPlugin; diff --git a/eslint/docs/src/about/index.md b/eslint/docs/src/about/index.md index 2ced3af..0f59d9c 100644 --- a/eslint/docs/src/about/index.md +++ b/eslint/docs/src/about/index.md @@ -1,6 +1,5 @@ --- title: About -layout: doc --- diff --git a/eslint/docs/src/assets/js/main.js b/eslint/docs/src/assets/js/main.js index 28ec0e1..bf32682 100644 --- a/eslint/docs/src/assets/js/main.js +++ b/eslint/docs/src/assets/js/main.js @@ -1,3 +1,42 @@ +(function () { + // for sticky table of contents + const tocBody = document.querySelector(".docs-aside #js-toc-panel"); + const options = { + root: null, + rootMargin: `0px 0px -90% 0px`, + threshold: 1.0, + }; + const activeClassName = "active"; + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const activeAnchor = tocBody.querySelector( + `a.${activeClassName}` + ); + if (activeAnchor) { + activeAnchor.parentNode.classList.remove(activeClassName); + activeAnchor.classList.remove(activeClassName); + } + + const nextActiveAnchor = tocBody.querySelector( + `a[href="#${entry.target.id}"]` + ); + if (nextActiveAnchor) { + nextActiveAnchor.parentNode.classList.add(activeClassName); + nextActiveAnchor.classList.add(activeClassName); + } + } + }); + }, options); + if (window.matchMedia("(min-width: 1400px)").matches) { + document + .querySelectorAll( + "#main > div > h2[id], #main > div > h3[id], #main > div > h4[id]" // only h2, h3, h4 are shown in toc + ) + .forEach((el) => observer.observe(el)); + } +})(); + (function() { var toc_trigger = document.getElementById("js-toc-label"), toc = document.getElementById("js-toc-panel"), @@ -279,4 +318,4 @@ if (index) { document.addEventListener("DOMContentLoaded", () => { anchors.add(".docs-content h2:not(.c-toc__label), .docs-content h3, .docs-content h4"); -}); \ No newline at end of file +}); diff --git a/eslint/docs/src/assets/js/scroll-up-btn.js b/eslint/docs/src/assets/js/scroll-up-btn.js new file mode 100644 index 0000000..cb77af1 --- /dev/null +++ b/eslint/docs/src/assets/js/scroll-up-btn.js @@ -0,0 +1,13 @@ +(function () { + const scrollUpBtn = document.getElementById("scroll-up-btn"); + + if(window.innerWidth < 1400) { + window.addEventListener("scroll", function () { + if(document.body.scrollTop > 500 || document.documentElement.scrollTop > 500) { + scrollUpBtn.style.display = "flex"; + } else { + scrollUpBtn.style.display = "none"; + } + }); + } +})(); \ No newline at end of file diff --git a/eslint/docs/src/assets/js/search.js b/eslint/docs/src/assets/js/search.js index 29162c0..6d8eaa7 100644 --- a/eslint/docs/src/assets/js/search.js +++ b/eslint/docs/src/assets/js/search.js @@ -49,7 +49,6 @@ function clearSearchResults() { resultsElement.removeChild(resultsElement.firstChild); } resultsElement.innerHTML = ""; - searchClearBtn.setAttribute('hidden', ''); } /** @@ -80,13 +79,11 @@ function displaySearchResults(results) { `.trim(); list.append(listItem); } - searchClearBtn.removeAttribute('hidden'); } else { resultsLiveRegion.innerHTML = "No results found."; resultsElement.innerHTML = "No results found."; resultsElement.setAttribute('data-results', 'false'); - searchClearBtn.setAttribute('hidden', ''); } } @@ -111,63 +108,85 @@ function maintainScrollVisibility(activeElement, scrollParent) { else if (isBelow) { scrollParent.scrollTo(0, offsetTop - parentOffsetHeight + offsetHeight); } - + } +/** + * Debounces the provided callback with a given delay. + * @param {Function} callback The callback that needs to be debounced. + * @param {Number} delay Time in ms that the timer should wait before the callback is executed. + * @returns {Function} Returns the new debounced function. + */ +function debounce(callback, delay) { + let timer; + return (...args) => { + if (timer) clearTimeout(timer); + timer = setTimeout(() => callback.apply(this, args), delay); + } +} + +const debouncedFetchSearchResults = debounce((query) => { + fetchSearchResults(query) + .then(displaySearchResults) + .catch(clearSearchResults); +}, 300); //----------------------------------------------------------------------------- // Event Handlers //----------------------------------------------------------------------------- // listen for input changes -if(searchInput) +if (searchInput) searchInput.addEventListener('keyup', function (e) { const query = searchInput.value; - if(query === searchQuery) return; + if (query === searchQuery) return; - if(query.length) searchClearBtn.removeAttribute('hidden'); + if (query.length) searchClearBtn.removeAttribute('hidden'); else searchClearBtn.setAttribute('hidden', ''); if (query.length > 2) { - fetchSearchResults(query) - .then(displaySearchResults) - .catch(clearSearchResults); - document.addEventListener('click', function(e) { - if(e.target !== resultsElement) clearSearchResults(); + debouncedFetchSearchResults(query); + + document.addEventListener('click', function (e) { + if (e.target !== resultsElement) clearSearchResults(); }); } else { clearSearchResults(); } - searchQuery = query + searchQuery = query }); -if(searchClearBtn) - searchClearBtn.addEventListener('click', function(e) { +if (searchClearBtn) + searchClearBtn.addEventListener('click', function (e) { searchInput.value = ''; searchInput.focus(); clearSearchResults(); + searchClearBtn.setAttribute('hidden', ''); }); document.addEventListener('keydown', function (e) { + const searchResults = Array.from(document.querySelectorAll('.search-results__item')); + if (e.key === 'Escape') { e.preventDefault(); - clearSearchResults(); - searchInput.focus(); + if (searchResults.length) { + clearSearchResults(); + searchInput.focus(); + } } if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); searchInput.focus(); - document.querySelector('.search').scrollIntoView({ behaviour: "smooth", block: "start" }); + document.querySelector('.search').scrollIntoView({ behavior: "smooth", block: "start" }); } - const searchResults = Array.from(document.querySelectorAll('.search-results__item')); if (!searchResults.length) return; switch (e.key) { @@ -188,4 +207,3 @@ document.addEventListener('keydown', function (e) { maintainScrollVisibility(activeSearchResult, resultsElement); } }); - \ No newline at end of file diff --git a/eslint/docs/src/assets/scss/carbon-ads.scss b/eslint/docs/src/assets/scss/carbon-ads.scss index ccf578e..bd7ea8e 100644 --- a/eslint/docs/src/assets/scss/carbon-ads.scss +++ b/eslint/docs/src/assets/scss/carbon-ads.scss @@ -1,7 +1,7 @@ .hero-ad { - @media all and (max-width: 800px) { - display: none; - } + @media all and (max-width: 800px) { + display: none; + } } #carbonads * { @@ -15,9 +15,9 @@ padding: .6em; font-size: 1rem; overflow: hidden; - border-radius: 4px; background-color: var(--body-background-color); border: 1px solid var(--border-color); + border-radius: 4px; border-radius: var(--border-radius); box-shadow: 0 1px 4px 1px hsla(0, 0%, 0%, 0.1); @@ -31,8 +31,8 @@ } .jumbotron #carbonads { - border: solid 1px hsla(250, 20%, 50%, .6); - background-color: hsla(0, 0%, 70%, .15); + border: solid 1px hsla(250, 20%, 50%, 0.6); + background-color: hsla(0, 0%, 70%, 0.15); } #carbonads a { diff --git a/eslint/docs/src/assets/scss/components/alert.scss b/eslint/docs/src/assets/scss/components/alert.scss index ddd5c69..8235c14 100644 --- a/eslint/docs/src/assets/scss/components/alert.scss +++ b/eslint/docs/src/assets/scss/components/alert.scss @@ -16,6 +16,7 @@ color: var(--color-rose-600); [data-theme="dark"] & { + border: 1px solid var(--color-rose-300); color: var(--color-rose-300); background-color: var(--color-rose-900); } @@ -27,6 +28,7 @@ [data-theme="dark"] & { color: var(--color-warning-300); + border: 1px solid var(--color-warning-300); background-color: var(--color-warning-900); } } @@ -37,24 +39,8 @@ [data-theme="dark"] & { color: var(--color-success-300); - background-color: var(--color-success-900); - } - } -} - - -[data-theme="dark"] { - .alert { - &.alert--warning { - border: 1px solid var(--color-rose-300); - } - - &.alert--important { - border: 1px solid var(--color-warning-300); - } - - &.alert--tip { border: 1px solid var(--color-success-300); + background-color: var(--color-success-900); } } } @@ -101,7 +87,6 @@ } } - .alert__learn-more { display: block; font-weight: 500; diff --git a/eslint/docs/src/assets/scss/components/buttons.scss b/eslint/docs/src/assets/scss/components/buttons.scss index bbe6451..ca0aa72 100644 --- a/eslint/docs/src/assets/scss/components/buttons.scss +++ b/eslint/docs/src/assets/scss/components/buttons.scss @@ -23,9 +23,7 @@ button { align-items: center; justify-content: center; border-radius: var(--border-radius); - - transition: background-color .2s linear, - border-color .2s linear; + transition: background-color .2s linear, border-color .2s linear; svg { color: inherit; diff --git a/eslint/docs/src/assets/scss/components/docs-index.scss b/eslint/docs/src/assets/scss/components/docs-index.scss index b9f0452..22e156e 100644 --- a/eslint/docs/src/assets/scss/components/docs-index.scss +++ b/eslint/docs/src/assets/scss/components/docs-index.scss @@ -60,7 +60,6 @@ } .index-js [aria-expanded="true"] .index-icon { - -ms-transform: rotate(180deg); transform: rotate(180deg); } @@ -143,7 +142,6 @@ } #ham-top { - transform: rotate(41deg); } diff --git a/eslint/docs/src/assets/scss/components/docs-navigation.scss b/eslint/docs/src/assets/scss/components/docs-navigation.scss index 1a8ee50..f47fce3 100644 --- a/eslint/docs/src/assets/scss/components/docs-navigation.scss +++ b/eslint/docs/src/assets/scss/components/docs-navigation.scss @@ -13,7 +13,6 @@ margin-bottom: 2rem; margin-block-end: 2rem; - @media all and (min-width: 1024px) { font-size: var(--step-0); margin-top: 0; @@ -50,7 +49,6 @@ } } - .docs-nav-panel { @media all and (min-width: 1024px) { display: flex; @@ -83,11 +81,9 @@ align-items: center; margin-left: .5rem; margin-right: -10px; - margin-inline-start: .5rem; margin-inline-end: -10px; - svg { width: 40px; height: 40px; @@ -118,7 +114,6 @@ } #ham-top { - transform: rotate(41deg); } @@ -128,8 +123,6 @@ } } - - @media all and (min-width: 1024px) { .docs-site-nav { flex-direction: row; @@ -151,5 +144,4 @@ order: 1; } } - } diff --git a/eslint/docs/src/assets/scss/components/hero.scss b/eslint/docs/src/assets/scss/components/hero.scss index 54c303e..44a7390 100644 --- a/eslint/docs/src/assets/scss/components/hero.scss +++ b/eslint/docs/src/assets/scss/components/hero.scss @@ -36,11 +36,15 @@ padding: 0 calc(1rem + 1vw); padding-bottom: 0; align-items: center; + max-width: 1700px; + + @media all and (min-width: 1700px) { + margin: auto; + } } } .hero--homepage { - .section-title { margin-bottom: 1.5rem; margin-block-end: 1.5rem; diff --git a/eslint/docs/src/assets/scss/components/index.scss b/eslint/docs/src/assets/scss/components/index.scss index ea9bd3b..5989e1f 100644 --- a/eslint/docs/src/assets/scss/components/index.scss +++ b/eslint/docs/src/assets/scss/components/index.scss @@ -79,7 +79,6 @@ } #ham-top { - transform: rotate(41deg); } diff --git a/eslint/docs/src/assets/scss/components/language-switcher.scss b/eslint/docs/src/assets/scss/components/language-switcher.scss index 1aa9b2c..364f23f 100644 --- a/eslint/docs/src/assets/scss/components/language-switcher.scss +++ b/eslint/docs/src/assets/scss/components/language-switcher.scss @@ -18,11 +18,10 @@ flex: 1 0 10ch; } - -.switcher--language .switcher__select { +.switcher--language .switcher__select { flex: 1 0 12rem; - @media all and (max-width: 800px) { + @media all and (max-width: 799px) { max-width: 250px; } } diff --git a/eslint/docs/src/assets/scss/components/resources.scss b/eslint/docs/src/assets/scss/components/resources.scss index cf483cd..4ee2616 100644 --- a/eslint/docs/src/assets/scss/components/resources.scss +++ b/eslint/docs/src/assets/scss/components/resources.scss @@ -7,14 +7,12 @@ overflow: hidden; margin-bottom: .5rem; margin-block-end: .5rem; - position: relative; transition: all .2s linear; &:hover { background-color: var(--lighter-background-color); } - } .resource__image { @@ -27,7 +25,6 @@ display: block; height: 100%; width: 100%; - // object-fit: cover; object-fit: contain; } } @@ -38,7 +35,6 @@ align-self: center; } - .resource__title { // a text-decoration: none; color: var(--headings-color); @@ -51,7 +47,7 @@ left: 0; offset-inline-start: 0; top: 0; - block-inline-start: 0; + offset-block-start: 0; width: 100%; height: 100%; } diff --git a/eslint/docs/src/assets/scss/components/rules.scss b/eslint/docs/src/assets/scss/components/rules.scss index ec52172..4e0f461 100644 --- a/eslint/docs/src/assets/scss/components/rules.scss +++ b/eslint/docs/src/assets/scss/components/rules.scss @@ -13,24 +13,24 @@ background: none; border: none; - @media screen and (min-width:768px){ + @media screen and (min-width: 768px) { &:not(:first-child)::after { content: ""; display: block; padding: 1px; border-left: 1px solid var(--divider-color); - left: 0px; + left: 0; } } - @media screen and (min-width:768px) and (max-width:1023px), screen and (min-width:1440px){ + @media screen and (min-width: 768px) and (max-width: 1023px), screen and (min-width: 1440px) { &:not(:first-child)::after { height: 70%; position: absolute; } } - @media screen and (min-width:1024px) and (max-width:1439px){ + @media screen and (min-width: 1024px) and (max-width: 1439px) { &:nth-child(2)::after { height: 70%; position: absolute; @@ -65,7 +65,7 @@ } } -.rule { +.rule:not(.token) { border-radius: var(--border-radius); background-color: var(--lightest-background-color); display: flex; @@ -171,7 +171,6 @@ a.rule__name { } .related-rules__list__item { - svg { color: inherit; } @@ -194,7 +193,7 @@ a.rule__name { } } -a.rule-list-item+a.rule-list-item::before { +a.rule-list-item + a.rule-list-item::before { content: ","; display: inline-block; margin-left: 5px; diff --git a/eslint/docs/src/assets/scss/components/search.scss b/eslint/docs/src/assets/scss/components/search.scss index ff3b412..4b90582 100644 --- a/eslint/docs/src/assets/scss/components/search.scss +++ b/eslint/docs/src/assets/scss/components/search.scss @@ -1,11 +1,10 @@ [type="search"]::-webkit-search-cancel-button, [type="search"]::-webkit-search-decoration { - -webkit-appearance: none; appearance: none; } -[type=search]::-ms-clear, -[type=search]::-ms-reveal { +[type="search"]::-ms-clear, +[type="search"]::-ms-reveal { display: none; width: 0; height: 0; @@ -65,12 +64,11 @@ .search .search-results { font-size: .875rem; background-color: var(--body-background-color); - position: relative; z-index: 10; width: 100%; border-radius: 0 0 var(--border-radius) var(--border-radius); border: 1px solid var(--divider-color); - position: rekative; + position: relative; top: .25rem; max-height: 400px; overflow-y: auto; @@ -84,6 +82,7 @@ &[data-results="true"] { padding: 0; } + &[data-results="false"] { padding: 1rem; } @@ -110,7 +109,7 @@ background-color: var(--lightest-background-color); } - &:focus-within{ + &:focus-within { background-color: var(--lightest-background-color); } } @@ -121,7 +120,6 @@ margin-bottom: 0; font-family: var(--text-font); - a { display: block; text-decoration: none; diff --git a/eslint/docs/src/assets/scss/components/social-icons.scss b/eslint/docs/src/assets/scss/components/social-icons.scss index 37902a9..eddd47f 100644 --- a/eslint/docs/src/assets/scss/components/social-icons.scss +++ b/eslint/docs/src/assets/scss/components/social-icons.scss @@ -11,7 +11,6 @@ li { margin: 0; - display: inline-flex; align-items: center; a { diff --git a/eslint/docs/src/assets/scss/components/tabs.scss b/eslint/docs/src/assets/scss/components/tabs.scss index f2672a1..8a7d866 100644 --- a/eslint/docs/src/assets/scss/components/tabs.scss +++ b/eslint/docs/src/assets/scss/components/tabs.scss @@ -27,11 +27,7 @@ align-items: center; justify-content: center; border-radius: var(--border-radius) var(--border-radius) 0 0; - - transition: background-color .2s linear, - border-color .2s linear; - - + transition: background-color .2s linear, border-color .2s linear; &:hover { color: var(--link-color); @@ -55,7 +51,6 @@ } } - .c-tabs__tabpanel__title { margin-bottom: 1.5rem; margin-block-end: 1.5rem; diff --git a/eslint/docs/src/assets/scss/components/theme-switcher.scss b/eslint/docs/src/assets/scss/components/theme-switcher.scss index 0fa59fe..d44aa90 100644 --- a/eslint/docs/src/assets/scss/components/theme-switcher.scss +++ b/eslint/docs/src/assets/scss/components/theme-switcher.scss @@ -6,7 +6,6 @@ } .theme-switcher-label.theme-switcher-label { - font-size: inherit; color: inherit; font: inherit; font-family: var(--text-font); @@ -21,7 +20,7 @@ } .theme-switcher__button { - flex: 0; + flex-wrap: wrap; box-shadow: var(--shadow-xs); padding: .625rem .875rem; display: inline-flex; @@ -76,9 +75,3 @@ } } } - -.theme-switcher__button:hover { - .theme-switcher__icon { - color: var(--link-color); - } -} diff --git a/eslint/docs/src/assets/scss/components/toc.scss b/eslint/docs/src/assets/scss/components/toc.scss index d1f47a6..96647b4 100644 --- a/eslint/docs/src/assets/scss/components/toc.scss +++ b/eslint/docs/src/assets/scss/components/toc.scss @@ -1,23 +1,43 @@ .docs-toc { margin: 2rem 0; -} - -.docs-toc { - .docs-aside & { - display: none; - } @media all and (min-width: 1400px) { display: none; } .docs-aside & { + display: none; + @media all and (min-width: 1400px) { display: block; } } } +.docs-aside { + // for sticky table of contents in sidebar + .docs-toc.c-toc { + background-color: var(--body-background-color); + @media all and (min-width: 1400px) { + position: sticky; + top: 20px; + overflow-y: auto; // show scrollbar when toc is higher than viewport + padding-right: 5px; // push scrollbar away from content + max-height: calc(100vh - 32px); // minus element's margin-top + a.active { + color: var(--link-color); + font-weight: 500; + } + } + } + + .c-toc ol li.active::before { + @media all and (min-width: 1400px) { + color: var(--link-color); + } + } +} + .c-toc { ol { margin: 0; @@ -100,13 +120,10 @@ color: var(--color-neutral-400); [aria-expanded="true"] & { - -ms-transform: rotate(180deg); transform: rotate(180deg); } } - - .c-toc__panel { &[data-open="false"] { display: none; diff --git a/eslint/docs/src/assets/scss/docs-header.scss b/eslint/docs/src/assets/scss/docs-header.scss index 6ba51ce..15f21cf 100644 --- a/eslint/docs/src/assets/scss/docs-header.scss +++ b/eslint/docs/src/assets/scss/docs-header.scss @@ -10,13 +10,16 @@ align-items: start; padding-top: 0; padding-bottom: 0; - padding-block-start: 0; padding-block-end: 0; + max-width: 1700px; @media all and (min-width: 1024px) { justify-content: space-between; } + @media all and (min-width: 1700px) { + margin: auto; + } } } diff --git a/eslint/docs/src/assets/scss/docs.scss b/eslint/docs/src/assets/scss/docs.scss index 22cde70..ee40123 100644 --- a/eslint/docs/src/assets/scss/docs.scss +++ b/eslint/docs/src/assets/scss/docs.scss @@ -4,11 +4,6 @@ html { scroll-behavior: smooth; } -.docs { - max-width: 1700px; - margin: 0 auto; -} - .docs-aside__content { flex: 1; } @@ -18,12 +13,16 @@ html { flex: 1; display: flex; flex-direction: column; + max-width: 1700px; @media all and (min-width: 1024px) { display: grid; grid-template-columns: minmax(250px, 1fr) minmax(0, 3.5fr); align-items: stretch; } + @media all and (min-width: 1700px) { + margin: auto; + } } .docs-nav { @@ -31,8 +30,7 @@ html { grid-row: 1 / 2; padding-top: var(--space-l-xl); padding-block-start: var(--space-l-xl); - font-size: .875rem; - + font-size: 0.875rem; display: grid; grid-auto-rows: max-content; align-items: start; @@ -41,7 +39,6 @@ html { padding: var(--space-l-xl) 0; padding-right: var(--space-s-l); padding-inline-end: var(--space-s-l); - border-right: 1px solid var(--divider-color); border-inline-end: 1px solid var(--divider-color); } @@ -73,7 +70,6 @@ html { @media all and (min-width: 800px) { padding-right: var(--space-s-l); padding-inline-end: var(--space-s-l); - border-right: 1px solid var(--divider-color); border-inline-end: 1px solid var(--divider-color); } @@ -83,7 +79,6 @@ html { } } - .docs-aside { grid-column: 2 / 3; display: flex; @@ -131,6 +126,15 @@ div.incorrect { } } +div.img-container { + background-color: var(--img-background-color); + border-radius: var(--border-radius); + + img { + margin: 0 auto; + } +} + pre[class*="language-"] { position: relative; } @@ -138,10 +142,10 @@ pre[class*="language-"] { .c-btn.c-btn--playground { position: absolute; font-size: var(--step--1); - bottom: .5rem; - right: .5rem; - offset-block-end: .5rem; - offset-inline-end: .5rem; + bottom: 0.5rem; + right: 0.5rem; + offset-block-end: 0.5rem; + offset-inline-end: 0.5rem; @media all and (max-width: 768px) { display: none; @@ -153,3 +157,27 @@ pre[class*="language-"] { opacity: 1; } } + +#scroll-up-btn { + width: 50px; + height: 50px; + display: none; + position: fixed; + right: 50px; + bottom: 35px; + font-size: 1.5rem; + border-radius: 50%; + color: var(--body-background-color); + text-decoration: none; + justify-content: center; + align-items: center; + background-color: var(--link-color); + + @media (max-width: 800px) { + right: 35px; + } + + @media (max-width: 600px) { + right: 25px; + } +} diff --git a/eslint/docs/src/assets/scss/forms.scss b/eslint/docs/src/assets/scss/forms.scss index e6d830c..3ca1452 100644 --- a/eslint/docs/src/assets/scss/forms.scss +++ b/eslint/docs/src/assets/scss/forms.scss @@ -1,12 +1,10 @@ .c-custom-select { - -moz-appearance: none; - -webkit-appearance: none; appearance: none; box-sizing: border-box; display: block; width: 100%; max-width: 100%; - min-width: 0px; + min-width: 0; padding: .625rem .875rem; padding-right: calc(.875rem * 2.5); padding-inline-end: calc(.875rem * 2.5); @@ -19,14 +17,12 @@ background-color: var(--body-background-color); background-image: url("data:image/svg+xml,%3Csvg width='20' height='21' viewBox='0 0 20 21' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 7.60938L10 12.6094L15 7.60938' stroke='%23667085' stroke-width='1.66667' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A"), linear-gradient(to bottom, var(--body-background-color) 0%, var(--body-background-color) 100%); background-repeat: no-repeat, repeat; - background-position: right .875rem top 50%, - 0 0; + background-position: right .875rem top 50%, 0 0; background-size: 1em auto, 100%; } .label__text.label__text { display: flex; - font-size: .875rem; align-items: center; gap: .5rem; font-size: .875rem; @@ -45,7 +41,6 @@ input { font: inherit; font-size: 1rem; display: block; - line-height: 1.3; min-width: 0; line-height: 1.3; max-width: 100%; diff --git a/eslint/docs/src/assets/scss/foundations.scss b/eslint/docs/src/assets/scss/foundations.scss index 27849b3..68e4465 100644 --- a/eslint/docs/src/assets/scss/foundations.scss +++ b/eslint/docs/src/assets/scss/foundations.scss @@ -46,7 +46,6 @@ input:focus { box-shadow: 0 0 0 2px var(--link-color); } - *, *::before, *::after { @@ -63,6 +62,7 @@ html { } body { + font-size: var(--step-0); position: relative; margin: 0 auto; line-height: 1.5; @@ -81,7 +81,6 @@ body { offset-block-start: -30em; offset-inline-start: 0; offset-inline-end: auto; - z-index: 999; transition: top .1s linear; @@ -123,8 +122,12 @@ hr { width: 100%; margin: 0 auto; padding: var(--space-xl-3xl) calc(1rem + 1vw); -} + max-width: 1700px; + @media all and (min-width: 1700px) { + margin: auto; + } +} .section-head { .section-supporting-text { @@ -155,7 +158,6 @@ hr { font-size: var(--step-1); } - code, pre { font-family: var(--mono-font); @@ -170,11 +172,6 @@ code { } } -p:empty { - display: none; - margin: 0; -} - .c-icon { color: var(--icon-color); flex: none; @@ -214,7 +211,6 @@ a { } } - a { color: var(--link-color); transition: color .1s linear; @@ -249,7 +245,6 @@ h6 { overflow-wrap: break-word; } - ul, ol { margin-top: 0; @@ -259,7 +254,7 @@ ol { margin: 0 0 .75em; } - .person__bio & { + .person__bio & { padding-left: 1.5rem; padding-inline-start: 1.5rem; } @@ -326,7 +321,6 @@ nav { } } - .video { width: 90%; max-width: 1400px; @@ -347,12 +341,7 @@ nav { } } - /* typography */ -body { - font-size: var(--step-0); - line-height: 1.5; -} .eyebrow { color: var(--link-color); @@ -374,7 +363,6 @@ h6 { font-weight: 500; margin-top: 0; margin-block-start: 0; - } h2, @@ -382,7 +370,6 @@ h3, h4, h5, h6 { - .docs-main &, .components-main & { margin-top: 3rem; @@ -397,7 +384,6 @@ h6 { } } - small, caption, cite, diff --git a/eslint/docs/src/assets/scss/languages.scss b/eslint/docs/src/assets/scss/languages.scss index b3872e4..9b29097 100644 --- a/eslint/docs/src/assets/scss/languages.scss +++ b/eslint/docs/src/assets/scss/languages.scss @@ -13,10 +13,10 @@ a { color: inherit; - display: block; width: 100%; padding: .75rem .1rem; text-decoration: none; + display: block; display: flex; align-items: center; border-bottom: 1px solid var(--divider-color); @@ -27,7 +27,10 @@ color: var(--link-color); &::after { - content: "✔️"; + content: " ✔️"; + white-space: pre; + color: rgba(100%, 0%, 0%, 0); + text-shadow: 0 0 0 var(--headings-color); } } diff --git a/eslint/docs/src/assets/scss/print.scss b/eslint/docs/src/assets/scss/print.scss index 446c585..39dcc94 100644 --- a/eslint/docs/src/assets/scss/print.scss +++ b/eslint/docs/src/assets/scss/print.scss @@ -1,11 +1,11 @@ *, -*:before, -*:after, -*:first-letter, -p:first-line, -div:first-line, -blockquote:first-line, -li:first-line { +*::before, +*::after, +*::first-letter, +p::first-line, +div::first-line, +blockquote::first-line, +li::first-line { background: transparent !important; color: #000 !important; box-shadow: none !important; @@ -64,7 +64,6 @@ h6 { font-size: 14pt; } - p, h2, h3 { @@ -110,7 +109,7 @@ a:visited { // font-size: 90%; // } -abbr[title]:after { +abbr[title]::after { content: " ("attr(title) ")"; } @@ -119,17 +118,17 @@ a[href^="http://"] { color: #000; } -a[href$=".jpg"]:after, -a[href$=".jpeg"]:after, -a[href$=".gif"]:after, -a[href$=".png"]:after { +a[href$=".jpg"]::after, +a[href$=".jpeg"]::after, +a[href$=".gif"]::after, +a[href$=".png"]::after { content: " ("attr(href) ") "; display: none; } /* Don't show links that are fragment identifiers, or use the `javascript:` pseudo protocol .. taken from html5boilerplate */ -a[href^="#"]:after, -a[href^="javascript:"]:after { +a[href^="#"]::after, +a[href^="javascript:"]::after { content: ""; } @@ -172,7 +171,7 @@ tr { page-break-inside: avoid; } -body>*:not(main), +body > *:not(main), aside, *[class*="sidebar"] { display: none; @@ -184,8 +183,8 @@ button, display: none; } -a[href^='http']:not([href*='mywebsite.com'])::after { - content: ' ('attr(href) ')'; +a[href^="http"]:not([href*="eslint.org"])::after { + content: " ("attr(href) ")"; } .resource a::after { @@ -196,7 +195,10 @@ ul { page-break-inside: avoid; } -.docs-toc, .docs-index, .docs-aside, #skip-link{ +.docs-toc, +.docs-index, +.docs-aside, +#skip-link { display: none; } @@ -205,3 +207,7 @@ ul { margin: 1cm; } } + +#scroll-up-btn { + display: none; +} diff --git a/eslint/docs/src/assets/scss/styles.scss b/eslint/docs/src/assets/scss/styles.scss index e07b280..8907a6c 100644 --- a/eslint/docs/src/assets/scss/styles.scss +++ b/eslint/docs/src/assets/scss/styles.scss @@ -1,37 +1,35 @@ +@import "tokens/themes"; +@import "tokens/spacing"; +@import "tokens/typography"; +@import "tokens/ui"; -@import "tokens/themes.scss"; -@import "tokens/spacing.scss"; -@import "tokens/typography.scss"; -@import "tokens/ui.scss"; +@import "foundations"; +@import "syntax-highlighter"; +@import "docs-header"; +@import "docs-footer"; +@import "eslint-site-footer"; +@import "eslint-site-header"; +@import "forms"; +@import "docs"; +@import "versions"; +@import "languages"; -@import "foundations.scss"; -@import "syntax-highlighter.scss"; -@import "docs-header.scss"; -@import "docs-footer.scss"; -@import "eslint-site-footer.scss"; -@import "eslint-site-header.scss"; -@import "forms.scss"; -@import "docs.scss"; -@import "versions.scss"; -@import "languages.scss"; +@import "components/buttons"; +@import "components/docs-navigation"; +@import "components/toc"; +@import "components/search"; +@import "components/alert"; +@import "components/rules"; +@import "components/social-icons"; +@import "components/hero"; +@import "components/theme-switcher"; +@import "components/version-switcher"; +@import "components/language-switcher"; +@import "components/docs-index"; // docs index on the main docs pages +@import "components/index"; // used in component library +@import "components/tabs"; +@import "components/resources"; -@import "components/buttons.scss"; -@import "components/docs-navigation.scss"; -@import "components/toc.scss"; -@import "components/search.scss"; -@import "components/alert.scss"; -@import "components/rules.scss"; -@import "components/social-icons.scss"; -@import "components/hero.scss"; -@import "components/theme-switcher.scss"; -@import "components/version-switcher.scss"; -@import "components/language-switcher.scss"; -@import "components/docs-index.scss"; // docs index on the main docs pages -@import "components/index.scss"; // used in component library -@import "components/tabs.scss"; -@import "components/index.scss"; -@import "components/resources.scss"; +@import "carbon-ads"; -@import "carbon-ads.scss"; - -@import "utilities.scss"; +@import "utilities"; diff --git a/eslint/docs/src/assets/scss/syntax-highlighter.scss b/eslint/docs/src/assets/scss/syntax-highlighter.scss index b0160f7..cb744db 100644 --- a/eslint/docs/src/assets/scss/syntax-highlighter.scss +++ b/eslint/docs/src/assets/scss/syntax-highlighter.scss @@ -1,9 +1,11 @@ code[class*="language-"], pre[class*="language-"] { - font-family: var(--mono-font), Consolas, + font-family: + var(--mono-font), + Consolas, Monaco, - 'Andale Mono', - 'Ubuntu Mono', + "Andale Mono", + "Ubuntu Mono", monospace; font-size: 1em; text-align: left; @@ -13,19 +15,11 @@ pre[class*="language-"] { word-wrap: normal; line-height: 1.5; font-variant-ligatures: none; - - -moz-tab-size: 4; - -o-tab-size: 4; tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; hyphens: none; } @media print { - code[class*="language-"], pre[class*="language-"] { text-shadow: none; @@ -37,26 +31,28 @@ pre[class*="language-"] { padding: 1.5rem; margin: 1.5rem 0; overflow: auto; - background-color: var(--color-neutral-50); border-radius: var(--border-radius); - background-color: var(--lightest-background-color); color: var(--color-neutral-900); [data-theme="dark"] & { color: var(--color-neutral-100); } + + &.line-numbers-mode { + padding-left: calc(1.5rem + 2.4em + 1.2rem); + } } -:not(pre)>code[class*="language-"], +:not(pre) > code[class*="language-"], pre[class*="language-"] { background-color: var(--lightest-background-color); } /* Inline code */ -:not(pre)>code[class*="language-"] { - padding: .1em; - border-radius: .3em; +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; white-space: normal; } @@ -64,19 +60,17 @@ pre[class*="language-"] { .token.prolog, .token.doctype, .token.cdata { - color: #6E7F8E; + color: #6e7f8e; [data-theme="dark"] & { - color: #8E9FAE; + color: #8e9fae; } } - .token.namespace { - opacity: .7; + opacity: 0.7; } - .token.selector, .token.attr-name, .token.string, @@ -86,7 +80,6 @@ pre[class*="language-"] { color: var(--link-color); } - .token.atrule, .token.attr-value, .token.keyword { @@ -106,25 +99,24 @@ pre[class*="language-"] { cursor: help; } -pre { - counter-reset: lineNumber; -} - -code .highlight-line { - font-variant-ligatures: none; -} - -code .highlight-line:before { - -webkit-user-select: none; - color: var(--icon-color); - content: counter(lineNumber); - counter-increment: lineNumber; - display: inline-block; - font-variant-numeric: tabular-nums; - margin-right: 1.2em; - padding-right: 1.2em; - margin-inline-end: 1.2em; - padding-inline-end: 1.2em; +.line-numbers-wrapper { + position: absolute; + top: 0; + left: 1.5rem; text-align: right; - width: 2.4em; + padding-top: 1.5rem; + font-size: 1em; + font-family: var(--mono-font), Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; + line-height: 1.5; + color: var(--icon-color); + font-variant-ligatures: none; + + .line-number { + user-select: none; + color: var(--icon-color); + display: inline-block; + font-variant-numeric: tabular-nums; + text-align: right; + width: 1.2em; + } } diff --git a/eslint/docs/src/assets/scss/tokens/spacing.scss b/eslint/docs/src/assets/scss/tokens/spacing.scss index 1f5549b..2bc5424 100644 --- a/eslint/docs/src/assets/scss/tokens/spacing.scss +++ b/eslint/docs/src/assets/scss/tokens/spacing.scss @@ -6,15 +6,7 @@ --fluid-screen: 100vw; --fluid-bp: calc((var(--fluid-screen) - var(--fluid-min-width) / 16 * 1rem) / (var(--fluid-max-width) - var(--fluid-min-width))); -} -@media screen and (min-width: 1024px) { - :root { - --fluid-screen: calc(var(--fluid-max-width) * 1px); - } -} - -:root { --fc-3xs-min: (var(--fc-s-min) * 0.25); --fc-3xs-max: (var(--fc-s-max) * 0.25); diff --git a/eslint/docs/src/assets/scss/tokens/themes.scss b/eslint/docs/src/assets/scss/tokens/themes.scss index 11ced7f..de956b8 100644 --- a/eslint/docs/src/assets/scss/tokens/themes.scss +++ b/eslint/docs/src/assets/scss/tokens/themes.scss @@ -1,65 +1,65 @@ :root { /* Tier 1 variables */ // colors - --color-neutral-25: #FCFCFD; - --color-neutral-50: #F9FAFB; - --color-neutral-100: #F2F4F7; - --color-neutral-200: #E4E7EC; - --color-neutral-300: #D0D5DD; - --color-neutral-400: #98A2B3; + --color-neutral-25: #fcfcfd; + --color-neutral-50: #f9fafb; + --color-neutral-100: #f2f4f7; + --color-neutral-200: #e4e7ec; + --color-neutral-300: #d0d5dd; + --color-neutral-400: #98a2b3; --color-neutral-500: #667085; --color-neutral-600: #475467; --color-neutral-700: #344054; - --color-neutral-800: #1D2939; + --color-neutral-800: #1d2939; --color-neutral-900: #101828; - --color-primary-25: #FBFBFF; - --color-primary-50: #F6F6FE; - --color-primary-100: #ECECFD; - --color-primary-200: #DEDEFF; - --color-primary-300: #CCCCFA; - --color-primary-400: #B7B7FF; - --color-primary-500: #A0A0F5; - --color-primary-600: #8080F2; - --color-primary-700: #6358D4; - --color-primary-800: #4B32C3; - --color-primary-900: #341BAB; + --color-primary-25: #fbfbff; + --color-primary-50: #f6f6fe; + --color-primary-100: #ececfd; + --color-primary-200: #dedeff; + --color-primary-300: #ccccfa; + --color-primary-400: #b7b7ff; + --color-primary-500: #a0a0f5; + --color-primary-600: #8080f2; + --color-primary-700: #6358d4; + --color-primary-800: #4b32c3; + --color-primary-900: #341bab; - --color-warning-25: #FFFCF5; - --color-warning-50: #FFFAEB; - --color-warning-100: #FEF0C7; - --color-warning-200: #FEDF89; - --color-warning-300: #FEC84B; - --color-warning-400: #FDB022; - --color-warning-500: #F79009; - --color-warning-600: #DC6803; - --color-warning-700: #B54708; - --color-warning-800: #93370D; - --color-warning-900: #7A2E0E; + --color-warning-25: #fffcf5; + --color-warning-50: #fffaeb; + --color-warning-100: #fef0c7; + --color-warning-200: #fedf89; + --color-warning-300: #fec84b; + --color-warning-400: #fdb022; + --color-warning-500: #f79009; + --color-warning-600: #dc6803; + --color-warning-700: #b54708; + --color-warning-800: #93370d; + --color-warning-900: #7a2e0e; - --color-success-25: #F6FEF9; - --color-success-50: #ECFDF3; - --color-success-100: #D1FADF; - --color-success-200: #A6F4C5; - --color-success-300: #6CE9A6; - --color-success-400: #32D583; - --color-success-500: #12B76A; + --color-success-25: #f6fef9; + --color-success-50: #ecfdf3; + --color-success-100: #d1fadf; + --color-success-200: #a6f4c5; + --color-success-300: #6ce9a6; + --color-success-400: #32d583; + --color-success-500: #12b76a; --color-success-600: #039855; - --color-success-700: #027A48; - --color-success-800: #05603A; - --color-success-900: #054F31; + --color-success-700: #027a48; + --color-success-800: #05603a; + --color-success-900: #054f31; - --color-rose-25: #FFF5F6; - --color-rose-50: #FFF1F3; - --color-rose-100: #FFE4E8; - --color-rose-200: #FECDD6; - --color-rose-300: #FEA3B4; - --color-rose-400: #FD6F8E; - --color-rose-500: #F63D68; - --color-rose-600: #E31B54; - --color-rose-700: #C01048; - --color-rose-800: #A11043; - --color-rose-900: #89123E; + --color-rose-25: #fff5f6; + --color-rose-50: #fff1f3; + --color-rose-100: #ffe4e8; + --color-rose-200: #fecdd6; + --color-rose-300: #fea3b4; + --color-rose-400: #fd6f8e; + --color-rose-500: #f63d68; + --color-rose-600: #e31b54; + --color-rose-700: #c01048; + --color-rose-800: #a11043; + --color-rose-900: #89123e; /* Tier 2 variables */ --primary-button-background-color: var(--color-primary-800); @@ -79,7 +79,6 @@ --border-color: var(--color-neutral-300); --divider-color: var(--color-neutral-200); - --icon-color: var(--color-neutral-400); --dark-icon-color: var(--color-neutral-500); --link-color: var(--color-primary-800); @@ -97,14 +96,14 @@ --body-background-color: var(--color-neutral-900); --body-text-color: var(--color-neutral-300); --headings-color: #fff; - + --divider-color: var(--color-neutral-600); --border-color: var(--color-neutral-500); - + --icon-color: var(--body-text-color); --dark-icon-color: #fff; --link-color: var(--color-primary-400); - + --lighter-background-color: var(--color-neutral-800); --lightest-background-color: var(--color-neutral-800); --docs-lightest-background-color: var(--color-neutral-800); @@ -114,8 +113,6 @@ } } - - html[data-theme="light"] { --body-background-color: #fff; --body-text-color: var(--color-neutral-500); @@ -124,7 +121,6 @@ html[data-theme="light"] { --border-color: var(--color-neutral-300); --divider-color: var(--color-neutral-200); - --icon-color: var(--color-neutral-400); --dark-icon-color: var(--color-neutral-500); --link-color: var(--color-primary-800); @@ -135,6 +131,7 @@ html[data-theme="light"] { --hero-background-color: var(--color-neutral-25); --footer-background-color: var(--color-neutral-25); --outline-color: var(--color-brand); + --img-background-color: #fff; } html[data-theme="dark"] { @@ -155,4 +152,5 @@ html[data-theme="dark"] { --hero-background-color: var(--color-neutral-800); --footer-background-color: var(--color-neutral-800); --outline-color: #fff; + --img-background-color: var(--color-neutral-300); } diff --git a/eslint/docs/src/assets/scss/tokens/typography.scss b/eslint/docs/src/assets/scss/tokens/typography.scss index 8c178b5..a9e935b 100644 --- a/eslint/docs/src/assets/scss/tokens/typography.scss +++ b/eslint/docs/src/assets/scss/tokens/typography.scss @@ -1,13 +1,5 @@ /* @link https://utopia.fyi/type/calculator?c=320,16,1.125,1280,16,1.25,6,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l */ -:root { - --fluid-min-width: 320; - --fluid-max-width: 1280; - - --fluid-screen: 100vw; - --fluid-bp: calc((var(--fluid-screen) - var(--fluid-min-width) / 16 * 1rem) / (var(--fluid-max-width) - var(--fluid-min-width))); -} - @media screen and (min-width: 1280px) { :root { --fluid-screen: calc(var(--fluid-max-width) * 1px); @@ -15,6 +7,12 @@ } :root { + --fluid-min-width: 320; + --fluid-max-width: 1280; + + --fluid-screen: 100vw; + --fluid-bp: calc((var(--fluid-screen) - var(--fluid-min-width) / 16 * 1rem) / (var(--fluid-max-width) - var(--fluid-min-width))); + --f--2-min: 12.64; --f--2-max: 10.24; --step--2: calc(((var(--f--2-min) / 16) * 1rem) + (var(--f--2-max) - var(--f--2-min)) * var(--fluid-bp)); @@ -50,30 +48,31 @@ --f-6-min: 32.44; --f-6-max: 61.04; --step-6: calc(((var(--f-6-min) / 16) * 1rem) + (var(--f-6-max) - var(--f-6-min)) * var(--fluid-bp)); -} -:root { --mono-font: "Mono Punctuators", "Space Mono", monospace; - --text-font: "Inter", - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - Roboto, - Helvetica, - Arial, - sans-serif, - "Apple Color Emoji", - "Segoe UI Emoji", - "Segoe UI Symbol"; - --display-font: "Space Grotesk", - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - Roboto, - Helvetica, - Arial, - sans-serif, - "Apple Color Emoji", - "Segoe UI Emoji", - "Segoe UI Symbol"; + --text-font: + "Inter", + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + Helvetica, + Arial, + sans-serif, + "Apple Color Emoji", + "Twemoji Country Flags", + "Segoe UI Emoji", + "Segoe UI Symbol"; + --display-font: + "Space Grotesk", + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + Helvetica, + Arial, + sans-serif, + "Apple Color Emoji", + "Segoe UI Emoji", + "Segoe UI Symbol"; } diff --git a/eslint/docs/src/assets/scss/tokens/ui.scss b/eslint/docs/src/assets/scss/tokens/ui.scss index 08759db..49380e1 100644 --- a/eslint/docs/src/assets/scss/tokens/ui.scss +++ b/eslint/docs/src/assets/scss/tokens/ui.scss @@ -1,8 +1,9 @@ :root { // elevations - --shadow-lg: 0px 12px 16px -4px rgba(16, 24, 40, 0.1), - 0px 4px 6px -2px rgba(16, 24, 40, 0.05); - --shadow-xs: 0px 1px 2px rgba(16, 24, 40, 0.05); + --shadow-lg: + 0 12px 16px -4px rgba(16, 24, 40, 0.1), + 0 4px 6px -2px rgba(16, 24, 40, 0.05); + --shadow-xs: 0 1px 2px rgba(16, 24, 40, 0.05); --border-radius: .5rem; } diff --git a/eslint/docs/src/assets/scss/utilities.scss b/eslint/docs/src/assets/scss/utilities.scss index ac40064..c296358 100644 --- a/eslint/docs/src/assets/scss/utilities.scss +++ b/eslint/docs/src/assets/scss/utilities.scss @@ -11,8 +11,7 @@ clip: rect(0 0 0 0); clip-path: inset(100%); height: 1px; - overflow: - hidden; + overflow: hidden; position: absolute; width: 1px; white-space: nowrap; @@ -35,7 +34,6 @@ } .text.text { - font-size: inherit; color: inherit; font: inherit; font-family: var(--text-font); @@ -120,7 +118,6 @@ grid-column: 1 / 8; } - .span-1-12 { grid-column: 1 / -1; } diff --git a/eslint/docs/src/assets/scss/versions.scss b/eslint/docs/src/assets/scss/versions.scss index 2a5ba4f..f0979c6 100644 --- a/eslint/docs/src/assets/scss/versions.scss +++ b/eslint/docs/src/assets/scss/versions.scss @@ -14,10 +14,10 @@ a { color: var(--link-color); - display: block; width: 100%; padding: 1rem .5rem; text-decoration: none; + display: block; display: flex; align-items: center; border-bottom: 1px solid var(--divider-color); @@ -28,7 +28,10 @@ color: var(--link-color); &::after { - content: "✔️"; + content: " ✔️"; + white-space: pre; + color: rgba(100%, 0%, 0%, 0); + text-shadow: 0 0 0 var(--headings-color); } } diff --git a/eslint/docs/src/developer-guide/architecture/index.md b/eslint/docs/src/contribute/architecture/index.md similarity index 97% rename from eslint/docs/src/developer-guide/architecture/index.md rename to eslint/docs/src/contribute/architecture/index.md index 71e4469..3370e6d 100644 --- a/eslint/docs/src/developer-guide/architecture/index.md +++ b/eslint/docs/src/contribute/architecture/index.md @@ -1,14 +1,15 @@ --- title: Architecture -layout: doc eleventyNavigation: key: architecture - parent: developer guide + parent: contribute to eslint title: Architecture - order: 1 + order: 5 --- -
    dependency graph
    +:::img-container +dependency graph +::: At a high level, there are a few key parts to ESLint: diff --git a/eslint/docs/src/developer-guide/code-conventions.md b/eslint/docs/src/contribute/code-conventions.md similarity index 84% rename from eslint/docs/src/developer-guide/code-conventions.md rename to eslint/docs/src/contribute/code-conventions.md index 18b7274..089e124 100644 --- a/eslint/docs/src/developer-guide/code-conventions.md +++ b/eslint/docs/src/contribute/code-conventions.md @@ -1,6 +1,5 @@ --- title: Code Conventions -layout: doc --- Code conventions for ESLint are determined by @@ -8,7 +7,7 @@ Code conventions for ESLint are determined by 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 +our own [rule documentation](../rules/) and otherwise, see the documentation of the plugin in which the rule can be found. If you need to make changes to a `package.json` file, please see the diff --git a/eslint/docs/src/contribute/code-of-conduct.md b/eslint/docs/src/contribute/code-of-conduct.md new file mode 100644 index 0000000..3d82299 --- /dev/null +++ b/eslint/docs/src/contribute/code-of-conduct.md @@ -0,0 +1,10 @@ +--- +title: Code of Conduct +eleventyNavigation: + key: code of conduct + parent: contribute to eslint + title: Code of Conduct + order: 0 +--- + +ESLint welcomes contributions from everyone and adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). We kindly request that you read over this code of conduct before contributing. diff --git a/eslint/docs/src/contribute/core-rules.md b/eslint/docs/src/contribute/core-rules.md new file mode 100644 index 0000000..5610c00 --- /dev/null +++ b/eslint/docs/src/contribute/core-rules.md @@ -0,0 +1,110 @@ +--- +title: Contribute to Core Rules +eleventyNavigation: + key: contribute core rule + parent: contribute to eslint + title: Contribute to Core Rules + order: 10 +--- + +The ESLint core rules are the rules included in the ESLint package. + +## Rule Writing Documentation + +For full reference information on writing rules, refer to [Custom Rules](../extend/custom-rules). Both custom rules and core rules have the same API. The primary difference between core and custom rules are: + +1. Core rules are included in the `eslint` package. +1. Core rules must adhere to the conventions documented on this page. + +## File Structure + +Each core rule in ESLint has three files named with its identifier (for example, `no-extra-semi`). + +* in the `lib/rules` directory: a source file (for example, `no-extra-semi.js`) +* in the `tests/lib/rules` directory: a test file (for example, `no-extra-semi.js`) +* in the `docs/src/rules` directory: a Markdown documentation file (for example, `no-extra-semi.md`) + +**Important:** If you submit a core rule to the ESLint repository, you **must** follow the conventions explained below. + +Here is the basic format of the source file for a rule: + +```js +/** + * @fileoverview Rule to disallow unnecessary semicolons + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +/** @type {import('../shared/types').Rule} */ +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary semicolons", + recommended: true, + url: "https://eslint.org/docs/rules/no-extra-semi" + }, + fixable: "code", + schema: [] // no options + }, + create: function(context) { + return { + // callback functions + }; + } +}; +``` + +## Rule Unit Tests + +Each bundled rule for ESLint core must have a set of unit tests submitted with it to be accepted. The test file is named the same as the source file but lives in `tests/lib/`. For example, if the rule source file is `lib/rules/foo.js` then the test file should be `tests/lib/rules/foo.js`. + +ESLint provides the [`RuleTester`](../integrate/nodejs-api#ruletester) utility to make it easy to write tests for rules. + +## Performance Testing + +To keep the linting process efficient and unobtrusive, it is useful to verify the performance impact of new rules or modifications to existing rules. + +To learn how to profile the performance of individual rules, refer to [Profile Rule Performance](../extend/custom-rules#profile-rule-performance) in the custom rules documentation. + +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 main +Switched to branch 'main' + +$ npm run perf +CPU Speed is 2200 with multiplier 7500000 +Performance Run #1: 1394.689313ms +Performance Run #2: 1423.295351ms +Performance Run #3: 1385.09515ms +Performance Run #4: 1382.406982ms +Performance Run #5: 1409.68566ms +Performance budget ok: 1394.689313ms (limit: 3409.090909090909ms) + +$ git checkout my-rule-branch +Switched to branch 'my-rule-branch' + +$ npm run perf +CPU Speed is 2200 with multiplier 7500000 +Performance Run #1: 1443.736547ms +Performance Run #2: 1419.193291ms +Performance Run #3: 1436.018228ms +Performance Run #4: 1473.605485ms +Performance Run #5: 1457.455283ms +Performance budget ok: 1443.736547ms (limit: 3409.090909090909ms) +``` + +## Rule Naming Conventions + +The rule naming conventions for ESLint are as follows: + +* Use dashes between words. +* If your rule only disallows something, prefix it with `no-` such as `no-eval` for disallowing `eval()` and `no-debugger` for disallowing `debugger`. +* If your rule is enforcing the inclusion of something, use a short name without a special prefix. diff --git a/eslint/docs/src/developer-guide/development-environment.md b/eslint/docs/src/contribute/development-environment.md similarity index 70% rename from eslint/docs/src/developer-guide/development-environment.md rename to eslint/docs/src/contribute/development-environment.md index ad21740..b84af4e 100644 --- a/eslint/docs/src/developer-guide/development-environment.md +++ b/eslint/docs/src/contribute/development-environment.md @@ -1,12 +1,10 @@ --- -title: Development Environment -layout: doc +title: Set up a Development Environment eleventyNavigation: - key: set up a development environment - parent: developer guide - title: Set Up a Development Environment - order: 2 - + key: development environment + parent: contribute to eslint + title: Set up a Development Environment + order: 6 --- ESLint has a very lightweight development environment that makes updating code fast and easy. This is a step-by-step guide to setting up a local development environment that will let you contribute back to the project. @@ -17,10 +15,16 @@ Go to to download and install the latest stable version fo Most of the installers already come with [npm](https://www.npmjs.com/) but if for some reason npm doesn't work on your system, you can install it manually using the instructions on the site. -## Step 2: Fork and checkout your own ESLint repository +## Step 2: Fork and Checkout Your Own ESLint Repository Go to and click the "Fork" button. Follow the [GitHub documentation](https://help.github.com/articles/fork-a-repo) for forking and cloning. +Clone your fork: + +```shell +git clone https://github.com//eslint +``` + Once you've cloned the repository, run `npm install` to get all the necessary dependencies: ```shell @@ -30,7 +34,9 @@ npm install You must be connected to the Internet for this step to work. You'll see a lot of utilities being downloaded. -## Step 3: Add the upstream source +**Note:** It's a good idea to re-run `npm install` whenever you pull from the main repository to ensure you have the latest development dependencies. + +## Step 3: Add the Upstream Source The *upstream source* is the main ESLint repository where active development happens. While you won't have push access to upstream, you will have pull access, allowing you to pull in the latest code whenever you want. @@ -44,7 +50,7 @@ Now, the remote `upstream` points to the upstream source. ## Step 4: Install the Yeoman Generator -[Yeoman](http://yeoman.io) is a scaffold generator that ESLint uses to help streamline development of new rules. If you don't already have Yeoman installed, you can install it via npm: +[Yeoman](https://yeoman.io) is a scaffold generator that ESLint uses to help streamline development of new rules. If you don't already have Yeoman installed, you can install it via npm: ```shell npm install -g yo @@ -58,7 +64,7 @@ npm install -g generator-eslint Please see the [generator documentation](https://github.com/eslint/generator-eslint) for instructions on how to use it. -## Step 5: Run the tests +## Step 5: Run the Tests Running the tests is the best way to ensure you have correctly set up your development environment. Make sure you're in the `eslint` directory and run: @@ -70,9 +76,24 @@ The testing takes a few minutes to complete. If any tests fail, that likely mean ## Reference Information +### Directory Structure + +The ESLint directory and file structure is as follows: + +* `bin` - executable files that are available when ESLint is installed +* `conf` - default configuration information +* `docs` - documentation for the project +* `lib` - contains the source code + * `formatters` - all source files defining formatters + * `rules` - all source files defining rules +* `tests` - the main unit test folder + * `lib` - tests for the source code + * `formatters` - tests for the formatters + * `rules` - tests for the rules + ### Workflow -Once you have your development environment installed, you can make and submit changes to the ESLint source files. Doing this successfully requires careful adherence to our [pull-request submission workflow](contributing/pull-requests). +Once you have your development environment installed, you can make and submit changes to the ESLint source files. Doing this successfully requires careful adherence to our [pull-request submission workflow](./pull-requests). ### Build Scripts diff --git a/eslint/docs/src/maintainer-guide/governance.md b/eslint/docs/src/contribute/governance.md similarity index 95% rename from eslint/docs/src/maintainer-guide/governance.md rename to eslint/docs/src/contribute/governance.md index 0705b64..563840a 100644 --- a/eslint/docs/src/maintainer-guide/governance.md +++ b/eslint/docs/src/contribute/governance.md @@ -1,11 +1,10 @@ --- title: Governance -layout: doc eleventyNavigation: key: governance - parent: maintainer guide + parent: contribute to eslint title: Governance - order: 4 + order: 11 --- @@ -29,7 +28,7 @@ As Contributors gain experience and familiarity with the project, their profile ### Website Team Member -Website Team Members are community members who have shown that they are committed to the continued maintenance of [eslint.org](https://eslint.org/) through ongoing engagement with the community. Website Team Members are given push access to the `eslint.org` GitHub repository and must abide by the project's [Contribution Guidelines](../developer-guide/contributing/). +Website Team Members are community members who have shown that they are committed to the continued maintenance of [eslint.org](https://eslint.org/) through ongoing engagement with the community. Website Team Members are given push access to the `eslint.org` GitHub repository and must abide by the project's [Contribution Guidelines](../contribute/). Website Team Members: @@ -37,8 +36,8 @@ Website Team Members are community members who have shown that they are committe * Are expected to delete their public branches when they are no longer necessary. * Must submit pull requests for all changes. * Have their work reviewed by Reviewers and TSC members before acceptance into the repository. -* May label and close website-related issues (see [Managing Issues](issues.html)) -* May merge some pull requests (see [Managing Pull Requests](pullrequests.html)) +* May label and close website-related issues (see [Manage Issues](../maintain/manage-issues)) +* May merge some pull requests (see [Review Pull Requests](../maintain/review-pull-requests)) To become a Website Team Member: @@ -52,7 +51,7 @@ It is important to recognize that membership on the website team is a privilege, ### Committers -Committers are community members who have shown that they are committed to the continued development of the project through ongoing engagement with the community. Committers are given push access to the project's GitHub repos and must abide by the project's [Contribution Guidelines](../developer-guide/contributing/). +Committers are community members who have shown that they are committed to the continued development of the project through ongoing engagement with the community. Committers are given push access to the project's GitHub repos and must abide by the project's [Contribution Guidelines](../contribute/). Committers: @@ -60,8 +59,8 @@ Committers: * Are expected to delete their public branches when they are no longer necessary. * Must submit pull requests for all changes. * Have their work reviewed by TSC members before acceptance into the repository. -* May label and close issues (see [Managing Issues](issues.html)) -* May merge some pull requests (see [Managing Pull Requests](pullrequests.html)) +* May label and close issues (see [Manage Issues](../maintain/manage-issues)) +* May merge some pull requests (see [Review Pull Requests](../maintain/review-pull-requests)) To become a Committer: @@ -144,7 +143,7 @@ A Reviewer is invited to become a TSC member by existing TSC members. A nominati 1. Add the GitHub user to the "ESLint TSC" GitHub team 1. Set the GitHub user to be have the "Owner" role for the ESLint organization -1. Send a welcome email with a link to the [maintainer guide](./) and instructions for npm 2FA. +1. Send a welcome email with a link to the [Maintain ESLint documentation](../maintain/) and instructions for npm 2FA. 1. Invite to the Discord TSC channel 1. Make the TSC member an admin on the ESLint team mailing list 1. Add the TSC member to the recurring TSC meeting event on Google Calendar diff --git a/eslint/docs/src/developer-guide/contributing/index.md b/eslint/docs/src/contribute/index.md similarity index 61% rename from eslint/docs/src/developer-guide/contributing/index.md rename to eslint/docs/src/contribute/index.md index a5dfce7..3a20390 100644 --- a/eslint/docs/src/developer-guide/contributing/index.md +++ b/eslint/docs/src/contribute/index.md @@ -1,12 +1,9 @@ --- -title: Contributing -layout: doc +title: Contribute to ESLint eleventyNavigation: - key: contributing - parent: developer guide - title: Contributing - order: 10 - + key: contribute to eslint + title: Contribute to ESLint + order: 3 --- One of the great things about open source projects is that anyone can contribute in any number of meaningful ways. ESLint couldn't exist without the help of the many contributors it's had since the project began, and we want you to feel like you can contribute and make a difference as well. @@ -17,34 +14,54 @@ This guide is intended for anyone who wants to contribute to an ESLint project. ESLint welcomes contributions from everyone and adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). We kindly request that you read over our code of conduct before contributing. -## [Bug Reporting](reporting-bugs) +## [Report Bugs](report-bugs) Think you found a problem? We'd love to hear about it. This section explains how to submit a bug, the type of information we need to properly verify it, and the overall process. -## Proposing a [New Rule](new-rules) +## [Propose a New Rule](propose-new-rule) We get a lot of proposals for new rules in ESLint. This section explains how we determine which rules are accepted and what information you should provide to help us evaluate your proposal. -## Proposing a [Rule Change](rule-changes) +## [Propose a Rule Change](propose-rule-change) Want to make a change to an existing rule? This section explains the process and how we evaluate such proposals. -## Requesting a [Change](changes) +## [Request a Change](request-change) If you'd like to request a change other than a bug fix or new rule, this section explains that process. -## Reporting a security vulnerability +## [Architecture](architecture) -To report a security vulnerability in ESLint, please use our [HackerOne program](https://hackerone.com/eslint). +Learn about the architecture of the ESLint project. -## [Working on Issues](working-on-issues) +## [Set up a Development Environment](development-environment) + +Developing for ESLint is a bit different than running it on the command line. This section shows you how to set up a development environment and get you ready to write code. + +## [Run the Tests](tests) + +There are a lot of unit tests included with ESLint to make sure that we're keeping on top of code quality. This section explains how to run the unit tests. + +## [Work on Issues](work-on-issue) Have some extra time and want to contribute? This section talks about the process of working on issues. -## Submitting a [Pull Request](pull-requests) +## [Submit a Pull Request](pull-requests) We're always looking for contributions from the community. This section explains the requirements for pull requests and the process of contributing code. -## Signing the CLA +## [Contribute to Core Rules](core-rules) + +This section explains how to add to the core rules of ESLint. + +## [Governance](governance) + +Describes the governance policy for ESLint, including the rights and privileges of individuals inside the project. + +## [Report a Security Vulnerability](report-security-vulnerability) + +To report a security vulnerability in ESLint, please create an advisory on Github. + +## Sign the CLA In order to submit code or documentation to an ESLint project, you will need to electronically sign our Contributor License Agreement. 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. You will be asked to sign the CLA in the first pull request you open. diff --git a/eslint/docs/src/developer-guide/package-json-conventions.md b/eslint/docs/src/contribute/package-json-conventions.md similarity index 83% rename from eslint/docs/src/developer-guide/package-json-conventions.md rename to eslint/docs/src/contribute/package-json-conventions.md index 4733b42..99afe8b 100644 --- a/eslint/docs/src/developer-guide/package-json-conventions.md +++ b/eslint/docs/src/contribute/package-json-conventions.md @@ -1,7 +1,6 @@ --- title: Package.json Conventions -layout: doc -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/developer-guide/package-json-conventions.md +edit_link: https://github.com/eslint/eslint/edit/main/docs/src/contribute/package-json-conventions.md --- The following applies to the "scripts" section of `package.json` files. @@ -10,22 +9,16 @@ The following applies to the "scripts" section of `package.json` files. npm script names MUST contain only lower case letters, `:` to separate parts, `-` to separate words, and `+` to separate file extensions. Each part name SHOULD be either a full English word (e.g. `coverage` not `cov`) or a well-known initialism in all lowercase (e.g. `wasm`). -Here is a summary of the proposal in EBNF. +Here is a summary of the proposal in ABNF. -```ebnf -name = life-cycle | main ":fix"? target? option* ":watch"? - -life-cycle = prepare | preinstall | install | postinstall | prepublish | preprepare | prepare | postprepare | prepack | postpack | prepublishOnly; - -main = "build" | "lint" | "start" | "test"; - -target = ":" word ("-" word)* | extension ("+" extension)*; - -option = ":" word ("-" word)*; - -word = [a-z]+; - -extension = [a-z0-9]+; +```abnf +name = life-cycle / main target? option* ":watch"? +life-cycle = "prepare" / "preinstall" / "install" / "postinstall" / "prepublish" / "preprepare" / "prepare" / "postprepare" / "prepack" / "postpack" / "prepublishOnly" +main = "build" / "lint" ":fix"? / "release" / "start" / "test" +target = ":" word ("-" word)* / extension ("+" extension)* +option = ":" word ("-" word)* +word = ALPHA + +extension = ( ALPHA / DIGIT )+ ``` ## Order @@ -42,6 +35,10 @@ Scripts that generate a set of files from source code and / or data MUST have na If a package contains any `build:*` scripts, there MAY be a script named `build`. If so, SHOULD produce the same output as running each of the `build` scripts individually. It MUST produce a subset of the output from running those scripts. +### Release + +Scripts that have public side effects (publishing the web site, committing to Git, etc.) MUST begin with `release`. + ### Lint Scripts that statically analyze files (mostly, but not limited to running `eslint` itself) MUST have names that begin with `lint`. diff --git a/eslint/docs/src/developer-guide/contributing/new-rules.md b/eslint/docs/src/contribute/propose-new-rule.md similarity index 89% rename from eslint/docs/src/developer-guide/contributing/new-rules.md rename to eslint/docs/src/contribute/propose-new-rule.md index f3c859a..7aa9d3a 100644 --- a/eslint/docs/src/developer-guide/contributing/new-rules.md +++ b/eslint/docs/src/contribute/propose-new-rule.md @@ -1,7 +1,10 @@ --- -title: New Rules -layout: doc - +title: Propose a New Rule +eleventyNavigation: + key: propose rule + parent: contribute to eslint + title: Propose a New Rule + order: 2 --- ESLint is all about rules. For most of the project's lifetime, we've had over 200 rules, and that list continues to grow. However, we can't just accept any proposed rule because all rules need to work cohesively together. As such, we have some guidelines around which rules can be part of the ESLint core and which are better off as custom rules and plugins. @@ -23,7 +26,7 @@ Even though these are the formal criteria for inclusion, each rule is evaluated ## Proposing a Rule -If you want to propose a new rule, please see how to [create a pull request](/docs/developer-guide/contributing/pull-requests) or submit an issue by filling out a [new rule template](https://github.com/eslint/eslint/issues/new/choose). +If you want to propose a new rule, please see how to [create a pull request](pull-requests) or submit an issue by filling out a [new rule template](https://github.com/eslint/eslint/issues/new/choose). We need all of this information in order to determine whether or not the rule is a good core rule candidate. @@ -43,4 +46,4 @@ The ESLint team doesn't implement new rules that are suggested by users because ## Alternative: Creating Your Own Rules -Remember that ESLint is completely pluggable, which means you can create your own rules and distribute them using plugins. We did this on purpose because we don't want to be the gatekeepers for all possible rules. Even if we don't accept a rule into the core, that doesn't mean you can't have the exact rule that you want. See the [working with rules](../working-with-rules) and [working with plugins](../working-with-plugins) documentation for more information. +Remember that ESLint is completely pluggable, which means you can create your own rules and distribute them using plugins. We did this on purpose because we don't want to be the gatekeepers for all possible rules. Even if we don't accept a rule into the core, that doesn't mean you can't have the exact rule that you want. See the [Custom Rules](../extend/custom-rules) and [Create Plugins](../extend/plugins) documentation for more information. diff --git a/eslint/docs/src/developer-guide/contributing/rule-changes.md b/eslint/docs/src/contribute/propose-rule-change.md similarity index 76% rename from eslint/docs/src/developer-guide/contributing/rule-changes.md rename to eslint/docs/src/contribute/propose-rule-change.md index b96d668..d2d198b 100644 --- a/eslint/docs/src/developer-guide/contributing/rule-changes.md +++ b/eslint/docs/src/contribute/propose-rule-change.md @@ -1,14 +1,17 @@ --- -title: Rule Changes -layout: doc - +title: Propose a Rule Change +eleventyNavigation: + key: propose rule change + parent: contribute to eslint + title: Propose a Rule Change + order: 3 --- Occasionally, a core ESLint rule needs to be changed. This is not necessarily a bug, but rather, an enhancement that makes a rule more configurable. In those situations, we will consider making changes to rules. ## Proposing a Rule Change -To propose a change to an existing rule, [create a pull request](/docs/developer-guide/contributing/pull-requests) or [new issue](https://github.com/eslint/eslint/issues/new/choose) and fill out the template. +To propose a change to an existing rule, [create a pull request](pull-requests) or [new issue](https://github.com/eslint/eslint/issues/new/choose) and fill out the template. We need all of this information in order to determine whether or not the change is a good candidate for inclusion. @@ -16,7 +19,7 @@ We need all of this information in order to determine whether or not the change In order for a rule change to be accepted into ESLint, it must: -1. Adhere to the [Core Rule Guidelines](new-rules#core-rule-guidelines) +1. Adhere to the [Core Rule Guidelines](propose-new-rule#core-rule-guidelines) 1. Have an ESLint team member champion the change 1. Be important enough that rule is deemed incomplete without this change diff --git a/eslint/docs/src/developer-guide/contributing/pull-requests.md b/eslint/docs/src/contribute/pull-requests.md similarity index 90% rename from eslint/docs/src/developer-guide/contributing/pull-requests.md rename to eslint/docs/src/contribute/pull-requests.md index 2eece34..8854ee2 100644 --- a/eslint/docs/src/developer-guide/contributing/pull-requests.md +++ b/eslint/docs/src/contribute/pull-requests.md @@ -1,7 +1,10 @@ --- -title: Pull Requests -layout: doc - +title: Submit a Pull Request +eleventyNavigation: + key: submit pull request + parent: contribute to eslint + title: Submit a Pull Request + order: 9 --- If you want to contribute to an ESLint repo, please use a GitHub pull request. This is the fastest way for us to evaluate your code and to merge it into the code base. Please don't file an issue with snippets of code. Doing so means that we need to manually merge the changes in and update any appropriate tests. That decreases the likelihood that your code is going to get included in a timely manner. Please use pull requests. @@ -10,8 +13,8 @@ If you want to contribute to an ESLint repo, please use a GitHub pull request. T If you'd like to work on a pull request and you've never submitted code before, follow these steps: -1. Set up a [development environment](../development-environment). -1. If you want to implement a breaking change or a change to the core, ensure there's an issue that describes what you're doing and the issue has been accepted. You can create a new issue or just indicate you're [working on an existing issue](working-on-issues). Bug fixes, documentation changes, and other pull requests do not require an issue. +1. Set up a [development environment](./development-environment). +1. If you want to implement a breaking change or a change to the core, ensure there's an issue that describes what you're doing and the issue has been accepted. You can create a new issue or just indicate you're [working on an existing issue](./work-on-issue). Bug fixes, documentation changes, and other pull requests do not require an issue. After that, you're ready to start working on code. @@ -43,14 +46,14 @@ You should do all of your development for the issue in this branch. ### Step 2: Make your changes -Make the changes to the code and tests, following the [code conventions](../code-conventions) as you go. Once you have finished, commit the changes to your branch: +Make the changes to the code and tests, following the [code conventions](./code-conventions) as you go. Once you have finished, commit the changes to your branch: ```shell git add -A git commit ``` -All ESLint projects follow [Conventional Commits](https://www.conventionalcommits.org/) for our commit messages. Here's an example commit message: +All ESLint projects follow [Conventional Commits](https://www.conventionalcommits.org/) for our commit messages. (Note: we don’t support the optional scope in messages.) Here's an example commit message: ```txt tag: Short description of what you did @@ -76,7 +79,7 @@ The `tag` is one of the following: * `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#issue-labels) to determine the best tag. +Use the [labels of the issue you are working on](work-on-issue#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 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`. @@ -120,7 +123,7 @@ With your code ready to go, this is a good time to double-check your submission * Make separate pull requests for unrelated changes. Large pull requests with multiple unrelated changes may be closed without merging. * All changes must be accompanied by tests, even if the feature you're working on previously had no tests. * All user-facing changes must be accompanied by appropriate documentation. -* Follow the [Code Conventions](../code-conventions). +* Follow the [Code Conventions](./code-conventions). ### Step 6: Push your changes @@ -180,7 +183,7 @@ The commit messages in subsequent commits do not need to be in any specific form ### Rebasing -If your code is out-of-date, we might ask you to rebase. That means we want you to apply your changes on top of the latest upstream code. Make sure you have set up a [development environment](../development-environment) and then you can rebase using these commands: +If your code is out-of-date, we might ask you to rebase. That means we want you to apply your changes on top of the latest upstream code. Make sure you have set up a [development environment](./development-environment) and then you can rebase using these commands: ```shell git fetch upstream diff --git a/eslint/docs/src/developer-guide/contributing/reporting-bugs.md b/eslint/docs/src/contribute/report-bugs.md similarity index 61% rename from eslint/docs/src/developer-guide/contributing/reporting-bugs.md rename to eslint/docs/src/contribute/report-bugs.md index fb6ae0f..c1a14b1 100644 --- a/eslint/docs/src/developer-guide/contributing/reporting-bugs.md +++ b/eslint/docs/src/contribute/report-bugs.md @@ -1,11 +1,14 @@ --- -title: Reporting Bugs -layout: doc - +title: Report Bugs +eleventyNavigation: + key: report bugs + parent: contribute to eslint + title: Report Bugs + order: 1 --- -If you think you've found a bug in ESLint, please [create a new issue](https://github.com/eslint/eslint/issues/new/choose) or a [pull request](/docs/developer-guide/contributing/pull-requests) on GitHub. +If you think you've found a bug in ESLint, please [create a new issue](https://github.com/eslint/eslint/issues/new/choose) or a [pull request](pull-requests) on GitHub. Please include as much detail as possible to help us properly address your issue. If we need to triage issues and constantly ask people for more detail, that's time taken away from actually fixing issues. Help us be as efficient as possible by including a lot of detail in your issues. -**Note:** If you just have a question that won't necessarily result in a change to ESLint, such as asking how something works or how to contribute, please use the [mailing list](https://groups.google.com/group/eslint) or [chat](https://eslint.org/chat) instead of filing an issue. +**Note:** If you just have a question that won't necessarily result in a change to ESLint, such as asking how something works or how to contribute, please open a [discussion](https://github.com/eslint/eslint/discussions) or stop by our [Discord server](https://eslint.org/chat) instead of filing an issue. diff --git a/eslint/docs/src/contribute/report-security-vulnerability.md b/eslint/docs/src/contribute/report-security-vulnerability.md new file mode 100644 index 0000000..f68319f --- /dev/null +++ b/eslint/docs/src/contribute/report-security-vulnerability.md @@ -0,0 +1,10 @@ +--- +title: Report a Security Vulnerability +eleventyNavigation: + key: report security vulnerability + parent: contribute to eslint + title: Report a Security Vulnerability + order: 11 +--- + +To report a security vulnerability in ESLint, please use our [create an advisory form](https://github.com/eslint/eslint/security/advisories/new) on GitHub. diff --git a/eslint/docs/src/developer-guide/contributing/changes.md b/eslint/docs/src/contribute/request-change.md similarity index 73% rename from eslint/docs/src/developer-guide/contributing/changes.md rename to eslint/docs/src/contribute/request-change.md index 264d1a9..4e8df68 100644 --- a/eslint/docs/src/developer-guide/contributing/changes.md +++ b/eslint/docs/src/contribute/request-change.md @@ -1,7 +1,10 @@ --- -title: Change Requests -layout: doc - +title: Request a Change +eleventyNavigation: + key: request change + parent: contribute to eslint + title: Request a Change + order: 4 --- If you'd like to request a change to ESLint, please [create a new issue](https://github.com/eslint/eslint/issues/new/choose) on GitHub. Be sure to include the following information: @@ -18,4 +21,4 @@ If you're requesting a change to a rule, it's helpful to include this informatio Please include as much detail as possible to help us properly address your issue. If we need to triage issues and constantly ask people for more detail, that's time taken away from actually fixing issues. Help us be as efficient as possible by including a lot of detail in your issues. -**Note:** If you just have a question that won't necessarily result in a change to ESLint, such as asking how something works or how to contribute, please use the [mailing list](https://groups.google.com/group/eslint) or [chat](https://eslint.org/chat) instead of filing an issue. +**Note:** If you just have a question that won't necessarily result in a change to ESLint, such as asking how something works or how to contribute, please open a [discussion](https://github.com/eslint/eslint/discussions) or stop by our [Discord server](https://eslint.org/chat) instead of filing an issue. diff --git a/eslint/docs/src/developer-guide/unit-tests.md b/eslint/docs/src/contribute/tests.md similarity index 95% rename from eslint/docs/src/developer-guide/unit-tests.md rename to eslint/docs/src/contribute/tests.md index 0c8426f..9ad2550 100644 --- a/eslint/docs/src/developer-guide/unit-tests.md +++ b/eslint/docs/src/contribute/tests.md @@ -1,12 +1,10 @@ --- -title: Unit Tests -layout: doc +title: Run the Tests eleventyNavigation: - key: run the tests - parent: developer guide + key: run tests + parent: contribute to eslint title: Run the Tests - order: 3 - + order: 7 --- Most parts of ESLint have unit tests associated with them. Unit tests are written using [Mocha](https://mochajs.org/) and are required when making contributions to ESLint. You'll find all of the unit tests in the `tests` directory. diff --git a/eslint/docs/src/developer-guide/contributing/working-on-issues.md b/eslint/docs/src/contribute/work-on-issue.md similarity index 85% rename from eslint/docs/src/developer-guide/contributing/working-on-issues.md rename to eslint/docs/src/contribute/work-on-issue.md index 75322e2..6205a37 100644 --- a/eslint/docs/src/developer-guide/contributing/working-on-issues.md +++ b/eslint/docs/src/contribute/work-on-issue.md @@ -1,17 +1,20 @@ --- -title: Working on Issues -layout: doc - +title: Work on Issues +eleventyNavigation: + key: work on issues + parent: contribute to eslint + title: Work on Issues + order: 8 --- Our public [issues tracker](https://github.com/eslint/eslint/issues) lists all of the things we plan on doing as well as suggestions from the community. Before starting to work on an issue, be sure you read through the rest of this page. ## Issue Labels -We use labels to indicate the status of issues. The most complete documentation on the labels is found in the [Maintainer Guide](https://eslint.org/docs/maintainer-guide/issues.html#when-an-issue-is-opened), but most contributors should find the information on this page sufficient. The most important questions that labels can help you, as a contributor, answer are: +We use labels to indicate the status of issues. The most complete documentation on the labels is found in the [Maintain ESLint documentation](../maintain/manage-issues#when-an-issue-is-opened), but most contributors should find the information on this page sufficient. The most important questions that labels can help you, as a contributor, answer are: 1. Is this issue available for me to work on? If you have little or no experience contributing to ESLint, the [`good first issue`](https://github.com/eslint/eslint/labels/good%20first%20issue) label marks appropriate issues. Otherwise, the [`help wanted`](https://github.com/eslint/eslint/labels/help%20wanted) label is an invitation to work on the issue. If you have more experience, you can try working on other issues labeled [`accepted`](https://github.com/eslint/eslint/labels/accepted). Conversely, issues not yet ready to work on are labeled `triage`, `evaluating`, and/or `needs bikeshedding`, and issues that cannot currently be worked on because of something else, such as a bug in a dependency, are labeled `blocked`. -1. What is this issue about? Labels describing the nature of issues include `bug`, `enhancement`, `feature`, `question`, `rule`, `documentation`, `core`, `build`, `cli`, `infrastructure`, `breaking`, and `chore`. These are documented in the [Maintainer Guide](https://eslint.org/docs/maintainer-guide/issues.html#types-of-issues). +1. What is this issue about? Labels describing the nature of issues include `bug`, `enhancement`, `feature`, `question`, `rule`, `documentation`, `core`, `build`, `cli`, `infrastructure`, `breaking`, and `chore`. These are documented in [Maintain ESLint](../maintain/manage-issues#types-of-issues). 1. What is the priority of this issue? Because we have a lot of issues, we prioritize certain issues above others. The following is the list of priorities, from highest to lowest: 1. **Bugs** - problems with the project are actively affecting users. We want to get these resolved as quickly as possible. diff --git a/eslint/docs/src/developer-guide/index.md b/eslint/docs/src/developer-guide/index.md deleted file mode 100644 index a682ac2..0000000 --- a/eslint/docs/src/developer-guide/index.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Developer Guide -layout: doc -eleventyNavigation: - key: developer guide - title: Developer Guide - order: 2 - ---- - -This guide is intended for those who wish to: - -* Contribute code to ESLint -* Create their own rules for ESLint - -In order to work with ESLint as a developer, it's recommended that: - -* You know JavaScript, since ESLint is written in JavaScript. -* You have some familiarity with Node.js, since ESLint runs on it. -* You're comfortable with command-line programs. -* You understand unit tests and why they're important. - -If that sounds like you, then continue reading to get started. - -## Section 1: Get the [Source Code](source-code) - -Before you can get started, you'll need to get a copy of the ESLint source code. This section explains how to do that and a little about the source code structure. - -## Section 2: Set up a [Development Environment](development-environment) - -Developing for ESLint is a bit different than running it on the command line. This section shows you how to set up a development environment and get you ready to write code. - -## Section 3: Run the [Unit Tests](unit-tests) - -There are a lot of unit tests included with ESLint to make sure that we're keeping on top of code quality. This section explains how to run the unit tests. - -## Section 4: [Working with Rules](working-with-rules) - -You're finally ready to start working with rules. You may want to fix an existing rule or create a new one. This section explains how to do all of that. - -## Section 5: [Working with Plugins](working-with-plugins) - -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) - -If you aren't going to use the default parser of ESLint, this section explains about using custom parsers. - -## Section 7: [Node.js API](nodejs-api) - -If you're interested in writing a tool that uses ESLint, then you can use the Node.js API to get programmatic access to functionality. - -## Section 8: [Contributing](contributing/) - -Once you've made changes that you want to share with the community, the next step is to submit those changes back via a pull request. diff --git a/eslint/docs/src/developer-guide/shareable-configs.md b/eslint/docs/src/developer-guide/shareable-configs.md deleted file mode 100644 index a6ef597..0000000 --- a/eslint/docs/src/developer-guide/shareable-configs.md +++ /dev/null @@ -1,219 +0,0 @@ ---- -title: Shareable Configs -layout: doc -eleventyNavigation: - key: shareable configs - parent: developer guide - title: Shareable Configs - order: 8 - ---- - -The configuration that you have in your `.eslintrc` file is an important part of your project, and as such, you may want to share it with other projects or people. Shareable configs allow you to publish your configuration settings on [npm](https://www.npmjs.com/) and have others download and use it in their ESLint projects. - -## Creating a Shareable Config - -Shareable configs are simply npm packages that export a configuration object. To start, [create a Node.js module](https://docs.npmjs.com/getting-started/creating-node-modules) like you normally would. Make sure the module name begins with `eslint-config-`, such as `eslint-config-myconfig`. - -npm [scoped modules](https://docs.npmjs.com/misc/scope) are also supported, by naming or prefixing the module with `@scope/eslint-config`, such as `@scope/eslint-config` or `@scope/eslint-config-myconfig`. - -Create a new `index.js` file and export an object containing your settings: - -```js -module.exports = { - - globals: { - MyGlobal: true - }, - - rules: { - semi: [2, "always"] - } - -}; -``` - -Since `index.js` is just JavaScript, you can optionally read these settings from a file or generate them dynamically. - -## Publishing a Shareable Config - -Once your shareable config is ready, you can [publish to npm](https://docs.npmjs.com/getting-started/publishing-npm-packages) to share with others. We recommend using the `eslint` and `eslintconfig` keywords so others can easily find your module. - -You should declare your dependency on ESLint in `package.json` using the [peerDependencies](https://docs.npmjs.com/files/package.json#peerdependencies) field. The recommended way to declare a dependency for future proof compatibility is with the ">=" range syntax, using the lowest required ESLint version. For example: - -```json -{ - "peerDependencies": { - "eslint": ">= 3" - } -} -``` - -If your shareable config depends on a plugin, you should also specify it as a `peerDependency` (plugins will be loaded relative to the end user's project, so the end user is required to install the plugins they need). However, if your shareable config depends on a third-party parser or another shareable config, you can specify these packages as `dependencies`. - -You can also test your shareable config on your computer before publishing by linking your module globally. Type: - -```bash -npm link -``` - -Then, in your project that wants to use your shareable config, type: - -```bash -npm link eslint-config-myconfig -``` - -Be sure to replace `eslint-config-myconfig` with the actual name of your module. - -## Using a Shareable Config - -Shareable configs are designed to work with the `extends` feature of `.eslintrc` files. Instead of using a file path for the value of `extends`, use your module name. For example: - -```json -{ - "extends": "eslint-config-myconfig" -} -``` - -You can also omit the `eslint-config-` and it will be automatically assumed by ESLint: - -```json -{ - "extends": "myconfig" -} -``` - -### npm scoped modules - -npm [scoped modules](https://docs.npmjs.com/misc/scope) are also supported in a number of ways. - -By using the module name: - -```json -{ - "extends": "@scope/eslint-config" -} -``` - -You can also omit the `eslint-config` and it will be automatically assumed by ESLint: - -```json -{ - "extends": "@scope" -} -``` - -The module name can also be customized, just note that when using [scoped modules](https://docs.npmjs.com/misc/scope) it is not possible to omit the `eslint-config-` prefix. Doing so would result in package naming conflicts, and thus in resolution errors in most of cases. For example a package named `@scope/eslint-config-myconfig` vs `@scope/myconfig`, since both are valid scoped package names, the configuration should be specified as: - -```json -{ - "extends": "@scope/eslint-config-myconfig" -} -``` - -You can override settings from the shareable config by adding them directly into your `.eslintrc` file. - -## Sharing Multiple Configs - -It's possible to share multiple configs in the same npm package. You can specify a default config for the package by following the directions in the first section. You can specify additional configs by simply adding a new file to your npm package and then referencing it from your ESLint config. - -As an example, you can create a file called `my-special-config.js` in the root of your npm package and export a config, such as: - -```js -module.exports = { - rules: { - quotes: [2, "double"] - } -}; -``` - -Then, assuming you're using the package name `eslint-config-myconfig`, you can access the additional config via: - -```json -{ - "extends": "myconfig/my-special-config" -} -``` - -When using [scoped modules](https://docs.npmjs.com/misc/scope) it is not possible to omit the `eslint-config` namespace. Doing so would result in resolution errors as explained above. Assuming the package name is `@scope/eslint-config`, the additional config can be accessed as: - -```json -{ - "extends": "@scope/eslint-config/my-special-config" -} -``` - -Note that you can leave off the `.js` from the filename. In this way, you can add as many additional configs to your package as you'd like. - -**Important:** We strongly recommend always including a default config for your plugin to avoid errors. - -## Local Config File Resolution - -If you need to make multiple configs that can extend from each other and live in different directories, you can create a single shareable config that handles this scenario. - -As an example, let's assume you're using the package name `eslint-config-myconfig` and your package looks something like this: - -```text -myconfig -├── index.js -└─┬ lib - ├── defaults.js - ├── dev.js - ├── ci.js - └─┬ ci - ├── frontend.js - ├── backend.js - └── common.js -``` - -In your `index.js` you can do something like this: - -```js -module.exports = require('./lib/ci.js'); -``` - -Now inside your package you have `/lib/defaults.js`, which contains: - -```js -module.exports = { - rules: { - 'no-console': 1 - } -}; -``` - -Inside your `/lib/ci.js` you have - -```js -module.exports = require('./ci/backend'); -``` - -Inside your `/lib/ci/common.js` - -```js -module.exports = { - rules: { - 'no-alert': 2 - }, - extends: 'myconfig/lib/defaults' -}; -``` - -Despite being in an entirely different directory, you'll see that all `extends` must use the full package path to the config file you wish to extend. - -Now inside your `/lib/ci/backend.js` - -```js -module.exports = { - rules: { - 'no-console': 1 - }, - extends: 'myconfig/lib/ci/common' -}; -``` - -In the last file, you'll once again see that to properly resolve your config, you'll need include the full package path. - -## Further Reading - -* [npm Developer Guide](https://docs.npmjs.com/misc/developers) diff --git a/eslint/docs/src/developer-guide/source-code.md b/eslint/docs/src/developer-guide/source-code.md deleted file mode 100644 index ec3d3ce..0000000 --- a/eslint/docs/src/developer-guide/source-code.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: Source Code -layout: doc -eleventyNavigation: - key: getting the source code - parent: developer guide - title: Getting the Source Code - order: 1 - ---- - -ESLint is hosted at [GitHub](https://github.com/eslint/eslint) and uses [Git](https://git-scm.com/) for source control. In order to obtain the source code, you must first install Git on your system. Instructions for installing and setting up Git can be found at [https://help.github.com/articles/set-up-git/](https://help.github.com/articles/set-up-git/). - -If you simply want to create a local copy of the source to play with, you can clone the main repository using this command: - -```shell -git clone git://github.com/eslint/eslint.git -``` - -If you're planning on contributing to ESLint, then it's a good idea to fork the repository. You can find instructions for forking a repository at [https://help.github.com/articles/fork-a-repo/](https://help.github.com/articles/fork-a-repo/). After forking the ESLint repository, you'll want to create a local copy of your fork. - -## Start Developing - -Before you can get started developing, you'll need to have a couple of things installed: - -* [Node.JS](https://nodejs.org) -* [npm](https://www.npmjs.com/) - -Once you have a local copy and have Node.JS and npm installed, you'll need to install the ESLint dependencies: - -```shell -cd eslint -npm install -``` - -Now when you run `eslint`, it will be running your local copy and showing your changes. - -**Note:** It's a good idea to re-run `npm install` whenever you pull from the main repository to ensure you have the latest development dependencies. - -## Directory structure - -The ESLint directory and file structure is as follows: - -* `bin` - executable files that are available when ESLint is installed -* `conf` - default configuration information -* `docs` - documentation for the project -* `lib` - contains the source code - * `formatters` - all source files defining formatters - * `rules` - all source files defining rules -* `tests` - the main unit test folder - * `lib` - tests for the source code - * `formatters` - tests for the formatters - * `rules` - tests for the rules diff --git a/eslint/docs/src/developer-guide/working-with-custom-parsers.md b/eslint/docs/src/developer-guide/working-with-custom-parsers.md deleted file mode 100644 index 62707b3..0000000 --- a/eslint/docs/src/developer-guide/working-with-custom-parsers.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: Working with Custom Parsers -layout: doc -eleventyNavigation: - key: working with custom parsers - parent: developer guide - title: Working with Custom Parsers - order: 7 - ---- - -If you want to use your own parser and provide additional capabilities for your rules, you can specify your own custom parser. If a `parseForESLint` method is exposed on the parser, this method will be used to parse the code. Otherwise, the `parse` method will be used. Both methods should take in the source code as the first argument, and an optional configuration object as the second argument (provided as `parserOptions` in a config file). The `parse` method should simply return the AST. The `parseForESLint` method should return an object that contains the required property `ast` and optional properties `services`, `scopeManager`, and `visitorKeys`. - -* `ast` should contain the AST. -* `services` can contain any parser-dependent services (such as type checkers for nodes). The value of the `services` property is available to rules as `context.parserServices`. Default is an empty object. -* `scopeManager` can be a [ScopeManager](./scope-manager-interface) object. Custom parsers can use customized scope analysis for experimental/enhancement syntaxes. Default is the `ScopeManager` object which is created by [eslint-scope](https://github.com/eslint/eslint-scope). - * Support for `scopeManager` was added in ESLint v4.14.0. ESLint versions which support `scopeManager` will provide an `eslintScopeManager: true` property in `parserOptions`, which can be used for feature detection. -* `visitorKeys` can be an object to customize AST traversal. The keys of the object are the type of AST nodes. Each value is an array of the property names which should be traversed. Default is [KEYS of `eslint-visitor-keys`](https://github.com/eslint/eslint-visitor-keys#evkkeys). - * Support for `visitorKeys` was added in ESLint v4.14.0. ESLint versions which support `visitorKeys` will provide an `eslintVisitorKeys: true` property in `parserOptions`, which can be used for feature detection. - -You can find an ESLint parser project [here](https://github.com/typescript-eslint/typescript-eslint). - -```json -{ - "parser": "./path/to/awesome-custom-parser.js" -} -``` - -```javascript -var espree = require("espree"); -// awesome-custom-parser.js -exports.parseForESLint = function(code, options) { - return { - ast: espree.parse(code, options), - services: { - foo: function() { - console.log("foo"); - } - }, - scopeManager: null, - visitorKeys: null - }; -}; - -``` - -## The AST specification - -The AST that custom parsers should create is based on [ESTree](https://github.com/estree/estree). The AST requires some additional properties about detail information of the source code. - -### All nodes: - -All nodes must have `range` property. - -* `range` (`number[]`) is an array of two numbers. Both numbers are a 0-based index which is the position in the array of source code characters. The first is the start position of the node, the second is the end position of the node. `code.slice(node.range[0], node.range[1])` must be the text of the node. This range does not include spaces/parentheses which are around the node. -* `loc` (`SourceLocation`) must not be `null`. [The `loc` property is defined as nullable by ESTree](https://github.com/estree/estree/blob/25834f7247d44d3156030f8e8a2d07644d771fdb/es5.md#node-objects), but ESLint requires this property. On the other hand, `SourceLocation#source` property can be `undefined`. ESLint does not use the `SourceLocation#source` property. - -The `parent` property of all nodes must be rewritable. ESLint sets each node's `parent` property to its parent node while traversing, before any rules have access to the AST. - -### The `Program` node: - -The `Program` node must have `tokens` and `comments` properties. Both properties are an array of the below Token interface. - -```ts -interface Token { - type: string; - loc: SourceLocation; - range: [number, number]; // See "All nodes:" section for details of `range` property. - value: string; -} -``` - -* `tokens` (`Token[]`) is the array of tokens which affect the behavior of programs. Arbitrary spaces can exist between tokens, so rules check the `Token#range` to detect spaces between tokens. This must be sorted by `Token#range[0]`. -* `comments` (`Token[]`) is the array of comment tokens. This must be sorted by `Token#range[0]`. - -The range indexes of all tokens and comments must not overlap with the range of other tokens and comments. - -### The `Literal` node: - -The `Literal` node must have `raw` property. - -* `raw` (`string`) is the source code of this literal. This is the same as `code.slice(node.range[0], node.range[1])`. diff --git a/eslint/docs/src/developer-guide/working-with-plugins.md b/eslint/docs/src/developer-guide/working-with-plugins.md deleted file mode 100644 index cec3f88..0000000 --- a/eslint/docs/src/developer-guide/working-with-plugins.md +++ /dev/null @@ -1,249 +0,0 @@ ---- -title: Working with Plugins -layout: doc -eleventyNavigation: - key: working with plugings - parent: developer guide - title: Working with Plugins - order: 5 - ---- - -Each plugin is an npm module with a name in the format of `eslint-plugin-`, such as `eslint-plugin-jquery`. You can also use scoped packages in the format of `@/eslint-plugin-` such as `@jquery/eslint-plugin-jquery` or even `@/eslint-plugin` such as `@jquery/eslint-plugin`. - -## Create a Plugin - -The easiest way to start creating a plugin is to use the [Yeoman generator](https://www.npmjs.com/package/generator-eslint). The generator will guide you through setting up the skeleton of a plugin. - -### Rules in Plugins - -Plugins can expose additional rules for use in ESLint. To do so, the plugin must export a `rules` object containing a key-value mapping of rule ID to rule. The rule ID does not have to follow any naming convention (so it can just be `dollar-sign`, for instance). - -```js -module.exports = { - rules: { - "dollar-sign": { - create: function (context) { - // rule implementation ... - } - } - } -}; -``` - -To use the rule in ESLint, you would use the unprefixed plugin name, followed by a slash, followed by the rule name. So if this plugin were named `eslint-plugin-myplugin`, then in your configuration you'd refer to the rule by the name `myplugin/dollar-sign`. Example: `"rules": {"myplugin/dollar-sign": 2}`. - -### Environments in Plugins - -Plugins can expose additional environments for use in ESLint. To do so, the plugin must export an `environments` object. The keys of the `environments` object are the names of the different environments provided and the values are the environment settings. For example: - -```js -module.exports = { - environments: { - jquery: { - globals: { - $: false - } - } - } -}; -``` - -There's a `jquery` environment defined in this plugin. To use the environment in ESLint, you would use the unprefixed plugin name, followed by a slash, followed by the environment name. So if this plugin were named `eslint-plugin-myplugin`, then you would set the environment in your configuration to be `"myplugin/jquery"`. - -Plugin environments can define the following objects: - -1. `globals` - acts the same `globals` in a configuration file. The keys are the names of the globals and the values are `true` to allow the global to be overwritten and `false` to disallow. -1. `parserOptions` - acts the same as `parserOptions` in a configuration file. - -### Processors in Plugins - -You can also create plugins that would tell ESLint how to process files other than JavaScript. In order to create a processor, the object that is exported from your module has to conform to the following interface: - -```js -module.exports = { - processors: { - "processor-name": { - // takes text of the file and filename - preprocess: function(text, filename) { - // here, you can strip out any non-JS content - // and split into multiple strings to lint - - return [ // return an array of code blocks to lint - { text: code1, filename: "0.js" }, - { text: code2, filename: "1.js" }, - ]; - }, - - // takes a Message[][] and filename - postprocess: function(messages, filename) { - // `messages` argument contains two-dimensional array of Message objects - // where each top-level array item contains array of lint messages related - // to the text that was returned in array from preprocess() method - - // you need to return a one-dimensional array of the messages you want to keep - return [].concat(...messages); - }, - - supportsAutofix: true // (optional, defaults to false) - } - } -}; -``` - -**The `preprocess` method** takes the file contents and filename as arguments, and returns an array of code blocks to lint. The code blocks will be linted separately but still be registered to the filename. - -A code block has two properties `text` and `filename`; the `text` property is the content of the block and the `filename` property is the name of the block. Name of the block can be anything, but should include the file extension, that would tell the linter how to process the current block. The linter will check [`--ext` CLI option](../user-guide/command-line-interface#--ext) to see if the current block should be linted, and resolve `overrides` configs to check how to process the current block. - -It's up to the plugin to decide if it needs to return just one part, or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts, but for `.md` file where each JavaScript block might be independent, you can return multiple items. - -**The `postprocess` method** takes a two-dimensional array of arrays of lint messages and the filename. Each item in the input array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the locations of all errors to correspond to locations in the original, unprocessed code, and aggregate them into a single flat array and return it. - -Reported problems have the following location information: - -```typescript -{ - line: number, - column: number, - - endLine?: number, - endColumn?: number -} -``` - -By default, ESLint will not perform autofixes when a processor is used, even when the `--fix` flag is enabled on the command line. To allow ESLint to autofix code when using your processor, you should take the following additional steps: - -1. Update the `postprocess` method to additionally transform the `fix` property of reported problems. All autofixable problems will have a `fix` property, which is an object with the following schema: - - ```js - { - range: [number, number], - text: string - } - ``` - - The `range` property contains two indexes in the code, referring to the start and end location of a contiguous section of text that will be replaced. The `text` property refers to the text that will replace the given range. - - In the initial list of problems, the `fix` property will refer to a fix in the processed JavaScript. The `postprocess` method should transform the object to refer to a fix in the original, unprocessed file. - -2. Add a `supportsAutofix: true` property to the processor. - -You can have both rules and processors in a single plugin. You can also have multiple processors in one plugin. -To support multiple extensions, add each one to the `processors` element and point them to the same object. - -#### Specifying Processor in Config Files - -To use a processor, add its ID to a `processor` section in the config file. Processor ID is a concatenated string of plugin name and processor name with a slash as a separator. This can also be added to a `overrides` section of the config, to specify which processors should handle which files. - -For example: - -```yml -plugins: - - a-plugin -overrides: - - files: "*.md" - processor: a-plugin/markdown -``` - -See [Specifying Processor](../user-guide/configuring/plugins#specifying-processor) for details. - -#### File Extension-named Processor - -If a processor name starts with `.`, ESLint handles the processor as a **file extension-named processor** especially and applies the processor to the kind of files automatically. People don't need to specify the file extension-named processors in their config files. - -For example: - -```js -module.exports = { - processors: { - // This processor will be applied to `*.md` files automatically. - // Also, people can use this processor as "plugin-id/.md" explicitly. - ".md": { - preprocess(text, filename) { /* ... */ }, - postprocess(messageLists, filename) { /* ... */ } - } - } -} -``` - -### Configs in Plugins - -You can bundle configurations inside a plugin by specifying them under the `configs` key. This can be useful when you want to provide not just code style, but also some custom rules to support it. Multiple configurations are supported per plugin. Note that it is not possible to specify a default configuration for a given plugin and that users must specify in their configuration file when they want to use one. - -```js -// eslint-plugin-myPlugin - -module.exports = { - configs: { - myConfig: { - plugins: ["myPlugin"], - env: ["browser"], - rules: { - semi: "error", - "myPlugin/my-rule": "error", - "eslint-plugin-myPlugin/another-rule": "error" - } - }, - myOtherConfig: { - plugins: ["myPlugin"], - env: ["node"], - rules: { - "myPlugin/my-rule": "off", - "eslint-plugin-myPlugin/another-rule": "off", - "eslint-plugin-myPlugin/yet-another-rule": "error" - } - } - } -}; -``` - -If the example plugin above were called `eslint-plugin-myPlugin`, the `myConfig` and `myOtherConfig` configurations would then be usable by extending off of `"plugin:myPlugin/myConfig"` and `"plugin:myPlugin/myOtherConfig"`, respectively. - -```json -{ - "extends": ["plugin:myPlugin/myConfig"] -} - -``` - -**Note:** Please note that configuration will not enable any of the plugin's rules by default, and instead should be treated as a standalone config. This means that you must specify your plugin name in the `plugins` array as well as any rules you want to enable that are part of the plugin. Any plugin rules must be prefixed with the short or long plugin name. See [Configuring Plugins](../user-guide/configuring/plugins#configuring-plugins) for more information. - -### Peer Dependency - -To make clear that the plugin requires ESLint to work correctly you have to declare ESLint as a `peerDependency` in your `package.json`. -The plugin support was introduced in ESLint version `0.8.0`. Ensure the `peerDependency` points to ESLint `0.8.0` or later. - -```json -{ - "peerDependencies": { - "eslint": ">=0.8.0" - } -} -``` - -### Testing - -ESLint provides the [`RuleTester`](/docs/developer-guide/nodejs-api#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. - -Recommended keywords: - -* `eslint` -* `eslintplugin` - -Add these keywords into your `package.json` file to make it easy for others to find. - -## Further Reading - -* [npm Developer Guide](https://docs.npmjs.com/misc/developers) diff --git a/eslint/docs/src/developer-guide/working-with-rules.md b/eslint/docs/src/developer-guide/working-with-rules.md deleted file mode 100644 index 16412b5..0000000 --- a/eslint/docs/src/developer-guide/working-with-rules.md +++ /dev/null @@ -1,777 +0,0 @@ ---- -title: Working with Rules -layout: doc -eleventyNavigation: - key: working with rules - parent: developer guide - title: Working with Rules - order: 4 - ---- - -**Note:** This page covers the most recent rule format for ESLint >= 3.0.0. There is also a [deprecated rule format](./working-with-rules-deprecated). - -Each rule in ESLint has three files named with its identifier (for example, `no-extra-semi`). - -* in the `lib/rules` directory: a source file (for example, `no-extra-semi.js`) -* in the `tests/lib/rules` directory: a test file (for example, `no-extra-semi.js`) -* in the `docs/src/rules` directory: a Markdown documentation file (for example, `no-extra-semi.md`) - -**Important:** If you submit a **core** rule to the ESLint repository, you **must** follow some conventions explained below. - -Here is the basic format of the source file for a rule: - -```js -/** - * @fileoverview Rule to disallow unnecessary semicolons - * @author Nicholas C. Zakas - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ - -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "disallow unnecessary semicolons", - recommended: true, - url: "https://eslint.org/docs/rules/no-extra-semi" - }, - fixable: "code", - schema: [] // no options - }, - create: function(context) { - return { - // callback functions - }; - } -}; -``` - -## Rule Basics - -The source file for a rule exports an object with the following properties. - -`meta` (object) contains metadata for the rule: - -* `type` (string) indicates the type of rule, which is one of `"problem"`, `"suggestion"`, or `"layout"`: - * `"problem"` means the rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. - * `"suggestion"` means the rule is identifying something that could be done in a better way but no errors will occur if the code isn't changed. - * `"layout"` means the rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren't specified in the AST. - -* `docs` (object) is required for core rules of ESLint: - - * `description` (string) provides the short description of the rule in the [rules index](../rules/) - * `recommended` (boolean) is whether the `"extends": "eslint:recommended"` property in a [configuration file](../user-guide/configuring/configuration-files#extending-configuration-files) enables the rule - * `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#--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#configuring-rules) - -* `deprecated` (boolean) indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated. - -* `replacedBy` (array) in the case of a deprecated rule, specifies replacement rule(s) - -`create` (function) returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code: - -* if a key is a node type or a [selector](./selectors), ESLint calls that **visitor** function while going **down** the tree -* if a key is a node type or a [selector](./selectors) plus `:exit`, ESLint calls that **visitor** function while going **up** the tree -* if a key is an event name, ESLint calls that **handler** function for [code path analysis](./code-path-analysis) - -A rule can use the current node and its surrounding tree to report or fix problems. - -Here are methods for the [array-callback-return](../rules/array-callback-return) rule: - -```js -function checkLastSegment (node) { - // report problem for function if last code path segment is reachable -} - -module.exports = { - meta: { ... }, - create: function(context) { - // declare the state of the rule - return { - ReturnStatement: function(node) { - // at a ReturnStatement node while going down - }, - // at a function expression node while going up: - "FunctionExpression:exit": checkLastSegment, - "ArrowFunctionExpression:exit": checkLastSegment, - onCodePathStart: function (codePath, node) { - // at the start of analyzing a code path - }, - onCodePathEnd: function(codePath, node) { - // at the end of analyzing a code path - } - }; - } -}; -``` - -## The Context Object - -The `context` object contains additional functionality that is helpful for rules to do their jobs. As the name implies, the `context` object contains information that is relevant to the context of the rule. The `context` object has the following properties: - -* `parserOptions` - the parser options configured for this run (more details [here](../user-guide/configuring/language-options#specifying-parser-options)). -* `id` - the rule ID. -* `options` - an array of the [configured options](/docs/user-guide/configuring/rules#configuring-rules) for this rule. This array does not include the rule severity. For more information, see [here](#contextoptions). -* `settings` - the [shared settings](/docs/user-guide/configuring/configuration-files#adding-shared-settings) from configuration. -* `parserPath` - the name of the `parser` from configuration. -* `parserServices` - an object containing parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) - -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#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#variable-interface) declared by the given node. This information can be used to track references to variables. - * If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. - * If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. - * If the node is a `FunctionDeclaration` or `FunctionExpression`, the variable for the function name is returned, in addition to variables for the function parameters. - * If the node is an `ArrowFunctionExpression`, variables for the parameters are returned. - * If the node is a `ClassDeclaration` or a `ClassExpression`, the variable for the class name is returned. - * If the node is a `CatchClause`, the variable for the exception is returned. - * If the node is an `ImportDeclaration`, variables for all of its specifiers are returned. - * If the node is an `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`, the declared variable is returned. - * Otherwise, if the node does not declare any variables, an empty array is returned. -* `getFilename()` - returns the filename associated with the source. -* `getPhysicalFilename()` - when linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `—stdin-filename` or `` if not specified. -* `getScope()` - returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. -* `getSourceCode()` - returns a [`SourceCode`](#contextgetsourcecode) object that you can use to work with the source that was passed to ESLint. -* `markVariableAsUsed(name)` - marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. -* `report(descriptor)` - reports a problem in the code (see the [dedicated section](#contextreport)). - -**Note:** Earlier versions of ESLint supported additional methods on the `context` object. Those methods were removed in the new format and should not be relied upon. - -### context.getScope() - -This method returns the scope which has the following types: - -| AST Node Type | Scope Type | -|:--------------------------|:-----------| -| `Program` | `global` | -| `FunctionDeclaration` | `function` | -| `FunctionExpression` | `function` | -| `ArrowFunctionExpression` | `function` | -| `ClassDeclaration` | `class` | -| `ClassExpression` | `class` | -| `BlockStatement` ※1 | `block` | -| `SwitchStatement` ※1 | `switch` | -| `ForStatement` ※2 | `for` | -| `ForInStatement` ※2 | `for` | -| `ForOfStatement` ※2 | `for` | -| `WithStatement` | `with` | -| `CatchClause` | `catch` | -| others | ※3 | - -**※1** Only if the configured parser provided the block-scope feature. The default parser provides the block-scope feature if `parserOptions.ecmaVersion` is not less than `6`.
    -**※2** Only if the `for` statement defines the iteration variable as a block-scoped variable (E.g., `for (let i = 0;;) {}`).
    -**※3** The scope of the closest ancestor node which has own scope. If the closest ancestor node has multiple scopes then it chooses the innermost scope (E.g., the `Program` node has a `global` scope and a `module` scope if `Program#sourceType` is `"module"`. The innermost scope is the `module` scope.). - -The returned value is a [`Scope` object](scope-manager-interface) defined by the `eslint-scope` package. The `Variable` objects of global variables have some additional properties. - -* `variable.writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only. -* `variable.eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file. -* `variable.eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments. -* `variable.eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments. - -### context.report() - -The main method you'll use is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties: - -* `message` - the problem message. -* `node` - (optional) the AST node related to the problem. If present and `loc` is not specified, then the starting location of the node is used as the location of the problem. -* `loc` - (optional) an object specifying the location of the problem. If both `loc` and `node` are specified, then the location is used from `loc` instead of `node`. - * `start` - An object of the start location. - * `line` - the 1-based line number at which the problem occurred. - * `column` - the 0-based column number at which the problem occurred. - * `end` - An object of the end location. - * `line` - the 1-based line number at which the problem occurred. - * `column` - the 0-based column number at which the problem occurred. -* `data` - (optional) [placeholder](#using-message-placeholders) data for `message`. -* `fix` - (optional) a function that applies a [fix](#applying-fixes) to resolve the problem. - -Note that at least one of `node` or `loc` is required. - -The simplest example is to use just `node` and `message`: - -```js -context.report({ - node: node, - message: "Unexpected identifier" -}); -``` - -The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node. - -### Using message placeholders - -You can also use placeholders in the message and provide `data`: - -```js -{% raw %} -context.report({ - node: node, - message: "Unexpected identifier: {{ identifier }}", - data: { - identifier: node.name - } -}); -{% endraw %} -``` - -Note that leading and trailing whitespace is optional in message parameters. - -The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node. - -### `messageId`s - -Instead of typing out messages in both the `context.report()` call and your tests, you can use `messageId`s instead. - -This allows you to avoid retyping error messages. It also prevents errors reported in different sections of your rule from having out-of-date messages. - -```js -{% raw %} -// in your rule -module.exports = { - meta: { - messages: { - avoidName: "Avoid using variables named '{{ name }}'" - } - }, - create(context) { - return { - Identifier(node) { - if (node.name === "foo") { - context.report({ - node, - messageId: "avoidName", - data: { - name: "foo", - } - }); - } - } - }; - } -}; - -// in the file to lint: - -var foo = 2; -// ^ error: Avoid using variables named 'foo' - -// In your tests: -var rule = require("../../../lib/rules/my-rule"); -var RuleTester = require("eslint").RuleTester; - -var ruleTester = new RuleTester(); -ruleTester.run("my-rule", rule, { - valid: ["bar", "baz"], - invalid: [ - { - code: "foo", - errors: [ - { - messageId: "avoidName" - } - ] - } - ] -}); -{% endraw %} -``` - -### Applying Fixes - -If you'd like ESLint to attempt to fix the problem you're reporting, you can do so by specifying the `fix` function when using `context.report()`. The `fix` function receives a single argument, a `fixer` object, that you can use to apply a fix. For example: - -```js -context.report({ - node: node, - message: "Missing semicolon", - fix: function(fixer) { - return fixer.insertTextAfter(node, ";"); - } -}); -``` - -Here, the `fix()` function is used to insert a semicolon after the node. Note that a fix is not immediately applied, and may not be applied at all if there are conflicts with other fixes. After applying fixes, ESLint will run all of the enabled rules again on the fixed code, potentially applying more fixes. This process will repeat up to 10 times, or until no more fixable problems are found. Afterwards, any remaining problems will be reported as usual. - -**Important:** The `meta.fixable` property is mandatory for fixable rules. ESLint will throw an error if a rule that implements `fix` functions does not [export](#rule-basics) the `meta.fixable` property. - -The `fixer` object has the following methods: - -* `insertTextAfter(nodeOrToken, text)` - inserts text after the given node or token -* `insertTextAfterRange(range, text)` - inserts text after the given range -* `insertTextBefore(nodeOrToken, text)` - inserts text before the given node or token -* `insertTextBeforeRange(range, text)` - inserts text before the given range -* `remove(nodeOrToken)` - removes the given node or token -* `removeRange(range)` - removes text in the given range -* `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: - -* A `fixing` object. -* An array which includes `fixing` objects. -* An iterable object which enumerates `fixing` objects. Especially, the `fix()` function can be a generator. - -If you make a `fix()` function which returns multiple `fixing` objects, those `fixing` objects must not be overlapped. - -Best practices for fixes: - -1. Avoid any fixes that could change the runtime behavior of code and cause it to stop working. -1. Make fixes as small as possible. Fixes that are unnecessarily large could conflict with other fixes, and prevent them from being applied. -1. Only make one fix per message. This is enforced because you must return the result of the fixer operation from `fix()`. -1. Since all rules are run again after the initial round of fixes is applied, it's not necessary for a rule to check whether the code style of a fix will cause errors to be reported by another rule. - * For example, suppose a fixer would like to surround an object key with quotes, but it's not sure whether the user would prefer single or double quotes. - - ```js - ({ foo : 1 }) - - // should get fixed to either - - ({ 'foo': 1 }) - - // or - - ({ "foo": 1 }) - ``` - - * This fixer can just select a quote type arbitrarily. If it guesses wrong, the resulting code will be automatically reported and fixed by the [`quotes`](/docs/rules/quotes) rule. - -Note: Making fixes as small as possible is a best practice, but in some cases it may be correct to extend the range of the fix in order to intentionally prevent other rules from making fixes in a surrounding range in the same pass. For instance, if replacement text declares a new variable, it can be useful to prevent other changes in the scope of the variable as they might cause name collisions. - -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. - -In order to provide suggestions, use the `suggest` key in the report argument with an array of suggestion objects. The suggestion objects represent individual suggestions that could be applied and require either a `desc` key string that describes what applying the suggestion would do or a `messageId` key (see [below](#suggestion-messageids)), and a `fix` key that is a function defining the suggestion result. This `fix` function follows the same API as regular fixes (described above in [applying fixes](#applying-fixes)). - -```js -{% raw %} -context.report({ - node: node, - message: "Unnecessary escape character: \\{{character}}.", - data: { character }, - suggest: [ - { - desc: "Remove the `\\`. This maintains the current functionality.", - fix: function(fixer) { - return fixer.removeRange(range); - } - }, - { - desc: "Replace the `\\` with `\\\\` to include the actual backslash character.", - fix: function(fixer) { - return fixer.insertTextBeforeRange(range, "\\"); - } - } - ] -}); -{% 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: - -1. Don't try to do too much and suggest large refactors that could introduce a lot of breaking changes. -1. As noted above, don't try to conform to user-defined styles. - -Suggestions are intended to provide fixes. ESLint will automatically remove the whole suggestion from the linting output if the suggestion's `fix` function returned `null` or an empty array/sequence. - -#### Suggestion `messageId`s - -Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageids)). Here is an example of how to use it in a rule: - -```js -{% raw %} -module.exports = { - meta: { - messages: { - 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) { - // ... - context.report({ - node: node, - messageId: 'unnecessaryEscape', - data: { character }, - suggest: [ - { - messageId: "removeEscape", - fix: function(fixer) { - return fixer.removeRange(range); - } - }, - { - messageId: "escapeBackslash", - fix: function(fixer) { - return fixer.insertTextBeforeRange(range, "\\"); - } - } - ] - }); - } -}; -{% endraw %} -``` - -#### Placeholders in suggestion messages - -You can also use placeholders in the suggestion message. This works the same way as placeholders for the overall error (see [using message placeholders](#using-message-placeholders)). - -Please note that you have to provide `data` on the suggestion's object. Suggestion messages cannot use properties from the overall error's `data`. - -```js -{% raw %} -module.exports = { - meta: { - messages: { - unnecessaryEscape: "Unnecessary escape character: \\{{character}}.", - removeEscape: "Remove `\\` before {{character}}.", - }, - hasSuggestions: true - }, - create: function(context) { - // ... - context.report({ - node: node, - messageId: "unnecessaryEscape", - data: { character }, // data for the unnecessaryEscape overall message - suggest: [ - { - messageId: "removeEscape", - data: { character }, // data for the removeEscape suggestion message - fix: function(fixer) { - return fixer.removeRange(range); - } - } - ] - }); - } -}; -{% endraw %} -``` - -### context.options - -Some rules require options in order to function correctly. These options appear in configuration (`.eslintrc`, command line, or in comments). For example: - -```json -{ - "quotes": ["error", "double"] -} -``` - -The `quotes` rule in this example has one option, `"double"` (the `error` is the error level). You can retrieve the options for a rule by using `context.options`, which is an array containing every configured option for the rule. In this case, `context.options[0]` would contain `"double"`: - -```js -module.exports = { - create: function(context) { - var isDouble = (context.options[0] === "double"); - - // ... - } -}; -``` - -Since `context.options` is just an array, you can use it to determine how many options have been passed as well as retrieving the actual options themselves. Keep in mind that the error level is not part of `context.options`, as the error level cannot be known or modified from inside a rule. - -When using options, make sure that your rule has some logical defaults in case the options are not provided. - -### context.getSourceCode() - -The `SourceCode` object is the main object for getting more information about the source code being linted. You can retrieve the `SourceCode` object at any time by using the `getSourceCode()` method: - -```js -module.exports = { - create: function(context) { - var sourceCode = context.getSourceCode(); - - // ... - } -}; -``` - -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. -* `getCommentsBefore(nodeOrToken)` - returns an array of comment tokens that occur directly before the given node or token. -* `getCommentsAfter(nodeOrToken)` - returns an array of comment tokens that occur directly after the given node or token. -* `getCommentsInside(node)` - returns an array of all comment tokens inside a given node. -* `isSpaceBetween(nodeOrToken, nodeOrToken)` - returns true if there is a whitespace character between the two tokens or, if given a node, the last token of the first node and the first token of the second node. -* `getFirstToken(node, skipOptions)` - returns the first token representing the given node. -* `getFirstTokens(node, countOptions)` - returns the first `count` tokens representing the given node. -* `getLastToken(node, skipOptions)` - returns the last token representing the given node. -* `getLastTokens(node, countOptions)` - returns the last `count` tokens representing the given node. -* `getTokenAfter(nodeOrToken, skipOptions)` - returns the first token after the given node or token. -* `getTokensAfter(nodeOrToken, countOptions)` - returns `count` tokens after the given node or token. -* `getTokenBefore(nodeOrToken, skipOptions)` - returns the first token before the given node or token. -* `getTokensBefore(nodeOrToken, countOptions)` - returns `count` tokens before the given node or token. -* `getFirstTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)` - returns the first token between two nodes or tokens. -* `getFirstTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)` - returns the first `count` tokens between two nodes or tokens. -* `getLastTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)` - returns the last token between two nodes or tokens. -* `getLastTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)` - returns the last `count` tokens between two nodes or tokens. -* `getTokens(node)` - returns all tokens for the given node. -* `getTokensBetween(nodeOrToken1, nodeOrToken2)` - returns all tokens between two nodes. -* `getTokenByRangeStart(index, rangeOptions)` - returns the token whose range starts at the given index in the source. -* `getNodeByRangeIndex(index)` - returns the deepest node in the AST containing the given source index. -* `getLocFromIndex(index)` - returns an object with `line` and `column` properties, corresponding to the location of the given source index. `line` is 1-based and `column` is 0-based. -* `getIndexFromLoc(loc)` - returns the index of a given location in the source code, where `loc` is an object with a 1-based `line` key and a 0-based `column` key. -* `commentsExistBetween(nodeOrToken1, nodeOrToken2)` - returns `true` if comments exist between two nodes. - -`skipOptions` is an object which has 3 properties; `skip`, `includeComments`, and `filter`. Default is `{skip: 0, includeComments: false, filter: null}`. - -* `skip` is a positive integer, the number of skipping tokens. If `filter` option is given at the same time, it doesn't count filtered tokens as skipped. -* `includeComments` is a boolean value, the flag to include comment tokens into the result. -* `filter` is a function which gets a token as the first argument, if the function returns `false` then the result excludes the token. - -`countOptions` is an object which has 3 properties; `count`, `includeComments`, and `filter`. Default is `{count: 0, includeComments: false, filter: null}`. - -* `count` is a positive integer, the maximum number of returning tokens. -* `includeComments` is a boolean value, the flag to include comment tokens into the result. -* `filter` is a function which gets a token as the first argument, if the function returns `false` then the result excludes the token. - -`rangeOptions` is an object which has 1 property: `includeComments`. - -* `includeComments` is a boolean value, the flag to include comment tokens into the result. - -There are also some properties you can access: - -* `hasBOM` - the flag to indicate whether or not the source code has Unicode BOM. -* `text` - the full text of the code being linted. Unicode BOM has been stripped from this text. -* `ast` - the `Program` node of the AST for the code being linted. -* `scopeManager` - the [ScopeManager](./scope-manager-interface#scopemanager-interface) object of the code. -* `visitorKeys` - the visitor keys to traverse this AST. -* `lines` - an array of lines, split according to the specification's definition of line breaks. - -You should use a `SourceCode` object whenever you need to get more information about the code being linted. - -#### Deprecated - -Please note that the following methods have been deprecated and will be removed in a future version of ESLint: - -* `getComments()` - replaced by `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` -* `getTokenOrCommentBefore()` - replaced by `getTokenBefore()` with the `{ includeComments: true }` option -* `getTokenOrCommentAfter()` - replaced by `getTokenAfter()` with the `{ includeComments: true }` option -* `isSpaceBetweenTokens()` - replaced by `isSpaceBetween()` -* `getJSDocComment()` - -### Options Schemas - -Rules may export a `schema` property, which is a [JSON schema](https://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. - -There are two formats for a rule's exported `schema`. The first is a full JSON Schema object describing all possible options the rule accepts, including the rule's error level as the first argument and any optional arguments thereafter. - -However, to simplify schema creation, rules may also export an array of schemas for each optional positional argument, and ESLint will automatically validate the required error level first. For example, the `yoda` rule accepts a primary mode argument, as well as an extra options object with named properties. - -```js -// "yoda": [2, "never", { "exceptRange": true }] -module.exports = { - meta: { - schema: [ - { - "enum": ["always", "never"] - }, - { - "type": "object", - "properties": { - "exceptRange": { - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - }, -}; -``` - -In the preceding example, the error level is assumed to be the first argument. It is followed by the first optional argument, a string which may be either `"always"` or `"never"`. The final optional argument is an object, which may have a Boolean property named `exceptRange`. - -To learn more about JSON Schema, we recommend looking at some examples in [website](https://json-schema.org/learn/) to start, and also reading [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) (a free ebook). - -**Note:** Currently you need to use full JSON Schema object rather than array in case your schema has references ($ref), because in case of array format ESLint transforms this array into a single schema without updating references that makes them incorrect (they are ignored). - -### Getting the Source - -If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows: - -```js - -// get all source -var source = sourceCode.getText(); - -// get source for just this AST node -var nodeSource = sourceCode.getText(node); - -// get source for AST node plus previous two characters -var nodeSourceWithPrev = sourceCode.getText(node, 2); - -// get source for AST node plus following two characters -var nodeSourceWithFollowing = sourceCode.getText(node, 0, 2); -``` - -In this way, you can look for patterns in the JavaScript text itself when the AST isn't providing the appropriate data (such as location of commas, semicolons, parentheses, etc.). - -### Accessing Comments - -While comments are not technically part of the AST, ESLint provides a few ways for rules to access them: - -#### sourceCode.getAllComments() - -This method returns an array of all the comments found in the program. This is useful for rules that need to check all comments regardless of location. - -#### sourceCode.getCommentsBefore(), sourceCode.getCommentsAfter(), and sourceCode.getCommentsInside() - -These methods return an array of comments that appear directly before, directly after, and inside nodes, respectively. They are useful for rules that need to check comments in relation to a given node or token. - -Keep in mind that the results of this method are calculated on demand. - -#### Token traversal methods - -Finally, comments can be accessed through many of `sourceCode`'s methods using the `includeComments` option. - -### Accessing Shebangs - -Shebangs are represented by tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined above. - -### Accessing Code Paths - -ESLint analyzes code paths while traversing AST. -You can access that code path objects with five events related to code paths. - -[details here](./code-path-analysis) - -## Rule Unit Tests - -Each bundled rule for ESLint core must have a set of unit tests submitted with it to be accepted. The test file is named the same as the source file but lives in `tests/lib/`. For example, if the rule source file is `lib/rules/foo.js` then the test file should be `tests/lib/rules/foo.js`. - -ESLint provides the [`RuleTester`](/docs/developer-guide/nodejs-api#ruletester) utility to make it easy to write tests for rules. - -## Performance Testing - -To keep the linting process efficient and unobtrusive, it is useful to verify the performance impact of new rules or modifications to existing rules. - -### Overall Performance - -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 main -Switched to branch 'main' - -$ npm run perf -CPU Speed is 2200 with multiplier 7500000 -Performance Run #1: 1394.689313ms -Performance Run #2: 1423.295351ms -Performance Run #3: 1385.09515ms -Performance Run #4: 1382.406982ms -Performance Run #5: 1409.68566ms -Performance budget ok: 1394.689313ms (limit: 3409.090909090909ms) - -$ git checkout my-rule-branch -Switched to branch 'my-rule-branch' - -$ npm run perf -CPU Speed is 2200 with multiplier 7500000 -Performance Run #1: 1443.736547ms -Performance Run #2: 1419.193291ms -Performance Run #3: 1436.018228ms -Performance Run #4: 1473.605485ms -Performance Run #5: 1457.455283ms -Performance budget ok: 1443.736547ms (limit: 3409.090909090909ms) -``` - -### Per-rule Performance - -ESLint has a built-in method to track performance of individual rules. Setting the `TIMING` environment variable will trigger the display, upon linting completion, of the ten longest-running rules, along with their individual running time (rule creation + rule execution) and relative performance impact as a percentage of total rule processing time (rule creation + rule execution). - -```bash -$ TIMING=1 eslint lib -Rule | Time (ms) | Relative -:-----------------------|----------:|--------: -no-multi-spaces | 52.472 | 6.1% -camelcase | 48.684 | 5.7% -no-irregular-whitespace | 43.847 | 5.1% -valid-jsdoc | 40.346 | 4.7% -handle-callback-err | 39.153 | 4.6% -space-infix-ops | 35.444 | 4.1% -no-undefined | 25.693 | 3.0% -no-shadow | 22.759 | 2.7% -no-empty-class | 21.976 | 2.6% -semi | 19.359 | 2.3% -``` - -To test one rule explicitly, combine the `--no-eslintrc`, and `--rule` options: - -```bash -$ TIMING=1 eslint --no-eslintrc --rule "quotes: [2, 'double']" lib -Rule | Time (ms) | Relative -:------|----------:|--------: -quotes | 18.066 | 100.0% -``` - -To see a longer list of results (more than 10), set the environment variable to another value such as `TIMING=50` or `TIMING=all`. - -## Rule Naming Conventions - -The rule naming conventions for ESLint are fairly simple: - -* If your rule is disallowing something, prefix it with `no-` such as `no-eval` for disallowing `eval()` and `no-debugger` for disallowing `debugger`. -* If your rule is enforcing the inclusion of something, use a short name without a special prefix. -* Use dashes between words. - -## Runtime Rules - -The thing that makes ESLint different from other linters is the ability to define custom rules at runtime. This is perfect for rules that are specific to your project or company and wouldn't make sense for ESLint to ship with. With runtime rules, you don't have to wait for the next version of ESLint or be disappointed that your rule isn't general enough to apply to the larger JavaScript community, just write your rules and include them at runtime. - -Runtime rules are written in the same format as all other rules. Create your rule as you would any other and then follow these steps: - -1. Place all of your runtime rules in the same directory (e.g., `eslint_rules`). -2. Create a [configuration file](../user-guide/configuring/) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `"warn"` or `"error"` in the configuration file. -3. Run the [command line interface](../user-guide/command-line-interface) using the `--rulesdir` option to specify the location of your runtime rules. diff --git a/eslint/docs/src/developer-guide/code-path-analysis.md b/eslint/docs/src/extend/code-path-analysis.md similarity index 95% rename from eslint/docs/src/developer-guide/code-path-analysis.md rename to eslint/docs/src/extend/code-path-analysis.md index 5433f27..7344f86 100644 --- a/eslint/docs/src/developer-guide/code-path-analysis.md +++ b/eslint/docs/src/extend/code-path-analysis.md @@ -1,6 +1,5 @@ --- title: Code Path Analysis Details -layout: doc --- @@ -15,7 +14,9 @@ if (a && b) { bar(); ``` +:::img-container ![Code Path Example](../assets/images/code-path-analysis/helo.svg) +::: ## Objects @@ -144,17 +145,23 @@ bar(); 1. First, the analysis advances to the end of loop. +:::img-container ![Loop Event's Example 1](../assets/images/code-path-analysis/loop-event-example-while-1.svg) +::: 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. +:::img-container ![Loop Event's Example 2](../assets/images/code-path-analysis/loop-event-example-while-2.svg) +::: 3. Last, it advances to the end. +:::img-container ![Loop Event's Example 3](../assets/images/code-path-analysis/loop-event-example-while-3.svg) +::: For example 2: @@ -169,29 +176,39 @@ bar(); First, the analysis advances to `ForStatement.update`. The `update` segment is hovered at first. +:::img-container ![Loop Event's Example 1](../assets/images/code-path-analysis/loop-event-example-for-1.svg) +::: 2. Second, it advances to `ForStatement.body`. Of course the `body` segment is preceded by the `test` segment. It keeps the `update` segment hovering. +:::img-container ![Loop Event's Example 2](../assets/images/code-path-analysis/loop-event-example-for-2.svg) +::: 3. Third, it creates the looping path from `body` segment to `update` segment. At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. It fires `onCodePathSegmentLoop` instead. +:::img-container ![Loop Event's Example 3](../assets/images/code-path-analysis/loop-event-example-for-3.svg) +::: 4. Fourth, also it creates the looping path from `update` segment to `test` segment. At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. It fires `onCodePathSegmentLoop` instead. +:::img-container ![Loop Event's Example 4](../assets/images/code-path-analysis/loop-event-example-for-4.svg) +::: 5. Last, it advances to the end. +:::img-container ![Loop Event's Example 5](../assets/images/code-path-analysis/loop-event-example-for-5.svg) +::: ## Usage Examples @@ -242,7 +259,8 @@ 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) { + const sourceCode = context.sourceCode; + return sourceCode.getDeclaredVariables(node).some(function(v) { return v.type === "Parameter" && v.name === "cb"; }); } @@ -337,7 +355,9 @@ See Also: console.log("Hello world!"); ``` +:::img-container ![Hello World](../assets/images/code-path-analysis/example-hello-world.svg) +::: ### `IfStatement` @@ -349,7 +369,9 @@ if (a) { } ``` +:::img-container ![`IfStatement`](../assets/images/code-path-analysis/example-ifstatement.svg) +::: ### `IfStatement` (chain) @@ -363,7 +385,9 @@ if (a) { } ``` +:::img-container ![`IfStatement` (chain)](../assets/images/code-path-analysis/example-ifstatement-chain.svg) +::: ### `SwitchStatement` @@ -384,7 +408,9 @@ switch (a) { } ``` +:::img-container ![`SwitchStatement`](../assets/images/code-path-analysis/example-switchstatement.svg) +::: ### `SwitchStatement` (has `default`) @@ -409,7 +435,9 @@ switch (a) { } ``` +:::img-container ![`SwitchStatement` (has `default`)](../assets/images/code-path-analysis/example-switchstatement-has-default.svg) +::: ### `TryStatement` (try-catch) @@ -432,7 +460,9 @@ It creates the paths from `try` block to `catch` block at: * The first throwable node (e.g. a function call) in the `try` block. * The end of the `try` block. +:::img-container ![`TryStatement` (try-catch)](../assets/images/code-path-analysis/example-trystatement-try-catch.svg) +::: ### `TryStatement` (try-finally) @@ -450,7 +480,9 @@ 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`). +:::img-container ![`TryStatement` (try-finally)](../assets/images/code-path-analysis/example-trystatement-try-finally.svg) +::: ### `TryStatement` (try-catch-finally) @@ -466,7 +498,9 @@ try { last(); ``` +:::img-container ![`TryStatement` (try-catch-finally)](../assets/images/code-path-analysis/example-trystatement-try-catch-finally.svg) +::: ### `WhileStatement` @@ -480,7 +514,9 @@ while (a) { } ``` +:::img-container ![`WhileStatement`](../assets/images/code-path-analysis/example-whilestatement.svg) +::: ### `DoWhileStatement` @@ -491,7 +527,9 @@ do { } while (a); ``` +:::img-container ![`DoWhileStatement`](../assets/images/code-path-analysis/example-dowhilestatement.svg) +::: ### `ForStatement` @@ -505,7 +543,9 @@ for (let i = 0; i < 10; ++i) { } ``` +:::img-container ![`ForStatement`](../assets/images/code-path-analysis/example-forstatement.svg) +::: ### `ForStatement` (for ever) @@ -516,7 +556,9 @@ for (;;) { bar(); ``` +:::img-container ![`ForStatement` (for ever)](../assets/images/code-path-analysis/example-forstatement-for-ever.svg) +::: ### `ForInStatement` @@ -526,7 +568,9 @@ for (let key in obj) { } ``` +:::img-container ![`ForInStatement`](../assets/images/code-path-analysis/example-forinstatement.svg) +::: ### When there is a function @@ -545,8 +589,12 @@ It creates two code paths. * The global's +:::img-container ![When there is a function](../assets/images/code-path-analysis/example-when-there-is-a-function-g.svg) +::: * The function's +:::img-container ![When there is a function](../assets/images/code-path-analysis/example-when-there-is-a-function-f.svg) +::: diff --git a/eslint/docs/src/developer-guide/working-with-custom-formatters.md b/eslint/docs/src/extend/custom-formatters.md similarity index 66% rename from eslint/docs/src/developer-guide/working-with-custom-formatters.md rename to eslint/docs/src/extend/custom-formatters.md index 1d970ea..b95db0f 100644 --- a/eslint/docs/src/developer-guide/working-with-custom-formatters.md +++ b/eslint/docs/src/extend/custom-formatters.md @@ -1,17 +1,22 @@ --- -title: Working with Custom Formatters -layout: doc +title: Custom Formatters eleventyNavigation: - key: working with custom formatters - parent: developer guide - title: Working with Custom Formatters - order: 6 + key: custom formatters + parent: extend eslint + title: Custom Formatters + order: 4 --- -While ESLint has some built-in formatters available to format the linting results, it's also possible to create and distribute your own custom formatters. You can include custom formatters in your project directly or create an npm package to distribute them separately. +Custom formatters let you display linting results in a format that best fits your needs, whether that's in a specific file format, a certain display style, or a format optimized for a particular tool. -Each formatter is just a function that receives a `results` object and a `context` and returns a string. For example, the following is how the `json` built-in formatter is implemented: +ESLint also has [built-in formatters](../use/formatters/) that you can use. + +You can include custom formatters in your project directly or create an npm package to distribute them separately. + +## Creating a Custom Formatter + +Each formatter is a function that receives a `results` object and a `context` as arguments and returns a string. For example, the following is how the built-in [JSON formatter](../use/formatters/#json) is implemented: ```js //my-awesome-formatter.js @@ -30,37 +35,17 @@ module.exports = async function(results) { }; ``` -To run ESLint with this formatter, you can use the `-f` (or `--format`) command line flag: +To run ESLint with this formatter, you can use the [`-f` (or `--format`)](../use/command-line-interface#-f---format) command line flag. You must begin the path to a locally defined custom formatter with a period (`.`), such as `./my-awesome-formatter.js` or `../formatters/my-awesome-formatter.js`. ```bash eslint -f ./my-awesome-formatter.js src/ ``` -In order to use a local file as a custom formatter, you must begin the filename with a dot (such as `./my-awesome-formatter.js` or `../formatters/my-awesome-formatter.js`). +The remainder of this section contains reference information on how to work with custom formatter functions. -## Packaging the Custom Formatter +### The `results` Argument -Custom formatters can also be distributed through npm packages. To do so, create an npm package with a name in the format of `eslint-formatter-*`, where `*` is the name of your formatter (such as `eslint-formatter-awesome`). Projects should then install the package and can use the custom formatter with the `-f` (or `--format`) flag like this: - -```bash -eslint -f awesome src/ -``` - -Because ESLint knows to look for packages beginning with `eslint-formatter-` when the specified formatter doesn't begin with a dot, there is no need to type `eslint-formatter-` when using a packaged custom formatter. - -Tips for `package.json`: - -* The `main` entry should be the JavaScript file implementing your custom formatter. -* Add these `keywords` to help users find your formatter: - * `"eslint"` - * `"eslint-formatter"` - * `"eslintformatter"` - -See all [formatters on npm](https://www.npmjs.com/search?q=eslint-formatter); - -## The `results` Argument - -The `results` object passed into a formatter is an array of objects containing the lint results for individual files. Here's some example output: +The `results` object passed into a formatter is an array of [`result`](#the-result-object) objects containing the linting results for individual files. Here's an example output: ```js [ @@ -102,7 +87,7 @@ The `results` object passed into a formatter is an array of objects containing t ] ``` -### The `result` Object +#### The `result` Object @@ -110,13 +95,13 @@ also be manually applied to that page. --> Each object in the `results` array is a `result` object. Each `result` object contains the path of the file that was linted and information about linting issues that were encountered. Here are the properties available on each `result` object: * **filePath**: The absolute path to the file that was linted. -* **messages**: An array of `message` objects. See below for more info about messages. +* **messages**: An array of [`message`](#the-message-object) objects. See below for more info about messages. * **errorCount**: The number of errors for the given file. * **warningCount**: The number of warnings for the given file. * **source**: The source code for the given file. This property is omitted if this file has no errors/warnings or if the `output` property is present. * **output**: The source code for the given file with as many fixes applied as possible. This property is omitted if no fix is available. -### The `message` Object +##### The `message` Object Each `message` object contains information about the ESLint rule that was triggered by some source code. The properties available on each `message` object are: @@ -125,20 +110,27 @@ Each `message` object contains information about the ESLint rule that was trigge * **message**: the human readable description of the error. * **line**: the line where the issue is located. * **column**: the column where the issue is located. -* **nodeType**: the type of the node in the [AST](https://github.com/estree/estree/blob/master/spec.md#node-objects) +* **nodeType**: the type of the node in the [AST](https://github.com/estree/estree/blob/master/es5.md#node-objects) -## The `context` Argument +### The `context` Argument -The formatter function receives an object as the second argument. The object has two properties: +The formatter function receives a `context` object as its second argument. The object has the following properties: -* `cwd` ... The current working directory. This value comes from the `cwd` constructor option of the [ESLint](nodejs-api#-new-eslintoptions) class. -* `rulesMeta` ... The `meta` property values of rules. See the [Working with Rules](working-with-rules) page for more information about rules. +* `cwd`: The current working directory. This value comes from the `cwd` constructor option of the [ESLint](../integrate/nodejs-api#-new-eslintoptions) class. +* `maxWarningsExceeded` (optional): If `--max-warnings` was set and the number of warnings exceeded the limit, this property's value is an object containing two properties: + * `maxWarnings`: the value of the `--max-warnings` option + * `foundWarnings`: the number of lint warnings +* `rulesMeta`: The `meta` property values of rules. See the [Custom Rules](custom-rules) page for more information about rules. -For example, here's what the object would look like if one rule, `no-extra-semi`, had been run: +For example, here's what the object would look like if the rule `no-extra-semi` had been run: ```js { cwd: "/path/to/cwd", + maxWarningsExceeded: { + maxWarnings: 5, + foundWarnings: 6 + }, rulesMeta: { "no-extra-semi": { type: "suggestion", @@ -153,151 +145,25 @@ For example, here's what the object would look like if one rule, `no-extra-semi` unexpected: "Unnecessary semicolon." } } - } + }, } ``` -**Note:** if a linting is executed by deprecated `CLIEngine` class, the `context` argument may be a different value because it is up to the API users. Please check whether the `context` argument is an expected value or not if you want to support legacy environments. +**Note:** if a linting is executed by the deprecated `CLIEngine` class, the `context` argument may be a different value because it is up to the API users. Please check whether the `context` argument is an expected value or not if you want to support legacy environments. -## Examples - -### Summary formatter - -A formatter that only cares about the total count of errors and warnings will look like this: - -```javascript -module.exports = function(results, context) { - // accumulate the errors and warnings - var summary = results.reduce( - function(seq, current) { - seq.errors += current.errorCount; - seq.warnings += current.warningCount; - return seq; - }, - { errors: 0, warnings: 0 } - ); - - if (summary.errors > 0 || summary.warnings > 0) { - return ( - "Errors: " + - summary.errors + - ", Warnings: " + - summary.warnings + - "\n" - ); - } - - return ""; -}; -``` - -Running `eslint` with the previous custom formatter, - -```bash -eslint -f ./my-awesome-formatter.js src/ -``` - -Will produce the following output: - -```bash -Errors: 2, Warnings: 4 -``` - -### Detailed formatter - -A more complex report will look something like this: - -```javascript -module.exports = function(results, context) { - var results = results || []; - - var summary = results.reduce( - function(seq, current) { - current.messages.forEach(function(msg) { - var logMessage = { - filePath: current.filePath, - ruleId: msg.ruleId, - ruleUrl: context.rulesMeta[msg.ruleId].docs.url, - message: msg.message, - line: msg.line, - column: msg.column - }; - - if (msg.severity === 1) { - logMessage.type = "warning"; - seq.warnings.push(logMessage); - } - if (msg.severity === 2) { - logMessage.type = "error"; - seq.errors.push(logMessage); - } - }); - return seq; - }, - { - errors: [], - warnings: [] - } - ); - - if (summary.errors.length > 0 || summary.warnings.length > 0) { - var lines = summary.errors - .concat(summary.warnings) - .map(function(msg) { - return ( - "\n" + - msg.type + - " " + - msg.ruleId + (msg.ruleUrl ? " (" + msg.ruleUrl + ")" : "") + - "\n " + - msg.filePath + - ":" + - msg.line + - ":" + - msg.column - ); - }) - .join("\n"); - - return lines + "\n"; - } -}; -``` - -So running `eslint` with this custom formatter: - -```bash -eslint -f ./my-awesome-formatter.js src/ -``` - -The output will be - -```bash -error space-infix-ops (https://eslint.org/docs/rules/space-infix-ops) - src/configs/bundler.js:6:8 -error semi (https://eslint.org/docs/rules/semi) - src/configs/bundler.js:6:10 -warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) - src/configs/bundler.js:5:6 -warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) - src/configs/bundler.js:6:6 -warning no-shadow (https://eslint.org/docs/rules/no-shadow) - src/configs/bundler.js:65:32 -warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) - src/configs/clean.js:3:6 -``` - -## Passing Arguments to Formatters +### Passing Arguments to Formatters While formatter functions do not receive arguments in addition to the results object and the context, it is possible to pass additional data into custom formatters using the methods described below. -## Using Environment Variables +#### Using Environment Variables -Custom formatters have access to environment variables and so can change their behavior based on environment variable data. Here's an example that uses a `AF_SKIP_WARNINGS` environment variable to determine whether or not to show warnings in the results: +Custom formatters have access to environment variables and so can change their behavior based on environment variable data. + +Here's an example that uses a `FORMATTER_SKIP_WARNINGS` environment variable to determine whether to show warnings in the results: ```js module.exports = function(results) { - var skipWarnings = process.env.AF_SKIP_WARNINGS === "true"; //af stands for awesome-formatter + var skipWarnings = process.env.FORMATTER_SKIP_WARNINGS === "true"; var results = results || []; var summary = results.reduce( @@ -357,7 +223,7 @@ module.exports = function(results) { You would run ESLint with this custom formatter and an environment variable set like this: ```bash -AF_SKIP_WARNINGS=true eslint -f ./my-awesome-formatter.js src/ +FORMATTER_SKIP_WARNINGS=true eslint -f ./my-awesome-formatter.js src/ ``` The output would be: @@ -370,9 +236,9 @@ error semi src/configs/bundler.js:6:10 ``` -### Complex Argument Passing +#### Complex Argument Passing -If you find the custom formatter pattern doesn't provide enough options for the way you'd like to format ESLint results, the best option is to use ESLint's built-in [JSON formatter](https://eslint.org/docs/user-guide/formatters/) and pipe the output to a second program. For example: +If you find the custom formatter pattern doesn't provide enough options for the way you'd like to format ESLint results, the best option is to use ESLint's built-in [JSON formatter](../use/formatters/#json) and pipe the output to a second program. For example: ```bash eslint -f json src/ | your-program-that-reads-JSON --option @@ -380,10 +246,158 @@ eslint -f json src/ | your-program-that-reads-JSON --option In this example, the `your-program-that-reads-json` program can accept the raw JSON of ESLint results and process it before outputting its own format of the results. You can pass as many command line arguments to that program as are necessary to customize the output. -## Note: Formatting for Terminals +### Formatting for Terminals Modern terminals like [iTerm2](https://www.iterm2.com/) or [Guake](http://guake-project.org/) expect a specific results format to automatically open filenames when they are clicked. Most terminals support this format for that purpose: ```bash file:line:column ``` + +## Packaging a Custom Formatter + +Custom formatters can be distributed through npm packages. To do so, create an npm package with a name in the format `eslint-formatter-*`, where `*` is the name of your formatter (such as `eslint-formatter-awesome`). Projects should then install the package and use the custom formatter with the [`-f` (or `--format`)](../use/command-line-interface#-f---format) flag like this: + +```bash +eslint -f awesome src/ +``` + +Because ESLint knows to look for packages beginning with `eslint-formatter-` when the specified formatter doesn't begin with a period, you do not need to type `eslint-formatter-` when using a packaged custom formatter. + +Tips for the `package.json` of a custom formatter: + +* The [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) entry point must be the JavaScript file implementing your custom formatter. +* Add these [`keywords`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) to help users find your formatter: + * `"eslint"` + * `"eslint-formatter"` + * `"eslintformatter"` + +See all [custom formatters on npm](https://www.npmjs.com/search?q=eslint-formatter). + +## Examples + +### Summary Formatter + +A formatter that only reports on the total count of errors and warnings will look like this: + +```javascript +module.exports = function(results, context) { + // accumulate the errors and warnings + var summary = results.reduce( + function(seq, current) { + seq.errors += current.errorCount; + seq.warnings += current.warningCount; + return seq; + }, + { errors: 0, warnings: 0 } + ); + + if (summary.errors > 0 || summary.warnings > 0) { + return ( + "Errors: " + + summary.errors + + ", Warnings: " + + summary.warnings + + "\n" + ); + } + + return ""; +}; +``` + +Run `eslint` with the above summary formatter: + +```bash +eslint -f ./my-awesome-formatter.js src/ +``` + +Will produce the following output: + +```bash +Errors: 2, Warnings: 4 +``` + +### Detailed Formatter + +A more complex report could look like this: + +```javascript +module.exports = function(results, context) { + var results = results || []; + + var summary = results.reduce( + function(seq, current) { + current.messages.forEach(function(msg) { + var logMessage = { + filePath: current.filePath, + ruleId: msg.ruleId, + ruleUrl: context.rulesMeta[msg.ruleId].docs.url, + message: msg.message, + line: msg.line, + column: msg.column + }; + + if (msg.severity === 1) { + logMessage.type = "warning"; + seq.warnings.push(logMessage); + } + if (msg.severity === 2) { + logMessage.type = "error"; + seq.errors.push(logMessage); + } + }); + return seq; + }, + { + errors: [], + warnings: [] + } + ); + + if (summary.errors.length > 0 || summary.warnings.length > 0) { + var lines = summary.errors + .concat(summary.warnings) + .map(function(msg) { + return ( + "\n" + + msg.type + + " " + + msg.ruleId + (msg.ruleUrl ? " (" + msg.ruleUrl + ")" : "") + + "\n " + + msg.filePath + + ":" + + msg.line + + ":" + + msg.column + ); + }) + .join("\n"); + + return lines + "\n"; + } +}; +``` + +When you run ESLint with this custom formatter: + +```bash +eslint -f ./my-awesome-formatter.js src/ +``` + +The output is: + +```bash +error space-infix-ops (https://eslint.org/docs/rules/space-infix-ops) + src/configs/bundler.js:6:8 +error semi (https://eslint.org/docs/rules/semi) + src/configs/bundler.js:6:10 +warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) + src/configs/bundler.js:5:6 +warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) + src/configs/bundler.js:6:6 +warning no-shadow (https://eslint.org/docs/rules/no-shadow) + src/configs/bundler.js:65:32 +warning no-unused-vars (https://eslint.org/docs/rules/no-unused-vars) + src/configs/clean.js:3:6 +``` diff --git a/eslint/docs/src/extend/custom-parsers.md b/eslint/docs/src/extend/custom-parsers.md new file mode 100644 index 0000000..388f54b --- /dev/null +++ b/eslint/docs/src/extend/custom-parsers.md @@ -0,0 +1,151 @@ +--- +title: Custom Parsers +eleventyNavigation: + key: custom parsers + parent: extend eslint + title: Custom Parsers + order: 5 + +--- + +ESLint custom parsers let you extend ESLint to support linting new non-standard JavaScript language features or custom syntax in your code. A parser is responsible for taking your code and transforming it into an abstract syntax tree (AST) that ESLint can then analyze and lint. + +## Creating a Custom Parser + +A custom parser is a JavaScript object with either a `parse` or `parseForESLint` method. The `parse` method only returns the AST, whereas `parseForESLint` also returns additional values that let the parser customize the behavior of ESLint even more. + +Both methods should take in the source code as the first argument, and an optional configuration object as the second argument, which is provided as [`parserOptions`](../use/configure/language-options#specifying-parser-options) in a configuration file. + +```javascript +// customParser.js + +const espree = require("espree"); + +// Logs the duration it takes to parse each file. +function parse(code, options) { + const label = `Parsing file "${options.filePath}"`; + console.time(label); + const ast = espree.parse(code, options); + console.timeEnd(label); + return ast; // Only the AST is returned. +}; + +module.exports = { parse }; +``` + +## `parse` Return Object + +The `parse` method should simply return the [AST](#ast-specification) object. + +## `parseForESLint` Return Object + +The `parseForESLint` method should return an object that contains the required property `ast` and optional properties `services`, `scopeManager`, and `visitorKeys`. + +* `ast` should contain the [AST](#ast-specification) object. +* `services` can contain any parser-dependent services (such as type checkers for nodes). The value of the `services` property is available to rules as `context.parserServices`. Default is an empty object. +* `scopeManager` can be a [ScopeManager](./scope-manager-interface) object. Custom parsers can use customized scope analysis for experimental/enhancement syntaxes. The default is the `ScopeManager` object which is created by [eslint-scope](https://github.com/eslint/eslint-scope). + * Support for `scopeManager` was added in ESLint v4.14.0. ESLint versions that support `scopeManager` will provide an `eslintScopeManager: true` property in `parserOptions`, which can be used for feature detection. +* `visitorKeys` can be an object to customize AST traversal. The keys of the object are the type of AST nodes. Each value is an array of the property names which should be traversed. The default is [KEYS of `eslint-visitor-keys`](https://github.com/eslint/eslint-visitor-keys#evkkeys). + * Support for `visitorKeys` was added in ESLint v4.14.0. ESLint versions that support `visitorKeys` will provide an `eslintVisitorKeys: true` property in `parserOptions`, which can be used for feature detection. + +## AST Specification + +The AST that custom parsers should create is based on [ESTree](https://github.com/estree/estree). The AST requires some additional properties about detail information of the source code. + +### All Nodes + +All nodes must have `range` property. + +* `range` (`number[]`) is an array of two numbers. Both numbers are a 0-based index which is the position in the array of source code characters. The first is the start position of the node, the second is the end position of the node. `code.slice(node.range[0], node.range[1])` must be the text of the node. This range does not include spaces/parentheses which are around the node. +* `loc` (`SourceLocation`) must not be `null`. [The `loc` property is defined as nullable by ESTree](https://github.com/estree/estree/blob/25834f7247d44d3156030f8e8a2d07644d771fdb/es5.md#node-objects), but ESLint requires this property. The `SourceLocation#source` property can be `undefined`. ESLint does not use the `SourceLocation#source` property. + +The `parent` property of all nodes must be rewritable. Before any rules have access to the AST, ESLint sets each node's `parent` property to its parent node while traversing. + +### The `Program` Node + +The `Program` node must have `tokens` and `comments` properties. Both properties are an array of the below `Token` interface. + +```ts +interface Token { + type: string; + loc: SourceLocation; + // See the "All Nodes" section for details of the `range` property. + range: [number, number]; + value: string; +} +``` + +* `tokens` (`Token[]`) is the array of tokens which affect the behavior of programs. Arbitrary spaces can exist between tokens, so rules check the `Token#range` to detect spaces between tokens. This must be sorted by `Token#range[0]`. +* `comments` (`Token[]`) is the array of comment tokens. This must be sorted by `Token#range[0]`. + +The range indexes of all tokens and comments must not overlap with the range of other tokens and comments. + +### The `Literal` Node + +The `Literal` node must have `raw` property. + +* `raw` (`string`) is the source code of this literal. This is the same as `code.slice(node.range[0], node.range[1])`. + +## Packaging a Custom Parser + +To publish your custom parser to npm, perform the following: + +1. Create a custom parser following the [Creating a Custom Parser](#creating-a-custom-parser) section above. +1. [Create an npm package](https://docs.npmjs.com/creating-node-js-modules) for the custom parser. +1. In your `package.json` file, set the [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) field as the file that exports your custom parser. +1. [Publish the npm package.](https://docs.npmjs.com/creating-and-publishing-unscoped-public-packages) + +For more information on publishing an npm package, refer to the [npm documentation](https://docs.npmjs.com/). + +Once you've published the npm package, you can use it by adding the package to your project. For example: + +```shell +npm install eslint-parser-myparser --save-dev +``` + +Then add the custom parser to your ESLint configuration file with the `parser` property. For example: + +```js +// .eslintrc.js + +module.exports = { + parser: 'eslint-parser-myparser', + // ... rest of configuration +}; +``` + +To learn more about using ESLint parsers in your project, refer to [Configure a Parser](../use/configure/parser). + +## Example + +For a complex example of a custom parser, refer to the [`@typescript-eslint/parser`](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/parser) source code. + +A simple custom parser that provides a `context.parserServices.foo()` method to rules. + +```javascript +// awesome-custom-parser.js +var espree = require("espree"); +function parseForESLint(code, options) { + return { + ast: espree.parse(code, options), + services: { + foo: function() { + console.log("foo"); + } + }, + scopeManager: null, + visitorKeys: null + }; +}; + +module.exports = { parseForESLint }; +``` + +Include the custom parser in an ESLint configuration file: + +```js +// .eslintrc.json +{ + "parser": "./path/to/awesome-custom-parser.js" +} +``` diff --git a/eslint/docs/src/extend/custom-processors.md b/eslint/docs/src/extend/custom-processors.md new file mode 100644 index 0000000..d112d72 --- /dev/null +++ b/eslint/docs/src/extend/custom-processors.md @@ -0,0 +1,179 @@ +--- +title: Custom Processors +eleventyNavigation: + key: custom processors + parent: create plugins + title: Custom Processors + order: 2 + +--- + +You can also create custom processors that tell ESLint how to process files other than standard JavaScript. For example, you could write a custom processor to extract and process JavaScript from Markdown files ([eslint-plugin-markdown](https://www.npmjs.com/package/eslint-plugin-markdown) includes a custom processor for this). + +## Custom Processor Specification + +In order to create a custom processor, the object exported from your module has to conform to the following interface: + +```js +module.exports = { + processors: { + "processor-name": { + // takes text of the file and filename + preprocess: function(text, filename) { + // here, you can strip out any non-JS content + // and split into multiple strings to lint + + return [ // return an array of code blocks to lint + { text: code1, filename: "0.js" }, + { text: code2, filename: "1.js" }, + ]; + }, + + // takes a Message[][] and filename + postprocess: function(messages, filename) { + // `messages` argument contains two-dimensional array of Message objects + // where each top-level array item contains array of lint messages related + // to the text that was returned in array from preprocess() method + + // you need to return a one-dimensional array of the messages you want to keep + return [].concat(...messages); + }, + + supportsAutofix: true // (optional, defaults to false) + } + } +}; +``` + +**The `preprocess` method** takes the file contents and filename as arguments, and returns an array of code blocks to lint. The code blocks will be linted separately but still be registered to the filename. + +A code block has two properties `text` and `filename`. The `text` property is the content of the block and the `filename` property is the name of the block. The name of the block can be anything, but should include the file extension, which tells the linter how to process the current block. The linter checks the [`--ext` CLI option](../use/command-line-interface#--ext) to see if the current block should be linted and resolves `overrides` configs to check how to process the current block. + +It's up to the plugin to decide if it needs to return just one part of the non-JavaScript file or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts. However, for `.md` files, you can return multiple items because each JavaScript block might be independent. + +**The `postprocess` method** takes a two-dimensional array of arrays of lint messages and the filename. Each item in the input array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the locations of all errors to correspond to locations in the original, unprocessed code, and aggregate them into a single flat array and return it. + +Reported problems have the following location information in each lint message: + +```typescript +type LintMessage = { + + /// The 1-based line number where the message occurs. + line?: number; + + /// The 1-based column number where the message occurs. + column?: number; + + /// The 1-based line number of the end location. + endLine?: number; + + /// The 1-based column number of the end location. + endColumn?: number; + + /// If `true`, this is a fatal error. + fatal?: boolean; + + /// Information for an autofix. + fix: Fix; + + /// The error message. + message: string; + + /// The ID of the rule which generated the message, or `null` if not applicable. + ruleId: string | null; + + /// The severity of the message. + severity: 0 | 1 | 2; + + /// Information for suggestions. + suggestions?: Suggestion[]; +}; + +type Fix = { + range: [number, number]; + text: string; +} + +type Suggestion = { + desc?: string; + messageId?: string; + fix: Fix; +} + +``` + +By default, ESLint does not perform autofixes when a custom processor is used, even when the `--fix` flag is enabled on the command line. To allow ESLint to autofix code when using your processor, you should take the following additional steps: + +1. Update the `postprocess` method to additionally transform the `fix` property of reported problems. All autofixable problems have a `fix` property, which is an object with the following schema: + + ```typescript + { + range: [number, number], + text: string + } + ``` + + The `range` property contains two indexes in the code, referring to the start and end location of a contiguous section of text that will be replaced. The `text` property refers to the text that will replace the given range. + + In the initial list of problems, the `fix` property will refer to a fix in the processed JavaScript. The `postprocess` method should transform the object to refer to a fix in the original, unprocessed file. + +2. Add a `supportsAutofix: true` property to the processor. + +You can have both rules and custom processors in a single plugin. You can also have multiple processors in one plugin. To support multiple extensions, add each one to the `processors` element and point them to the same object. + +## Specifying Processor in Config Files + +To use a processor, add its ID to a `processor` section in the config file. Processor ID is a concatenated string of plugin name and processor name with a slash as a separator. This can also be added to a `overrides` section of the config, to specify which processors should handle which files. + +For example: + +```yml +plugins: + - a-plugin +overrides: + - files: "*.md" + processor: a-plugin/markdown +``` + +See [Specify a Processor](../use/configure/plugins#specify-a-processor) in the Plugin Configuration documentation for more details. + +## File Extension-named Processor + +If a custom processor name starts with `.`, ESLint handles the processor as a **file extension-named processor**. ESLint applies the processor to files with that filename extension automatically. Users don't need to specify the file extension-named processors in their config files. + +For example: + +```js +module.exports = { + processors: { + // This processor will be applied to `*.md` files automatically. + // Also, you can use this processor as "plugin-id/.md" explicitly. + ".md": { + preprocess(text, filename) { /* ... */ }, + postprocess(messageLists, filename) { /* ... */ } + } + // This processor will not be applied to any files automatically. + // To use this processor, you must explicitly specify it + // in your configuration as "plugin-id/markdown". + "markdown": { + preprocess(text, filename) { /* ... */ }, + postprocess(messageLists, filename) { /* ... */ } + } + } +} +``` + +You can also use the same custom processor with multiple filename extensions. The following example shows using the same processor for both `.md` and `.mdx` files: + +```js +const myCustomProcessor = { /* processor methods */ }; + +module.exports = { + // The same custom processor is applied to both + // `.md` and `.mdx` files. + processors: { + ".md": myCustomProcessor, + ".mdx": myCustomProcessor + } +} +``` diff --git a/eslint/docs/src/developer-guide/working-with-rules-deprecated.md b/eslint/docs/src/extend/custom-rules-deprecated.md similarity index 96% rename from eslint/docs/src/developer-guide/working-with-rules-deprecated.md rename to eslint/docs/src/extend/custom-rules-deprecated.md index bd3fd77..49faa4f 100644 --- a/eslint/docs/src/developer-guide/working-with-rules-deprecated.md +++ b/eslint/docs/src/extend/custom-rules-deprecated.md @@ -1,10 +1,9 @@ --- title: Working with Rules (Deprecated) -layout: doc --- -**Note:** This page covers the deprecated rule format for ESLint <= 2.13.1. [This is the most recent rule format](./working-with-rules). +**Note:** This page covers the deprecated rule format for ESLint <= 2.13.1. [This is the most recent rule format](./custom-rules). Each rule in ESLint has two files named with its identifier (for example, `no-extra-semi`). @@ -38,13 +37,13 @@ module.exports.schema = []; // no options ## Rule Basics -`schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring/rules#configuring-rules) +`schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../use/configure/rules) `create` (function) returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code: * if a key is a node type, ESLint calls that **visitor** function while going **down** the tree * if a key is a node type plus `:exit`, ESLint calls that **visitor** function while going **up** the tree -* if a key is an event name, ESLint calls that **handler** function for [code path analysis](./code-path-analysis) +* if a key is an event name, ESLint calls that **handler** function for [code path analysis](code-path-analysis) A rule can use the current node and its surrounding tree to report or fix problems. @@ -78,7 +77,7 @@ module.exports = function(context) { The `context` object contains additional functionality that is helpful for rules to do their jobs. As the name implies, the `context` object contains information that is relevant to the context of the rule. The `context` object has the following properties: -* `parserOptions` - the parser options configured for this run (more details [here](../user-guide/configuring/language-options#specifying-parser-options)). +* `parserOptions` - the parser options configured for this run (more details [here](../use/configure/language-options#specifying-parser-options)). * `id` - the rule ID. * `options` - an array of rule options. * `settings` - the `settings` from configuration. @@ -328,7 +327,7 @@ Keep in mind that comments are technically not a part of the AST and are only at ESLint analyzes code paths while traversing AST. You can access that code path objects with five events related to code paths. -[details here](./code-path-analysis) +[details here](code-path-analysis) ## Rule Unit Tests @@ -482,7 +481,7 @@ valid: [ ] ``` -The options available and the expected syntax for `parserOptions` is the same as those used in [configuration](../user-guide/configuring/language-options#specifying-parser-options). +The options available and the expected syntax for `parserOptions` is the same as those used in [configuration](../use/configure/language-options#specifying-parser-options). ### Write Several Tests @@ -577,5 +576,5 @@ The thing that makes ESLint different from other linters is the ability to defin Runtime rules are written in the same format as all other rules. Create your rule as you would any other and then follow these steps: 1. Place all of your runtime rules in the same directory (i.e., `eslint_rules`). -2. Create a [configuration file](../user-guide/configuring/) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `1` or `2` in the configuration file. -3. Run the [command line interface](../user-guide/command-line-interface) using the `--rulesdir` option to specify the location of your runtime rules. +2. Create a [configuration file](../use/configure/) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `1` or `2` in the configuration file. +3. Run the [command line interface](../use/command-line-interface) using the `--rulesdir` option to specify the location of your runtime rules. diff --git a/eslint/docs/src/extend/custom-rules.md b/eslint/docs/src/extend/custom-rules.md new file mode 100644 index 0000000..35b04fd --- /dev/null +++ b/eslint/docs/src/extend/custom-rules.md @@ -0,0 +1,798 @@ +--- +title: Custom Rules +eleventyNavigation: + key: custom rules + parent: create plugins + title: Custom Rules + order: 1 + +--- + +You can create custom rules to use with ESLint. You might want to create a custom rule if the [core rules](../rules/) do not cover your use case. + +**Note:** This page covers the most recent rule format for ESLint >= 3.0.0. There is also a [deprecated rule format](./custom-rules-deprecated). + +Here's the basic format of a custom rule: + +```js +// customRule.js + +module.exports = { + meta: { + type: "suggestion", + docs: { + description: "Description of the rule", + }, + fixable: "code", + schema: [] // no options + }, + create: function(context) { + return { + // callback functions + }; + } +}; +``` + +## Rule Structure + +The source file for a rule exports an object with the following properties. Both custom rules and core rules follow this format. + +`meta`: (`object`) Contains metadata for the rule: + +* `type`: (`string`) Indicates the type of rule, which is one of `"problem"`, `"suggestion"`, or `"layout"`: + + * `"problem"`: The rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. + * `"suggestion"`: The rule is identifying something that could be done in a better way but no errors will occur if the code isn't changed. + * `"layout"`: The rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren't specified in the AST. + +* `docs`: (`object`) Required for core rules and optional for custom rules. Core rules have specific entries inside of `docs` while custom rules can include any properties that you need. The following properties are only relevant when working on core rules. + + * `description`: (`string`) Provides the short description of the rule in the [rules index](../rules/). + * `recommended`: (`boolean`) Specifies whether the `"extends": "eslint:recommended"` property in a [configuration file](../use/configure/configuration-files#extending-configuration-files) enables the rule. + * `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). + +* `fixable`: (`string`) Either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../use/command-line-interface#--fix) automatically fixes problems reported by the rule. + + **Important:** the `fixable` property is mandatory for fixable rules. If this property isn't specified, ESLint will throw an error whenever the rule attempts to produce a fix. Omit the `fixable` property if the rule is not fixable. + +* `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`: (`object | array`) Specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../use/configure/rules). + +* `deprecated`: (`boolean`) Indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated. + +* `replacedBy`: (`array`) In the case of a deprecated rule, specify replacement rule(s). + +`create()`: Returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code: + +* If a key is a node type or a [selector](./selectors), ESLint calls that **visitor** function while going **down** the tree. +* If a key is a node type or a [selector](./selectors) plus `:exit`, ESLint calls that **visitor** function while going **up** the tree. +* If a key is an event name, ESLint calls that **handler** function for [code path analysis](code-path-analysis). + +A rule can use the current node and its surrounding tree to report or fix problems. + +Here are methods for the [array-callback-return](../rules/array-callback-return) rule: + +```js +function checkLastSegment (node) { + // report problem for function if last code path segment is reachable +} + +module.exports = { + meta: { ... }, + create: function(context) { + // declare the state of the rule + return { + ReturnStatement: function(node) { + // at a ReturnStatement node while going down + }, + // at a function expression node while going up: + "FunctionExpression:exit": checkLastSegment, + "ArrowFunctionExpression:exit": checkLastSegment, + onCodePathStart: function (codePath, node) { + // at the start of analyzing a code path + }, + onCodePathEnd: function(codePath, node) { + // at the end of analyzing a code path + } + }; + } +}; +``` + +## The Context Object + +The `context` object is the only argument of the `create` method in a rule. For example: + +```js +// customRule.js + +module.exports = { + meta: { ... }, + // `context` object is the argument + create(context) { + // ... + } +}; +``` + +As the name implies, the `context` object contains information that is relevant to the context of the rule. + +The `context` object has the following properties: + +* `id`: (`string`) The rule ID. +* `filename`: (`string`) The filename associated with the source. +* `physicalFilename`: (`string`) When linting a file, it provides the full path of the file on disk without any code block information. When linting text, it provides the value passed to `—stdin-filename` or `` if not specified. +* `cwd`: (`string`) The `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. +* `options`: (`array`) An array of the [configured options](../use/configure/rules) for this rule. This array does not include the rule severity (see the [dedicated section](#accessing-options-passed-to-a-rule)). +* `sourceCode`: (`object`) A `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). +* `settings`: (`object`) The [shared settings](../use/configure/configuration-files#adding-shared-settings) from the configuration. +* `parserPath`: (`string`) The name of the `parser` from the configuration. +* `parserServices`: (`object`) Contains parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) +* `parserOptions`: The parser options configured for this run (more details [here](../use/configure/language-options#specifying-parser-options)). + +Additionally, the `context` object has the following methods: + +* `getAncestors()`: (**Deprecated:** Use `SourceCode#getAncestors(node)` instead.) 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()`: (**Deprecated:** Use `context.cwd` instead.) Returns the `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. +* `getDeclaredVariables(node)`: (**Deprecated:** Use `SourceCode#getDeclaredVariables(node)` instead.) Returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. + * If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. + * If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. + * If the node is a `FunctionDeclaration` or `FunctionExpression`, the variable for the function name is returned, in addition to variables for the function parameters. + * If the node is an `ArrowFunctionExpression`, variables for the parameters are returned. + * If the node is a `ClassDeclaration` or a `ClassExpression`, the variable for the class name is returned. + * If the node is a `CatchClause`, the variable for the exception is returned. + * If the node is an `ImportDeclaration`, variables for all of its specifiers are returned. + * If the node is an `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`, the declared variable is returned. + * Otherwise, if the node does not declare any variables, an empty array is returned. +* `getFilename()`: (**Deprecated:** Use `context.filename` instead.) Returns the filename associated with the source. +* `getPhysicalFilename()`: (**Deprecated:** Use `context.physicalFilename` instead.) When linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `—stdin-filename` or `` if not specified. +* `getScope()`: (**Deprecated:** Use `SourceCode#getScope(node)` instead.) Returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. +* `getSourceCode()`: (**Deprecated:** Use `context.sourceCode` instead.) Returns a `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). +* `markVariableAsUsed(name)`: (**Deprecated:** Use `SourceCode#markVariableAsUsed(name, node)` instead.) Marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. +* `report(descriptor)`. Reports a problem in the code (see the [dedicated section](#reporting-problems)). + +**Note:** Earlier versions of ESLint supported additional methods on the `context` object. Those methods were removed in the new format and should not be relied upon. + +### Reporting Problems + +The main method you'll use when writing custom rules is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties: + +* `message`: (`string`) The problem message. +* `node`: (optional `object`) The AST node related to the problem. If present and `loc` is not specified, then the starting location of the node is used as the location of the problem. +* `loc`: (optional `object`) Specifies the location of the problem. If both `loc` and `node` are specified, then the location is used from `loc` instead of `node`. + * `start`: An object of the start location. + * `line`: (`number`) The 1-based line number at which the problem occurred. + * `column`: (`number`) The 0-based column number at which the problem occurred. + * `end`: An object of the end location. + * `line`: (`number`) The 1-based line number at which the problem occurred. + * `column`: (`number`) The 0-based column number at which the problem occurred. +* `data`: (optional `object`) [Placeholder](#using-message-placeholders) data for `message`. +* `fix(fixer)`: (optional `function`) Applies a [fix](#applying-fixes) to resolve the problem. + +Note that at least one of `node` or `loc` is required. + +The simplest example is to use just `node` and `message`: + +```js +context.report({ + node: node, + message: "Unexpected identifier" +}); +``` + +The node contains all the information necessary to figure out the line and column number of the offending text as well as the source text representing the node. + +#### Using Message Placeholders + +You can also use placeholders in the message and provide `data`: + +```js +{% raw %} +context.report({ + node: node, + message: "Unexpected identifier: {{ identifier }}", + data: { + identifier: node.name + } +}); +{% endraw %} +``` + +Note that leading and trailing whitespace is optional in message parameters. + +The node contains all the information necessary to figure out the line and column number of the offending text as well as the source text representing the node. + +#### `messageId`s + +Instead of typing out messages in both the `context.report()` call and your tests, you can use `messageId`s instead. + +This allows you to avoid retyping error messages. It also prevents errors reported in different sections of your rule from having out-of-date messages. + +Rule file: + +```js +{% raw %} +// avoid-name.js + +module.exports = { + meta: { + messages: { + avoidName: "Avoid using variables named '{{ name }}'" + } + }, + create(context) { + return { + Identifier(node) { + if (node.name === "foo") { + context.report({ + node, + messageId: "avoidName", + data: { + name: "foo", + } + }); + } + } + }; + } +}; +{% endraw %} +``` + +In the file to lint: + +```javascript +// someFile.js + +var foo = 2; +// ^ error: Avoid using variables named 'foo' +``` + +In your tests: + +```javascript +// avoid-name.test.js + +var rule = require("../../../lib/rules/avoid-name"); +var RuleTester = require("eslint").RuleTester; + +var ruleTester = new RuleTester(); +ruleTester.run("avoid-name", rule, { + valid: ["bar", "baz"], + invalid: [ + { + code: "foo", + errors: [ + { + messageId: "avoidName" + } + ] + } + ] +}); +``` + +#### Applying Fixes + +If you'd like ESLint to attempt to fix the problem you're reporting, you can do so by specifying the `fix` function when using `context.report()`. The `fix` function receives a single argument, a `fixer` object, that you can use to apply a fix. For example: + +```js +context.report({ + node: node, + message: "Missing semicolon", + fix(fixer) { + return fixer.insertTextAfter(node, ";"); + } +}); +``` + +Here, the `fix()` function is used to insert a semicolon after the node. Note that a fix is not immediately applied, and may not be applied at all if there are conflicts with other fixes. After applying fixes, ESLint will run all the enabled rules again on the fixed code, potentially applying more fixes. This process will repeat up to 10 times, or until no more fixable problems are found. Afterward, any remaining problems will be reported as usual. + +**Important:** The `meta.fixable` property is mandatory for fixable rules. ESLint will throw an error if a rule that implements `fix` functions does not [export](#rule-structure) the `meta.fixable` property. + +The `fixer` object has the following methods: + +* `insertTextAfter(nodeOrToken, text)`: Insert text after the given node or token. +* `insertTextAfterRange(range, text)`: Insert text after the given range. +* `insertTextBefore(nodeOrToken, text)`: Insert text before the given node or token. +* `insertTextBeforeRange(range, text)`: Insert text before the given range. +* `remove(nodeOrToken)`: Remove the given node or token. +* `removeRange(range)`: Remove text in the given range. +* `replaceText(nodeOrToken, text)`: Replace the text in the given node or token. +* `replaceTextRange(range, text)`: Replace the text in the given range. + +A `range` is a two-item array containing character indices inside the source code. The first item is the start of the range (inclusive) and the second item is the end of the range (exclusive). Every node and token has a `range` property to identify the source code range they represent. + +The above methods return a `fixing` object. +The `fix()` function can return the following values: + +* A `fixing` object. +* An array which includes `fixing` objects. +* An iterable object which enumerates `fixing` objects. Especially, the `fix()` function can be a generator. + +If you make a `fix()` function which returns multiple `fixing` objects, those `fixing` objects must not overlap. + +Best practices for fixes: + +1. Avoid any fixes that could change the runtime behavior of code and cause it to stop working. +1. Make fixes as small as possible. Fixes that are unnecessarily large could conflict with other fixes, and prevent them from being applied. +1. Only make one fix per message. This is enforced because you must return the result of the fixer operation from `fix()`. +1. Since all rules are run again after the initial round of fixes is applied, it's not necessary for a rule to check whether the code style of a fix will cause errors to be reported by another rule. + * For example, suppose a fixer would like to surround an object key with quotes, but it's not sure whether the user would prefer single or double quotes. + + ```js + ({ foo : 1 }) + + // should get fixed to either + + ({ 'foo': 1 }) + + // or + + ({ "foo": 1 }) + ``` + + * This fixer can just select a quote type arbitrarily. If it guesses wrong, the resulting code will be automatically reported and fixed by the [`quotes`](../rules/quotes) rule. + +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, ""); + } +}); +``` + +#### Conflicting Fixes + +Conflicting fixes are fixes that apply different changes to the same part of the source code. +There is no way to specify which of the conflicting fixes is applied. + +For example, if two fixes want to modify characters 0 through 5, only one is applied. + +#### 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. + +To provide suggestions, use the `suggest` key in the report argument with an array of suggestion objects. The suggestion objects represent individual suggestions that could be applied and require either a `desc` key string that describes what applying the suggestion would do or a `messageId` key (see [below](#suggestion-messageids)), and a `fix` key that is a function defining the suggestion result. This `fix` function follows the same API as regular fixes (described above in [applying fixes](#applying-fixes)). + +```js +{% raw %} +context.report({ + node: node, + message: "Unnecessary escape character: \\{{character}}.", + data: { character }, + suggest: [ + { + desc: "Remove the `\\`. This maintains the current functionality.", + fix: function(fixer) { + return fixer.removeRange(range); + } + }, + { + desc: "Replace the `\\` with `\\\\` to include the actual backslash character.", + fix: function(fixer) { + return fixer.insertTextBeforeRange(range, "\\"); + } + } + ] +}); +{% 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-structure) this property. + +**Note:** Suggestions are applied as stand-alone changes, 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 the presence/absence of semicolons. All of those things can be corrected by multipass autofix when the user triggers it. + +Best practices for suggestions: + +1. Don't try to do too much and suggest large refactors that could introduce a lot of breaking changes. +1. As noted above, don't try to conform to user-defined styles. + +Suggestions are intended to provide fixes. ESLint will automatically remove the whole suggestion from the linting output if the suggestion's `fix` function returned `null` or an empty array/sequence. + +#### Suggestion `messageId`s + +Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageids)). Here is an example of how to use a suggestion `messageId` in a rule: + +```js +{% raw %} +module.exports = { + meta: { + messages: { + 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) { + // ... + context.report({ + node: node, + messageId: 'unnecessaryEscape', + data: { character }, + suggest: [ + { + messageId: "removeEscape", // suggestion messageId + fix: function(fixer) { + return fixer.removeRange(range); + } + }, + { + messageId: "escapeBackslash", // suggestion messageId + fix: function(fixer) { + return fixer.insertTextBeforeRange(range, "\\"); + } + } + ] + }); + } +}; +{% endraw %} +``` + +#### Placeholders in Suggestion Messages + +You can also use placeholders in the suggestion message. This works the same way as placeholders for the overall error (see [using message placeholders](#using-message-placeholders)). + +Please note that you have to provide `data` on the suggestion's object. Suggestion messages cannot use properties from the overall error's `data`. + +```js +{% raw %} +module.exports = { + meta: { + messages: { + unnecessaryEscape: "Unnecessary escape character: \\{{character}}.", + removeEscape: "Remove `\\` before {{character}}.", + }, + hasSuggestions: true + }, + create: function(context) { + // ... + context.report({ + node: node, + messageId: "unnecessaryEscape", + data: { character }, // data for the unnecessaryEscape overall message + suggest: [ + { + messageId: "removeEscape", + data: { character }, // data for the removeEscape suggestion message + fix: function(fixer) { + return fixer.removeRange(range); + } + } + ] + }); + } +}; +{% endraw %} +``` + +### Accessing Options Passed to a Rule + +Some rules require options in order to function correctly. These options appear in configuration (`.eslintrc`, command line interface, or comments). For example: + +```json +{ + "quotes": ["error", "double"] +} +``` + +The `quotes` rule in this example has one option, `"double"` (the `error` is the error level). You can retrieve the options for a rule by using `context.options`, which is an array containing every configured option for the rule. In this case, `context.options[0]` would contain `"double"`: + +```js +module.exports = { + create: function(context) { + var isDouble = (context.options[0] === "double"); + + // ... + } +}; +``` + +Since `context.options` is just an array, you can use it to determine how many options have been passed as well as retrieving the actual options themselves. Keep in mind that the error level is not part of `context.options`, as the error level cannot be known or modified from inside a rule. + +When using options, make sure that your rule has some logical defaults in case the options are not provided. + +### Accessing the Source Code + +The `SourceCode` object is the main object for getting more information about the source code being linted. You can retrieve the `SourceCode` object at any time by using the `context.sourceCode` property: + +```js +module.exports = { + create: function(context) { + var sourceCode = context.sourceCode; + + // ... + } +}; +``` + +**Deprecated:** The `context.getSourceCode()` method is deprecated; make sure to use `context.sourceCode` property instead. + +Once you have an instance of `SourceCode`, you can use the following methods on it to work with the code: + +* `getText(node)`: Returns the source code for the given node. Omit `node` to get the whole source (see the [dedicated section](#accessing-the-source-text)). +* `getAllComments()`: Returns an array of all comments in the source (see the [dedicated section](#accessing-comments)). +* `getCommentsBefore(nodeOrToken)`: Returns an array of comment tokens that occur directly before the given node or token (see the [dedicated section](#accessing-comments)). +* `getCommentsAfter(nodeOrToken)`: Returns an array of comment tokens that occur directly after the given node or token (see the [dedicated section](#accessing-comments)). +* `getCommentsInside(node)`: Returns an array of all comment tokens inside a given node (see the [dedicated section](#accessing-comments)). +* `isSpaceBetween(nodeOrToken, nodeOrToken)`: Returns true if there is a whitespace character between the two tokens or, if given a node, the last token of the first node and the first token of the second node. +* `getFirstToken(node, skipOptions)`: Returns the first token representing the given node. +* `getFirstTokens(node, countOptions)`: Returns the first `count` tokens representing the given node. +* `getLastToken(node, skipOptions)`: Returns the last token representing the given node. +* `getLastTokens(node, countOptions)`: Returns the last `count` tokens representing the given node. +* `getTokenAfter(nodeOrToken, skipOptions)`: Returns the first token after the given node or token. +* `getTokensAfter(nodeOrToken, countOptions)`: Returns `count` tokens after the given node or token. +* `getTokenBefore(nodeOrToken, skipOptions)`: Returns the first token before the given node or token. +* `getTokensBefore(nodeOrToken, countOptions)`: Returns `count` tokens before the given node or token. +* `getFirstTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)`: Returns the first token between two nodes or tokens. +* `getFirstTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)`: Returns the first `count` tokens between two nodes or tokens. +* `getLastTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)`: Returns the last token between two nodes or tokens. +* `getLastTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)`: Returns the last `count` tokens between two nodes or tokens. +* `getTokens(node)`: Returns all tokens for the given node. +* `getTokensBetween(nodeOrToken1, nodeOrToken2)`: Returns all tokens between two nodes. +* `getTokenByRangeStart(index, rangeOptions)`: Returns the token whose range starts at the given index in the source. +* `getNodeByRangeIndex(index)`: Returns the deepest node in the AST containing the given source index. +* `getLocFromIndex(index)`: Returns an object with `line` and `column` properties, corresponding to the location of the given source index. `line` is 1-based and `column` is 0-based. +* `getIndexFromLoc(loc)`: Returns the index of a given location in the source code, where `loc` is an object with a 1-based `line` key and a 0-based `column` key. +* `commentsExistBetween(nodeOrToken1, nodeOrToken2)`: Returns `true` if comments exist between two nodes. + +`skipOptions` is an object which has 3 properties; `skip`, `includeComments`, and `filter`. Default is `{skip: 0, includeComments: false, filter: null}`. + +* `skip`: (`number`) Positive integer, the number of skipping tokens. If `filter` option is given at the same time, it doesn't count filtered tokens as skipped. +* `includeComments`: (`boolean`) The flag to include comment tokens into the result. +* `filter(token)`: Function which gets a token as the first argument. If the function returns `false` then the result excludes the token. + +`countOptions` is an object which has 3 properties; `count`, `includeComments`, and `filter`. Default is `{count: 0, includeComments: false, filter: null}`. + +* `count`: (`number`) Positive integer, the maximum number of returning tokens. +* `includeComments`: (`boolean`) The flag to include comment tokens into the result. +* `filter(token)`: Function which gets a token as the first argument, if the function returns `false` then the result excludes the token. + +`rangeOptions` is an object that has 1 property, `includeComments`. Default is `{includeComments: false}`. + +* `includeComments`: (`boolean`) The flag to include comment tokens into the result. + +There are also some properties you can access: + +* `hasBOM`: (`boolean`) The flag to indicate whether the source code has Unicode BOM. +* `text`: (`string`) The full text of the code being linted. Unicode BOM has been stripped from this text. +* `ast`: (`object`) `Program` node of the AST for the code being linted. +* `scopeManager`: [ScopeManager](./scope-manager-interface#scopemanager-interface) object of the code. +* `visitorKeys`: (`object`) Visitor keys to traverse this AST. +* `lines`: (`array`) Array of lines, split according to the specification's definition of line breaks. + +You should use a `SourceCode` object whenever you need to get more information about the code being linted. + +#### Accessing the Source Text + +If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows: + +```js + +// get all source +var source = sourceCode.getText(); + +// get source for just this AST node +var nodeSource = sourceCode.getText(node); + +// get source for AST node plus previous two characters +var nodeSourceWithPrev = sourceCode.getText(node, 2); + +// get source for AST node plus following two characters +var nodeSourceWithFollowing = sourceCode.getText(node, 0, 2); +``` + +In this way, you can look for patterns in the JavaScript text itself when the AST isn't providing the appropriate data (such as the location of commas, semicolons, parentheses, etc.). + +#### Accessing Comments + +While comments are not technically part of the AST, ESLint provides the `sourceCode.getAllComments()`, `sourceCode.getCommentsBefore()`, `sourceCode.getCommentsAfter()`, and `sourceCode.getCommentsInside()` to access them. + +`sourceCode.getCommentsBefore()`, `sourceCode.getCommentsAfter()`, and `sourceCode.getCommentsInside()` are useful for rules that need to check comments in relation to a given node or token. + +Keep in mind that the results of these methods are calculated on demand. + +You can also access comments through many of `sourceCode`'s methods using the `includeComments` option. + +### Options Schemas + +Rules may export a `schema` property, which is a [JSON Schema](https://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. + +There are two formats for a rule's exported `schema`: + +1. A full JSON Schema object describing all possible options the rule accepts. +2. An array of JSON Schema objects for each optional positional argument. + +In both cases, these should exclude the [severity](../use/configure/rules#rule-severities), as ESLint automatically validates this first. + +For example, the `yoda` rule accepts a primary mode argument of `"always"` or `"never"`, as well as an extra options object with an optional property `exceptRange`: + +```js +// "yoda": ["error", "never", { "exceptRange": true }] +module.exports = { + meta: { + schema: [ + { + "enum": ["always", "never"] + }, + { + "type": "object", + "properties": { + "exceptRange": { + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + }, +}; +``` + +**Note:** If your rule schema uses JSON schema [`$ref`](https://json-schema.org/understanding-json-schema/structuring.html#ref) properties, you must use the full JSON Schema object rather than the array of positional property schemas. This is because ESLint transforms the array shorthand into a single schema without updating references that makes them incorrect (they are ignored). + +To learn more about JSON Schema, we recommend looking at some examples on the [JSON Schema website](https://json-schema.org/learn/), or reading the free [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) ebook. + +### Accessing Shebangs + +[Shebangs (#!)](https://en.wikipedia.org/wiki/Shebang_(Unix)) are represented by the unique tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined in the [Accessing Comments](#accessing-comments) section, such as `sourceCode.getAllComments()`. + +### Accessing Variable Scopes + +The `SourceCode#getScope(node)` method returns the scope of the given node. It is a useful method for finding information about the variables in a given scope and how they are used in other scopes. + +**Deprecated:** The `context.getScope()` is deprecated; make sure to use `SourceCode#getScope(node)` instead. + +#### Scope types + +The following table contains a list of AST node types and the scope type that they correspond to. For more information about the scope types, refer to the [`Scope` object documentation](./scope-manager-interface#scope-interface). + +| AST Node Type | Scope Type | +|:--------------------------|:-----------| +| `Program` | `global` | +| `FunctionDeclaration` | `function` | +| `FunctionExpression` | `function` | +| `ArrowFunctionExpression` | `function` | +| `ClassDeclaration` | `class` | +| `ClassExpression` | `class` | +| `BlockStatement` ※1 | `block` | +| `SwitchStatement` ※1 | `switch` | +| `ForStatement` ※2 | `for` | +| `ForInStatement` ※2 | `for` | +| `ForOfStatement` ※2 | `for` | +| `WithStatement` | `with` | +| `CatchClause` | `catch` | +| others | ※3 | + +**※1** Only if the configured parser provided the block-scope feature. The default parser provides the block-scope feature if `parserOptions.ecmaVersion` is not less than `6`.
    +**※2** Only if the `for` statement defines the iteration variable as a block-scoped variable (E.g., `for (let i = 0;;) {}`).
    +**※3** The scope of the closest ancestor node which has own scope. If the closest ancestor node has multiple scopes then it chooses the innermost scope (E.g., the `Program` node has a `global` scope and a `module` scope if `Program#sourceType` is `"module"`. The innermost scope is the `module` scope.). + +#### Scope Variables + +The `Scope#variables` property contains an array of [`Variable` objects](./scope-manager-interface#variable-interface). These are the variables declared in current scope. You can use these `Variable` objects to track references to a variable throughout the entire module. + +Inside of each `Variable`, the `Variable#references` property contains an array of [`Reference` objects](./scope-manager-interface#reference-interface). The `Reference` array contains all the locations where the variable is referenced in the module's source code. + +Also inside of each `Variable`, the `Variable#defs` property contains an array of [`Definition` objects](./scope-manager-interface#definition-interface). You can use the `Definitions` to find where the variable was defined. + +Global variables have the following additional properties: + +* `Variable#writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only. +* `Variable#eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file. +* `Variable#eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments. +* `Variable#eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments. + +For examples of using `SourceCode#getScope()` to track variables, refer to the source code for the following built-in rules: + +* [no-shadow](https://github.com/eslint/eslint/blob/main/lib/rules/no-shadow.js): Calls `sourceCode.getScope()` at the `Program` node and inspects all child scopes to make sure a variable name is not reused at a lower scope. ([no-shadow](../rules/no-shadow) documentation) +* [no-redeclare](https://github.com/eslint/eslint/blob/main/lib/rules/no-redeclare.js): Calls `sourceCode.getScope()` at each scope to make sure that a variable is not declared twice in the same scope. ([no-redeclare](../rules/no-redeclare) documentation) + +### Marking Variables as Used + +**Deprecated:** The `context.markVariableAsUsed()` method is deprecated in favor of `sourceCode.markVariableAsUsed()`. + +Certain ESLint rules, such as [`no-unused-vars`](../rules/no-unused-vars), check to see if a variable has been used. ESLint itself only knows about the standard rules of variable access and so custom ways of accessing variables may not register as "used". + +To help with this, you can use the `sourceCode.markVariableAsUsed()` method. This method takes two arguments: the name of the variable to mark as used and an option reference node indicating the scope in which you are working. Here's an example: + +```js +module.exports = { + create: function(context) { + var sourceCode = context.sourceCode; + + return { + ReturnStatement(node) { + + // look in the scope of the function for myCustomVar and mark as used + sourceCode.markVariableAsUsed("myCustomVar", node); + + // or: look in the global scope for myCustomVar and mark as used + sourceCode.markVariableAsUsed("myCustomVar"); + } + } + // ... + } +}; +``` + +Here, the `myCustomVar` variable is marked as used relative to a `ReturnStatement` node, which means ESLint will start searching from the scope closest to that node. If you omit the second argument, then the top-level scope is used. (For ESM files, the top-level scope is the module scope; for CommonJS files, the top-level scope is the first function scope.) + +### Accessing Code Paths + +ESLint analyzes code paths while traversing AST. You can access code path objects with five events related to code paths. For more information, refer to [Code Path Analysis](code-path-analysis). + +### Deprecated `SourceCode` Methods + +Please note that the following `SourceCode` methods have been deprecated and will be removed in a future version of ESLint: + +* `getComments()`: Replaced by `SourceCode#getCommentsBefore()`, `SourceCode#getCommentsAfter()`, and `SourceCode#getCommentsInside()`. +* `getTokenOrCommentBefore()`: Replaced by `SourceCode#getTokenBefore()` with the `{ includeComments: true }` option. +* `getTokenOrCommentAfter()`: Replaced by `SourceCode#getTokenAfter()` with the `{ includeComments: true }` option. +* `isSpaceBetweenTokens()`: Replaced by `SourceCode#isSpaceBetween()` +* `getJSDocComment()` + +## Rule Unit Tests + +ESLint provides the [`RuleTester`](../integrate/nodejs-api#ruletester) utility to make it easy to write tests for rules. + +## Rule Naming Conventions + +While you can give a custom rule any name you'd like, the core rules have naming conventions. It could be clearer to apply these same naming conventions to your custom rule. To learn more, refer to the [Core Rule Naming Conventions](../contribute/core-rules#rule-naming-conventions) documentation. + +## Runtime Rules + +The thing that makes ESLint different from other linters is the ability to define custom rules at runtime. This is perfect for rules that are specific to your project or company and wouldn't make sense for ESLint to ship with or be included in a plugin. Just write your rules and include them at runtime. + +Runtime rules are written in the same format as all other rules. Create your rule as you would any other and then follow these steps: + +1. Place all of your runtime rules in the same directory (e.g., `eslint_rules`). +2. Create a [configuration file](../use/configure/) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `"warn"` or `"error"` in the configuration file. +3. Run the [command line interface](../use/command-line-interface) using the `--rulesdir` option to specify the location of your runtime rules. + +## Profile Rule Performance + +ESLint has a built-in method to track the performance of individual rules. Setting the `TIMING` environment variable will trigger the display, upon linting completion, of the ten longest-running rules, along with their individual running time (rule creation + rule execution) and relative performance impact as a percentage of total rule processing time (rule creation + rule execution). + +```bash +$ TIMING=1 eslint lib +Rule | Time (ms) | Relative +:-----------------------|----------:|--------: +no-multi-spaces | 52.472 | 6.1% +camelcase | 48.684 | 5.7% +no-irregular-whitespace | 43.847 | 5.1% +valid-jsdoc | 40.346 | 4.7% +handle-callback-err | 39.153 | 4.6% +space-infix-ops | 35.444 | 4.1% +no-undefined | 25.693 | 3.0% +no-shadow | 22.759 | 2.7% +no-empty-class | 21.976 | 2.6% +semi | 19.359 | 2.3% +``` + +To test one rule explicitly, combine the `--no-eslintrc`, and `--rule` options: + +```bash +$ TIMING=1 eslint --no-eslintrc --rule "quotes: [2, 'double']" lib +Rule | Time (ms) | Relative +:------|----------:|--------: +quotes | 18.066 | 100.0% +``` + +To see a longer list of results (more than 10), set the environment variable to another value such as `TIMING=50` or `TIMING=all`. diff --git a/eslint/docs/src/extend/index.md b/eslint/docs/src/extend/index.md new file mode 100644 index 0000000..9f623a6 --- /dev/null +++ b/eslint/docs/src/extend/index.md @@ -0,0 +1,50 @@ +--- +title: Extend ESLint +eleventyNavigation: + key: extend eslint + title: Extend ESLint + order: 2 + +--- + +This guide is intended for those who wish to extend the functionality of ESLint. + +In order to extend ESLint, it's recommended that: + +* You know JavaScript, since ESLint is written in JavaScript. +* You have some familiarity with Node.js, since ESLint runs on it. +* You're comfortable with command-line programs. + +If that sounds like you, then continue reading to get started. + +## [Ways to Extend ESLint](ways-to-extend) + +This page summarizes the various ways that you can extend ESLint and how these extensions all fit together. + +## [Create Plugins](plugins) + +You've developed custom rules for ESLint and you want to share them with the community. You can publish an ESLint plugin on npm. + +## [Custom Rules](custom-rules) + +This section explains how to create custom rules to use with ESLint. + +## [Custom Formatters](custom-formatters) + +This section explains how you can create a custom formatter to control what ESLint outputs. + +## [Custom Parsers](custom-parsers) + +If you don't want to use the default parser of ESLint, this section explains how to create custom parsers. + +## [Custom Processors](custom-processors) + +This section explains how you can use a custom processor to have ESLint process files other than JavaScript. + +## [Share Configurations](shareable-configs) + +This section explains how you can bundle and share ESLint configuration in a JavaScript package. + +## [Node.js API Reference](../integrate/nodejs-api) + +If you're interested in writing a tool that uses ESLint, then you can use the Node.js API to get programmatic access to functionality. diff --git a/eslint/docs/src/extend/plugins.md b/eslint/docs/src/extend/plugins.md new file mode 100644 index 0000000..70bc3ee --- /dev/null +++ b/eslint/docs/src/extend/plugins.md @@ -0,0 +1,195 @@ +--- +title: Create Plugins +eleventyNavigation: + key: create plugins + parent: extend eslint + title: Create Plugins + order: 2 + +--- + +An ESLint plugin is an extension for ESLint that adds additional rules and configuration options. Plugins let you customize your ESLint configuration to enforce rules that are not included in the core ESLint package. Plugins can also provide additional environments, custom processors, and configurations. + +## Name a Plugin + +Each plugin is an npm module with a name in the format of `eslint-plugin-`, such as `eslint-plugin-jquery`. You can also use scoped packages in the format of `@/eslint-plugin-` such as `@jquery/eslint-plugin-jquery` or even `@/eslint-plugin` such as `@jquery/eslint-plugin`. + +## Create a Plugin + +The easiest way to start creating a plugin is to use the [Yeoman generator](https://www.npmjs.com/package/generator-eslint). The generator will guide you through setting up the skeleton of a plugin. + +### Metadata in Plugins + +For easier debugging and more effective caching of plugins, it's recommended to provide a name and version in a `meta` object at the root of your plugin, like this: + +```js +// preferred location of name and version +module.exports = { + meta: { + name: "eslint-plugin-custom", + version: "1.2.3" + } +}; +``` + +The `meta.name` property should match the npm package name for your plugin and the `meta.version` property should match the npm package version for your plugin. The easiest way to accomplish this is by reading this information from your `package.json`. + +As an alternative, you can also expose `name` and `version` properties at the root of your plugin, such as: + +```js +// alternate location of name and version +module.exports = { + name: "eslint-plugin-custom", + version: "1.2.3" +}; +``` + +While the `meta` object is the preferred way to provide the plugin name and version, this format is also acceptable and is provided for backward compatibility. + +### Rules in Plugins + +Plugins can expose custom rules for use in ESLint. To do so, the plugin must export a `rules` object containing a key-value mapping of rule ID to rule. The rule ID does not have to follow any naming convention (so it can just be `dollar-sign`, for instance). To learn more about creating custom rules in plugins, refer to [Custom Rules](custom-rules). + +```js +module.exports = { + rules: { + "dollar-sign": { + create: function (context) { + // rule implementation ... + } + } + } +}; +``` + +To use the rule in ESLint, you would use the unprefixed plugin name, followed by a slash, followed by the rule name. So if this plugin were named `eslint-plugin-myplugin`, then in your configuration you'd refer to the rule by the name `myplugin/dollar-sign`. Example: `"rules": {"myplugin/dollar-sign": 2}`. + +### Environments in Plugins + +Plugins can expose additional environments for use in ESLint. To do so, the plugin must export an `environments` object. The keys of the `environments` object are the names of the different environments provided and the values are the environment settings. For example: + +```js +module.exports = { + environments: { + jquery: { + globals: { + $: false + } + } + } +}; +``` + +There's a `jquery` environment defined in this plugin. To use the environment in ESLint, you would use the unprefixed plugin name, followed by a slash, followed by the environment name. So if this plugin were named `eslint-plugin-myplugin`, then you would set the environment in your configuration to be `"myplugin/jquery"`. + +Plugin environments can define the following objects: + +1. `globals` - acts the same `globals` in a configuration file. The keys are the names of the globals and the values are `true` to allow the global to be overwritten and `false` to disallow. +1. `parserOptions` - acts the same as `parserOptions` in a configuration file. + +### Processors in Plugins + +You can add processors to plugins by including the processor functions in the `processors` key. For more information on defining custom processors, refer to [Custom Processors](custom-processors). + +```js +module.exports = { + processors: { + // This processor will be applied to `*.md` files automatically. + ".md": { + preprocess(text, filename) { /* ... */ }, + postprocess(messages, filename) { /* ... */ } + } + "processor-name": { + preprocess: function(text, filename) {/* ... */}, + + postprocess: function(messages, filename) { /* ... */ }, + } + } +} +``` + +### Configs in Plugins + +You can bundle configurations inside a plugin by specifying them under the `configs` key. This can be useful when you want to bundle a set of custom rules with additional configuration. Multiple configurations are supported per plugin. + +You can include individual rules from a plugin in a config that's also included in the plugin. In the config, you must specify your plugin name in the `plugins` array as well as any rules you want to enable that are part of the plugin. Any plugin rules must be prefixed with the short or long plugin name. + +```js +// eslint-plugin-myPlugin + +module.exports = { + configs: { + myConfig: { + plugins: ["myPlugin"], + env: ["browser"], + rules: { + semi: "error", + "myPlugin/my-rule": "error", + "eslint-plugin-myPlugin/another-rule": "error" + } + }, + myOtherConfig: { + plugins: ["myPlugin"], + env: ["node"], + rules: { + "myPlugin/my-rule": "off", + "eslint-plugin-myPlugin/another-rule": "off", + "eslint-plugin-myPlugin/yet-another-rule": "error" + } + } + }, + rules: { + "my-rule": {/* rule definition */}, + "another-rule": {/* rule definition */}, + "yet-another-rule": {/* rule definition */} + } +}; +``` + +Plugins cannot force a specific configuration to be used. Users must manually include a plugin's configurations in their configuration file. + +If the example plugin above were called `eslint-plugin-myPlugin`, the `myConfig` and `myOtherConfig` configurations would then be usable in a configuration file by extending `"plugin:myPlugin/myConfig"` and `"plugin:myPlugin/myOtherConfig"`, respectively. + +```json +// .eslintrc.json + +{ + "extends": ["plugin:myPlugin/myConfig"] +} + +``` + +### Peer Dependency + +To make clear that the plugin requires ESLint to work correctly, you must declare ESLint as a peer dependency by mentioning it in the `peerDependencies` field of your plugin's `package.json`. + +Plugin support was introduced in ESLint version `0.8.0`. Ensure the `peerDependencies` points to ESLint `0.8.0` or later. + +```json +{ + "peerDependencies": { + "eslint": ">=0.8.0" + } +} +``` + +## Testing + +ESLint provides the [`RuleTester`](../integrate/nodejs-api#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-n](https://www.npmjs.com/package/eslint-plugin-n) + +## Share Plugins + +In order to make your plugin available to the community you have to publish it on npm. + +To make it easy for others to find your plugin, add these [keywords](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) to your `package.json` file: + +* `eslint` +* `eslintplugin` diff --git a/eslint/docs/src/developer-guide/scope-manager-interface.md b/eslint/docs/src/extend/scope-manager-interface.md similarity index 99% rename from eslint/docs/src/developer-guide/scope-manager-interface.md rename to eslint/docs/src/extend/scope-manager-interface.md index c864aba..60e2643 100644 --- a/eslint/docs/src/developer-guide/scope-manager-interface.md +++ b/eslint/docs/src/extend/scope-manager-interface.md @@ -1,6 +1,5 @@ --- title: ScopeManager -layout: doc --- diff --git a/eslint/docs/src/developer-guide/selectors.md b/eslint/docs/src/extend/selectors.md similarity index 90% rename from eslint/docs/src/developer-guide/selectors.md rename to eslint/docs/src/extend/selectors.md index 4f286c4..5ea700d 100644 --- a/eslint/docs/src/developer-guide/selectors.md +++ b/eslint/docs/src/extend/selectors.md @@ -1,6 +1,5 @@ --- title: Selectors -layout: doc --- @@ -94,7 +93,7 @@ If multiple selectors have equal specificity, their listeners will be called in ### Restricting syntax with selectors -With the [no-restricted-syntax](/docs/rules/no-restricted-syntax) rule, you can restrict the usage of particular syntax in your code. For example, you can use the following configuration to disallow using `if` statements that do not have block statements as their body: +With the [no-restricted-syntax](../rules/no-restricted-syntax) rule, you can restrict the usage of particular syntax in your code. For example, you can use the following configuration to disallow using `if` statements that do not have block statements as their body: ```json { @@ -138,4 +137,16 @@ Using selectors in the `no-restricted-syntax` rule can give you a lot of control ### 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/]`. +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/]`. + +For example, the following configuration disallows importing from `some/path`: + +```json +{ + "rules": { + "no-restricted-syntax": ["error", "ImportDeclaration[source.value=/^some\\u002Fpath$/]"] + } +} +``` + +Note that the `\` character needs to be escaped (`\\`) in JSON and string literals. diff --git a/eslint/docs/src/extend/shareable-configs.md b/eslint/docs/src/extend/shareable-configs.md new file mode 100644 index 0000000..60b3532 --- /dev/null +++ b/eslint/docs/src/extend/shareable-configs.md @@ -0,0 +1,237 @@ +--- +title: Share Configurations +eleventyNavigation: + key: share configs + parent: extend eslint + title: Share Configurations + order: 3 + +--- + +To share your ESLint configuration, create a **shareable config**. You can publish your shareable config on [npm](https://www.npmjs.com/) so that others can download and use it in their ESLint projects. + +This page explains how to create and publish a shareable config. + +## Creating a Shareable Config + +Shareable configs are simply npm packages that export a configuration object. To start, [create a Node.js module](https://docs.npmjs.com/getting-started/creating-node-modules) like you normally would. + +The module name must take one of the following forms: + +* Begin with `eslint-config-`, such as `eslint-config-myconfig`. +* Be an npm [scoped module](https://docs.npmjs.com/misc/scope). To create a scoped module, name or prefix the module with `@scope/eslint-config`, such as `@scope/eslint-config` or `@scope/eslint-config-myconfig`. + +In your module, export the shareable config from the module's [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) entry point file. The default main entry point is `index.js`. For example: + +```js +// index.js +module.exports = { + + globals: { + MyGlobal: true + }, + + rules: { + semi: [2, "always"] + } + +}; +``` + +Since the `index.js` file is just JavaScript, you can read these settings from a file or generate them dynamically. + +## Publishing a Shareable Config + +Once your shareable config is ready, you can [publish it to npm](https://docs.npmjs.com/getting-started/publishing-npm-packages) to share it with others. We recommend using the `eslint` and `eslintconfig` [keywords](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) in the `package.json` file so others can easily find your module. + +You should declare your dependency on ESLint in the `package.json` using the [peerDependencies](https://docs.npmjs.com/files/package.json#peerdependencies) field. The recommended way to declare a dependency for future-proof compatibility is with the ">=" range syntax, using the lowest required ESLint version. For example: + +```json +{ + "peerDependencies": { + "eslint": ">= 3" + } +} +``` + +If your shareable config depends on a plugin, you should also specify it as a `peerDependency` (plugins will be loaded relative to the end user's project, so the end user is required to install the plugins they need). However, if your shareable config depends on a [custom parser](custom-parsers) or another shareable config, you can specify these packages as `dependencies` in the `package.json`. + +You can also test your shareable config on your computer before publishing by linking your module globally. Type: + +```bash +npm link +``` + +Then, in your project that wants to use your shareable config, type: + +```bash +npm link eslint-config-myconfig +``` + +Be sure to replace `eslint-config-myconfig` with the actual name of your module. + +## Using a Shareable Config + +To use a shareable config, include the config name in the `extends` field of a configuration file. For the value, use your module name. For example: + +```json +{ + "extends": "eslint-config-myconfig" +} +``` + +You can also omit the `eslint-config-` and it is automatically assumed by ESLint: + +```json +{ + "extends": "myconfig" +} +``` + +You cannot use shareable configs with the ESLint CLI [`--config`](../use/command-line-interface#-c---config) flag. + +### npm Scoped Modules + +npm [scoped modules](https://docs.npmjs.com/misc/scope) are also supported in a number of ways. + +You can use the module name: + +```json +{ + "extends": "@scope/eslint-config" +} +``` + +You can also omit the `eslint-config` and it is automatically assumed by ESLint: + +```json +{ + "extends": "@scope" +} +``` + +The module name can also be customized. For example, if you have a package named `@scope/eslint-config-myconfig`, the configuration can be specified as: + +```json +{ + "extends": "@scope/eslint-config-myconfig" +} +``` + +You could also omit `eslint-config` to specify the configuration as: + +```json +{ + "extends": "@scope/myconfig" +} +``` + +### Overriding Settings from Shareable Configs + +You can override settings from the shareable config by adding them directly into your `.eslintrc` file. + +## Sharing Multiple Configs + +You can share multiple configs in the same npm package. Specify a default config for the package by following the directions in the [Creating a Shareable Config](#creating-a-shareable-config) section. You can specify additional shareable configs by adding a new file to your npm package and then referencing it from your ESLint config. + +As an example, you can create a file called `my-special-config.js` in the root of your npm package and export a config, such as: + +```js +// my-special-config.js +module.exports = { + rules: { + quotes: [2, "double"] + } +}; +``` + +Then, assuming you're using the package name `eslint-config-myconfig`, you can access the additional config via: + +```json +{ + "extends": "myconfig/my-special-config" +} +``` + +When using [scoped modules](https://docs.npmjs.com/misc/scope) it is not possible to omit the `eslint-config` namespace. Doing so would result in resolution errors as explained above. Assuming the package name is `@scope/eslint-config`, the additional config can be accessed as: + +```json +{ + "extends": "@scope/eslint-config/my-special-config" +} +``` + +Note that you can leave off the `.js` from the filename. + +**Important:** We strongly recommend always including a default config for your plugin to avoid errors. + +## Local Config File Resolution + +If you need to make multiple configs that can extend each other and live in different directories, you can create a single shareable config that handles this scenario. + +As an example, let's assume you're using the package name `eslint-config-myconfig` and your package looks something like this: + +```text +myconfig +├── index.js +└─┬ lib + ├── defaults.js + ├── dev.js + ├── ci.js + └─┬ ci + ├── frontend.js + ├── backend.js + └── common.js +``` + +In the `index.js` file, you can do something like this: + +```js +module.exports = require('./lib/ci.js'); +``` + +Now inside the package you have `/lib/defaults.js`, which contains: + +```js +module.exports = { + rules: { + 'no-console': 1 + } +}; +``` + +Inside `/lib/ci.js` you have: + +```js +module.exports = require('./ci/backend'); +``` + +Inside `/lib/ci/common.js`: + +```js +module.exports = { + rules: { + 'no-alert': 2 + }, + extends: 'myconfig/lib/defaults' +}; +``` + +Despite being in an entirely different directory, you'll see that all `extends` must use the full package path to the config file you wish to extend. + +Now inside `/lib/ci/backend.js`: + +```js +module.exports = { + rules: { + 'no-console': 1 + }, + extends: 'myconfig/lib/ci/common' +}; +``` + +In the last file, once again see that to properly resolve your config, you need to include the full package path. + +## Further Reading + +* [npm Developer Guide](https://docs.npmjs.com/misc/developers) diff --git a/eslint/docs/src/extend/ways-to-extend.md b/eslint/docs/src/extend/ways-to-extend.md new file mode 100644 index 0000000..0432492 --- /dev/null +++ b/eslint/docs/src/extend/ways-to-extend.md @@ -0,0 +1,60 @@ +--- +title: Ways to Extend ESLint +eleventyNavigation: + key: ways to extend + parent: extend eslint + title: Ways to Extend ESLint + order: 1 +--- + +ESLint is highly pluggable and configurable. There are a variety of ways that you can extend ESLint's functionality. + +This page explains the ways to extend ESLint, and how these extensions all fit together. + +## Plugins + +Plugins let you add your own ESLint custom rules and custom processors to a project. You can publish a plugin as an npm module. + +Plugins are useful because your project may require some ESLint configuration that isn't included in the core `eslint` package. For example, if you're using a frontend JavaScript library like React or framework like Vue, these tools have some features that require custom rules outside the scope of the ESLint core rules. + +Often a plugin is paired with a configuration for ESLint that applies a set of features from the plugin to a project. You can include configurations in a plugin as well. + +For example, [`eslint-plugin-react`](https://www.npmjs.com/package/eslint-plugin-react) is an ESLint plugin that includes rules specifically for React projects. The rules include things like enforcing consistent usage of React component lifecycle methods and requiring the use of key props when rendering dynamic lists. + +To learn more about creating the extensions you can include in a plugin, refer to the following documentation: + +* [Custom Rules](custom-rules) +* [Custom Processors](custom-processors) +* [Configs in Plugins](plugins#configs-in-plugins) + +To learn more about bundling these extensions into a plugin, refer to [Plugins](plugins). + +## Shareable Configs + +ESLint shareable configs are pre-defined configurations for ESLint that you can use in your projects. They bundle rules and other configuration together in an npm package. Anything that you can put in a configuration file can be put in a shareable config. + +You can either publish a shareable config independently or as part of a plugin. + +For example, a popular shareable config is [eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb), which contains a variety of rules in addition to some [parser options](../use/configure/language-options#specifying-parser-options). This is a set of rules for ESLint that is designed to match the style guide used by the [Airbnb JavaScript style guide](https://github.com/airbnb/javascript). By using the `eslint-config-airbnb` shareable config, you can automatically enforce the Airbnb style guide in your project without having to manually configure each rule. + +To learn more about creating a shareable config, refer to [Share Configuration](shareable-configs). + +## Custom Formatters + +Custom formatters take ESLint linting results and output the results in a format that you define. Custom formatters let you display linting results in a format that best fits your needs, whether that's in a specific file format, a certain display style, or a format optimized for a particular tool. You only need to create a custom formatter if the [built-in formatters](../use/formatters/) don't serve your use case. + +For example, the custom formatter [eslint-formatter-gitlab](https://www.npmjs.com/package/eslint-formatter-gitlab) can be used to display ESLint results in GitLab code quality reports. + +To learn more about creating a custom formatter, refer to [Custom Formatters](custom-formatters). + +## Custom Parsers + +ESLint custom parsers are a way to extend ESLint to support the linting of new language features or custom syntax in your code. A parser is responsible for taking your code and transforming it into an abstract syntax tree (AST) that ESLint can then analyze and lint. + +ESLint ships with a built-in JavaScript parser (Espree), but custom parsers allow you to lint other languages or to extend the linting capabilities of the built-in parser. + +For example, the custom parser [@typescript-eslint/parser](https://typescript-eslint.io/architecture/parser/) extends ESLint to lint TypeScript code. + +Custom parsers **cannot** be included in a plugin, unlike the other extension types. + +To learn more about creating a custom parser, refer to [Custom Parsers](custom-parsers). diff --git a/eslint/docs/src/developer-guide/nodejs-api.md b/eslint/docs/src/integrate/nodejs-api.md similarity index 91% rename from eslint/docs/src/developer-guide/nodejs-api.md rename to eslint/docs/src/integrate/nodejs-api.md index 0b8d842..853abdc 100644 --- a/eslint/docs/src/developer-guide/nodejs-api.md +++ b/eslint/docs/src/integrate/nodejs-api.md @@ -1,12 +1,10 @@ --- -title: Node.js API -layout: doc +title: Node.js API Reference eleventyNavigation: key: node.js api - parent: developer guide - title: Node.js API - order: 9 - + parent: extend eslint + title: Node.js API Reference + order: 6 --- While ESLint is designed to be run on the command line, it's possible to use ESLint programmatically through the Node.js API. The purpose of the Node.js API is to allow plugin and tool authors to use the ESLint functionality directly, without going through the command line interface. @@ -25,48 +23,92 @@ Here's a simple example of using the `ESLint` class: const { ESLint } = require("eslint"); (async function main() { - // 1. Create an instance. - const eslint = new ESLint(); + // 1. Create an instance. + const eslint = new ESLint(); - // 2. Lint files. - const results = await eslint.lintFiles(["lib/**/*.js"]); + // 2. Lint files. + const results = await eslint.lintFiles(["lib/**/*.js"]); - // 3. Format the results. - const formatter = await eslint.loadFormatter("stylish"); - const resultText = formatter.format(results); + // 3. Format the results. + const formatter = await eslint.loadFormatter("stylish"); + const resultText = formatter.format(results); - // 4. Output it. - console.log(resultText); + // 4. Output it. + console.log(resultText); })().catch((error) => { - process.exitCode = 1; - console.error(error); + process.exitCode = 1; + console.error(error); }); ``` -And here is an example that autofixes lint problems: +Here's an example that autofixes lint problems: ```js const { ESLint } = require("eslint"); (async function main() { - // 1. Create an instance with the `fix` option. - const eslint = new ESLint({ fix: true }); + // 1. Create an instance with the `fix` option. + const eslint = new ESLint({ fix: true }); - // 2. Lint files. This doesn't modify target files. - const results = await eslint.lintFiles(["lib/**/*.js"]); + // 2. Lint files. This doesn't modify target files. + const results = await eslint.lintFiles(["lib/**/*.js"]); - // 3. Modify the files with the fixed code. - await ESLint.outputFixes(results); + // 3. Modify the files with the fixed code. + await ESLint.outputFixes(results); - // 4. Format the results. - const formatter = await eslint.loadFormatter("stylish"); - const resultText = formatter.format(results); + // 4. Format the results. + const formatter = await eslint.loadFormatter("stylish"); + const resultText = formatter.format(results); - // 5. Output it. - console.log(resultText); + // 5. Output it. + console.log(resultText); })().catch((error) => { - process.exitCode = 1; - console.error(error); + process.exitCode = 1; + console.error(error); +}); +``` + +And here is an example of using the `ESLint` class with `lintText` API: + +```js +const { ESLint } = require("eslint"); + +const testCode = ` + const name = "eslint"; + if(true) { + console.log("constant condition warning") + }; +`; + +(async function main() { + // 1. Create an instance + const eslint = new ESLint({ + useEslintrc: false, + overrideConfig: { + extends: ["eslint:recommended"], + parserOptions: { + sourceType: "module", + ecmaVersion: "latest", + }, + env: { + es2022: true, + node: true, + }, + }, + }); + + // 2. Lint text. + const results = await eslint.lintText(testCode); + + // 3. Format the results. + const formatter = await eslint.loadFormatter("stylish"); + const resultText = formatter.format(results); + + // 4. Output it. + console.log(resultText); +})().catch((error) => { + process.exitCode = 1; + console.error(error); }); ``` @@ -410,8 +452,8 @@ This edit information means replacing the range of the `range` property by the ` The `LoadedFormatter` value is the object to convert the [LintResult] objects to text. The [eslint.loadFormatter()][eslint-loadformatter] method returns it. It has the following method: -* `format` (`(results: LintResult[]) => string | Promise`)
    - The method to convert the [LintResult] objects to text. +* `format` (`(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise`)
    + The method to convert the [LintResult] objects to text. `resultsMeta` is an object that will contain a `maxWarningsExceeded` object if `--max-warnings` was set and the number of warnings exceeded the limit. The `maxWarningsExceeded` object will contain two properties: `maxWarnings`, the value of the `--max-warnings` option, and `foundWarnings`, the number of lint warnings. --- @@ -464,11 +506,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. Unless you are working in the browser, you probably want to use the [ESLint class](#eslint-class) class instead. +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) 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#the-context-object)). If `cwd` is `undefined`, it will be normalized to `process.cwd()` if the global `process` object is defined (for example, in the Node.js runtime) , or `undefined` otherwise. +* `cwd` - Path to a directory that should be considered as the current working directory. It is accessible to rules from `context.cwd` or by calling `context.getCwd()` (see [The Context Object](../extend/custom-rules#the-context-object)). If `cwd` is `undefined`, it will be normalized to `process.cwd()` if the global `process` object is defined (for example, in the Node.js runtime) , or `undefined` otherwise. For example: @@ -478,7 +520,7 @@ const linter1 = new Linter({ cwd: 'path/to/project' }); const linter2 = new Linter(); ``` -In this example, rules run on `linter1` will get `path/to/project` when calling `context.getCwd()`. +In this example, rules run on `linter1` will get `path/to/project` from `context.cwd` or when calling `context.getCwd()`. Those run on `linter2` will get `process.cwd()` if the global `process` object is defined or `undefined` otherwise (e.g. on the browser ). ### Linter#verify @@ -490,8 +532,8 @@ The most important method on `Linter` is `verify()`, which initiates linting of * **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#processors-in-plugins) documentation describes as the `preprocess` method. - * `postprocess` - (optional) A function that [Processors in Plugins](/docs/developer-guide/working-with-plugins#processors-in-plugins) documentation describes as the `postprocess` method. + * `preprocess` - (optional) A function that [Processors in Plugins](../extend/plugins#processors-in-plugins) documentation describes as the `preprocess` method. + * `postprocess` - (optional) A function that [Processors in Plugins](../extend/plugins#processors-in-plugins) documentation describes as the `postprocess` method. * `filterCodeBlock` - (optional) A function that decides which code blocks the linter should adopt. The function receives two arguments. The first argument is the virtual filename of a code block. The second argument is the text of the code block. If the function returned `true` then the linter adopts the code block. If the function was omitted, the linter adopts only `*.js` code blocks. If you provided a `filterCodeBlock` function, it overrides this default behavior, so the linter doesn't adopt `*.js` code blocks automatically. * `disableFixes` - (optional) when set to `true`, the linter doesn't make either the `fix` or `suggestions` property of the lint result. * `allowInlineConfig` - (optional) set to `false` to disable inline comments from changing ESLint rules. @@ -555,7 +597,7 @@ The information available for each linting message is: * `endColumn` - the end column of the range on which the error occurred (this property is omitted if it's not range). * `endLine` - the end line of the range on which the error occurred (this property is omitted if it's not range). * `fix` - an object describing the fix for the problem (this property is omitted if no fix is available). -* `suggestions` - an array of objects describing possible lint fixes for editors to programmatically enable (see details in the [Working with Rules docs](./working-with-rules#providing-suggestions)). +* `suggestions` - an array of objects describing possible lint fixes for editors to programmatically enable (see details in the [Working with Rules docs](../extend/custom-rules#providing-suggestions)). You can get the suppressed messages from the previous run by `getSuppressedMessages()` method. If there is not a previous run, `getSuppressedMessage()` will return an empty list. @@ -687,7 +729,7 @@ Map { ### Linter#defineParser Each instance of `Linter` holds a map of custom parsers. If you want to define a parser programmatically, you can add this function -with the name of the parser as first argument and the [parser object](/docs/developer-guide/working-with-custom-parsers) as second argument. The default `"espree"` parser will already be loaded for every `Linter` instance. +with the name of the parser as first argument and the [parser object](../extend/custom-parsers) as second argument. The default `"espree"` parser will already be loaded for every `Linter` instance. ```js const Linter = require("eslint").Linter; @@ -767,7 +809,7 @@ const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } }); The `RuleTester#run()` method is used to run the tests. It should be passed the following arguments: * The name of the rule (string) -* The rule object itself (see ["working with rules"](./working-with-rules)) +* The rule object itself (see ["working with rules"](../extend/custom-rules)) * An object containing `valid` and `invalid` properties, each of which is an array containing test cases. A test case is an object with the following properties: @@ -918,23 +960,14 @@ ruleTester.run("my-rule", myRule, { --- -[configuration object]: ../user-guide/configuring/ -[builtin-formatters]: https://eslint.org/docs/user-guide/formatters/ +[configuration object]: ../use/configure/ +[builtin-formatters]: ../use/formatters/ [third-party-formatters]: https://www.npmjs.com/search?q=eslintformatter -[eslint]: #eslint-class -[eslint-constructor]: #-new-eslintoptions [eslint-lintfiles]: #-eslintlintfilespatterns [eslint-linttext]: #-eslintlinttextcode-options -[eslint-getrulesmetaforresults]: #-eslintgetrulesmetaforresultsresults -[eslint-calculateconfigforfile]: #-eslintcalculateconfigforfilefilepath -[eslint-ispathignored]: #-eslintispathignoredfilepath [eslint-loadformatter]: #-eslintloadformatternameorpath -[eslint-version]: #-eslintversion -[eslint-outputfixes]: #-eslintoutputfixesresults -[eslint-geterrorresults]: #-eslintgeterrorresultsresults [lintresult]: #-lintresult-type [lintmessage]: #-lintmessage-type [suppressedlintmessage]: #-suppressedlintmessage-type [editinfo]: #-editinfo-type [loadedformatter]: #-loadedformatter-type -[linter]: #linter diff --git a/eslint/docs/src/library/rule-categories.md b/eslint/docs/src/library/rule-categories.md index 9f6688f..8934fe7 100644 --- a/eslint/docs/src/library/rule-categories.md +++ b/eslint/docs/src/library/rule-categories.md @@ -4,7 +4,7 @@ title: Rule categories ## Rule categories -The rule categories—namely “recommended”, “fixable”, and “hasSuggestions”—are shown in the [rules page](/rules/). They are rendered using the `ruleCategories` macro (imported from `/components/rule-categories.macro.html`). There is also an individual macro for each category type. +The rule categories—namely “recommended”, “fixable”, and “hasSuggestions”—are shown in the [rules page](../rules/). They are rendered using the `ruleCategories` macro (imported from `/components/rule-categories.macro.html`). There is also an individual macro for each category type. ```html { % from 'components/rule-categories.macro.html' import ruleCategories % } diff --git a/eslint/docs/src/maintain/index.md b/eslint/docs/src/maintain/index.md new file mode 100644 index 0000000..29c3ab8 --- /dev/null +++ b/eslint/docs/src/maintain/index.md @@ -0,0 +1,30 @@ +--- +title: Maintain ESLint +eleventyNavigation: + key: maintain eslint + title: Maintain ESLint + order: 4 + +--- + +This guide is intended for those who work as part of the ESLint project team. + +## [How ESLint is Maintained](overview) + +Explains how ESLint is maintained, including information about team, governance, and funding. + +## [Manage Issues](manage-issues) + +Describes how to deal with issues when they're opened, how to interact with users who open issues, and how to close issues effectively. + +## [Review Pull Requests](review-pull-requests) + +Describes how to review incoming pull requests. + +## [Manage Releases](manage-releases) + +Describes how to do an ESLint project release. + +## [Working Groups](working-groups) + +Describes how working groups are created and how they function within the ESLint project. diff --git a/eslint/docs/src/maintainer-guide/issues.md b/eslint/docs/src/maintain/manage-issues.md similarity index 67% rename from eslint/docs/src/maintainer-guide/issues.md rename to eslint/docs/src/maintain/manage-issues.md index ca6abf3..803451b 100644 --- a/eslint/docs/src/maintainer-guide/issues.md +++ b/eslint/docs/src/maintain/manage-issues.md @@ -1,11 +1,10 @@ --- -title: Managing Issues -layout: doc +title: Manage Issues eleventyNavigation: - key: managing issues - parent: maintainer guide - title: Managing Issues - order: 1 + key: manage issues + parent: maintain eslint + title: Manage Issues + order: 2 --- @@ -13,7 +12,7 @@ New issues are filed frequently, and how we respond to those issues directly aff ## Things to Keep in Mind -1. **Be nice.** Even if the people are being rude or aggressive on an issue, as a project team member you must be the mature one in the conversation. Do your best to work with everyone no matter their style. Remember, poor wording choice can also be a sign of someone who doesn't know English very well, so be sure to consider that when trying to determine the tone of someone's message. Being rude, even when someone is being rude to you, reflects poorly on the team and the project as a whole. +1. **Be nice.** Even if the people are being rude or aggressive on an issue, you must be the mature one in the conversation as a project team member. Do your best to work with everyone no matter their style. Remember, poor wording choice can also be a sign of someone who doesn't know English very well, so be sure to consider that when trying to determine the tone of someone's message. Being rude, even when someone is being rude to you, reflects poorly on the team and the project as a whole. 1. **Be inquisitive.** Ask questions on the issue whenever something isn't clear. Don't assume you understand what's being reported if there are details missing. Whenever you are unsure, it's best to ask for more information. 1. **Not all requests are equal.** It's unlikely we'll be able to accommodate every request, so don't be afraid to say that something doesn't fit into the scope of the project or isn't practical. It's better to give such feedback if that's the case. 1. **Close when appropriate.** Don't be afraid to close issues that you don't think will be done, or when it's become clear from the conversation that there's no further work to do. Issues can always be reopened if they are closed incorrectly, so feel free to close issues when appropriate. Just be sure to leave a comment explaining why the issue is being closed (if not closed by a commit). @@ -22,28 +21,28 @@ New issues are filed frequently, and how we respond to those issues directly aff There are four primary issue categories: -1. **Bug** - something isn't working the way it's expected to work. -1. **Enhancement** - a change to something that already exists. For instance, adding a new option to an existing rule or a bug in a rule where fixing it will result in the rule reporting more problems (in this case, use both "Bug" and "Enhancement"). -1. **Feature** - adding something that doesn't already exist. For example, adding a new rule, new formatter, or new command line flag. -1. **Question** - an inquiry about how something works that won't result in a code change. We'd prefer if people use the mailing list or chatroom for questions, but sometimes they'll open an issue. +1. **Bug**: Something isn't working the way it's expected to work. +1. **Enhancement**: A change to something that already exists. For instance, adding a new option to an existing rule or fixing a bug in a rule where fixing it will result in the rule reporting more problems (in this case, use both "Bug" and "Enhancement"). +1. **Feature**: Adding something that doesn't already exist. For example, adding a new rule, new formatter, or new command line flag. +1. **Question**: An inquiry about how something works that won't result in a code change. We prefer if people use GitHub Discussions or Discord for questions, but sometimes they'll open an issue. The first goal when evaluating an issue is to determine which category the issue falls into. ## Triaging Process -All of ESLint's issues, across all GitHub repositories, are managed on our [Triage Project](https://github.com/orgs/eslint/projects/2). Please use the Triage project instead of the issues list when reviewing issues to determine what to work on. The Triage project has several columns: +All of ESLint's issues, across all GitHub repositories, are managed on our [Triage Project](https://github.com/orgs/eslint/projects/3). Please use the Triage project instead of the issues list when reviewing issues to determine what to work on. The Triage project has several columns: -* **Needs Triage** - issues that have not yet been reviewed by anyone -* **Triaging** - issues that someone has reviewed but has not been able to fully triage yet -* **Ready for Dev Team** - issues that have been triaged and have all of the information necessary for the dev team to take a look -* **Evaluating** - the dev team is evaluating these issues to determine whether to move forward or not -* **Feedback Needed** - a team member is requesting more input from the rest of the team before proceeding -* **Waiting for RFC** - the next step in the process is for an RFC to be written -* **RFC Opened** - an RFC is opened to address these issues -* **Blocked** - the issue can't move forward due to some dependency -* **Ready to Implement** - these issues have all of the details necessary to start implementation -* **PR Opened** - there is an open pull request for each of these issues -* **Completed** - the issue has been closed (either via pull request merge or by the team manually closing the issue) +* **Needs Triage**: Issues that have not yet been reviewed by anyone +* **Triaging**: Issues that someone has reviewed but has not been able to fully triage yet +* **Ready for Dev Team**: Issues that have been triaged and have all the information necessary for the dev team to take a look +* **Evaluating**: The dev team is evaluating these issues to determine whether to move forward or not +* **Feedback Needed**: A team member is requesting more input from the rest of the team before proceeding +* **Waiting for RFC**: The next step in the process is for an RFC to be written +* **RFC Opened**: An RFC is opened to address these issues +* **Blocked**: The issue can't move forward due to some dependency +* **Ready to Implement**: These issues have all the details necessary to start implementation +* **Implementing**: There is an open pull request for each of these issues +* **Completed**: The issue has been closed (either via pull request merge or by the team manually closing the issue) We make every attempt to automate movement between as many columns as we can, but sometimes moving issues needs to be done manually. @@ -51,12 +50,12 @@ We make every attempt to automate movement between as many columns as we can, bu When an issue is opened, it is automatically added to the "Needs Triage" column in the Triage project. These issues need to be evaluated to determine next steps. Anyone on the support team or dev team can follow these steps to properly triage issues. -**Note:** If an issue is in the "Triaging" column, that means someone is already triaging it and you should let them finish. There's no need to comment on issues in the "Triaging" column unless someone asks for help. +**Note:** If an issue is in the "Triaging" column, that means someone is already triaging it, and you should let them finish. There's no need to comment on issues in the "Triaging" column unless someone asks for help. The steps for triaging an issue are: -1. Move the issue from "Needs Triage" to "Triaging" in the Triage project -1. Check: Has all of the information in the issue template been provided? +1. Move the issue from "Needs Triage" to "Triaging" in the Triage project. +1. Check: Has all the information in the issue template been provided? * **No:** If information is missing from the issue template, or you can't tell what is being requested, please ask the author to provide the missing information: * Add the "needs info" label to the issue so we know that this issue is stalled due to lack of information. * Don't move on to other steps until the necessary information has been provided. @@ -65,16 +64,26 @@ The steps for triaging an issue are: * If the issue is actually a question (rather than something the dev team needs to change), please [convert it to a discussion](https://docs.github.com/en/free-pro-team@latest/discussions/managing-discussions-for-your-community/moderating-discussions#converting-an-issue-to-a-discussion). You can continue the conversation as a discussion. * If the issue is reporting a bug, try to reproduce the issue following the instructions in the issue. If you can reproduce the bug, please add the "repro:yes" label. (The bot will automatically remove the "repro:needed" label.) If you can't reproduce the bug, ask the author for more information about their environment or to clarify reproduction steps. * If the issue is reporting something that works as intended, please add the "works as intended" label and close the issue. - * For all issues, please add labels describing the part of ESLint affected: - * "3rd party plugin" - related to third-party functionality (plugins, parsers, rules, etc.) - * "build" - related to commands run during a build (testing, linting, release scripts, etc.) - * "cli" - related to command line input or output, or to `CLIEngine` - * "core" - related to internal APIs - * "documentation" - related to content on eslint.org - * "infrastructure" - related to resources needed for builds or deployment (VMs, CI tools, bots, etc.) - * "rule" - related to core rules - * If you can't properly triage the issue, move the issue back to the "Needs Triage" column in the Triage project so someone else can triage it - * If you have triaged the issue, move the issue to the "Ready for Dev Team" column in the Triage project + * Please add labels describing the part of ESLint affected: + * **3rd party plugin**: Related to third-party functionality (plugins, parsers, rules, etc.) + * **build**: Related to commands run during a build (testing, linting, release scripts, etc.) + * **cli**: Related to command line input or output, or to `CLIEngine` + * **core**: Related to internal APIs + * **documentation**: Related to content on eslint.org + * **infrastructure**: Related to resources needed for builds or deployment (VMs, CI tools, bots, etc.) + * **rule**: Related to core rules + * Please assign an initial priority based on the importance of the issue. If you're not sure, use your best judgment. We can always change the priority later. + * **P1**: Urgent and important, we need to address this immediately. + * **P2**: Important but not urgent. Should be handled by a TSC member or reviewer. + * **P3**: Nice to have but not important. Can be handled by any team member. + * **P4**: A good idea that we'd like to have but may take a while for the team to get to it. + * **P5**: A good idea that the core team can't commit to. Will likely need to be done by an outside contributor. + * Please assign an initial impact assessement (make your best guess): + * **Low**: Doesn't affect many users. + * **Medium**: Affects most users or has a noticeable effect on user experience. + * **High**: Affects a lot of users, is a breaking change, or otherwise will be very noticeable to users. + * If you can't properly triage the issue, move the issue back to the "Needs Triage" column in the Triage project so someone else can triage it. + * If you have triaged the issue, move the issue to the "Ready for Dev Team" column in the Triage project. ## Evaluation Process @@ -82,12 +91,12 @@ When an issue has been moved to the "Ready for Dev Team" column, any dev team me 1. Move the issue into the "Evaluating" column. 1. Next steps: - * **Bugs:** if you can verify the bug, add the "accepted" label and ask if they would like to submit a pull request. - * **New Rules:** if you are willing to champion the rule (meaning you believe it should be included in ESLint core and you will take ownership of the process for including it), add a comment saying you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. - * **Rule Changes:** if you are willing to champion the change and it would not be a breaking change (requiring a major version increment), add a comment saying that you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. - * **Breaking Changes:** if you suspect or can verify that a change would be breaking, label it as "Breaking". - * **Duplicates:** if you can verify the issue is a duplicate, add a comment mentioning the duplicate issue (such as, "Duplicate of #1234") and close the issue. -1. Regardless of the above, always leave a comment. Don't just add labels, engage with the person who opened the issue by asking a question (request more information if necessary) or stating your opinion of the issue. If it's a verified bug, ask if the user would like to submit a pull request. + * **Bugs**: If you can verify the bug, add the "accepted" label and ask if they would like to submit a pull request. + * **New Rules**: If you are willing to champion the rule (meaning you believe it should be included in ESLint core and you will take ownership of the process for including it), add a comment saying you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. + * **Rule Changes**: If you are willing to champion the change and it would not be a breaking change (requiring a major version increment), add a comment saying that you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. + * **Breaking Changes**: If you suspect or can verify that a change would be breaking, label it as "Breaking". + * **Duplicates**: If you can verify the issue is a duplicate, add a comment mentioning the duplicate issue (such as, "Duplicate of #1234") and close the issue. +1. Regardless of the above, always leave a comment. Don't just add labels; engage with the person who opened the issue by asking a question (request more information if necessary) or stating your opinion of the issue. If it's a verified bug, ask if the user would like to submit a pull request. 1. If the issue can't be implemented because it needs an external dependency to be updated or needs to wait for another issue to be resolved, move the issue to the "Blocked" column. 1. If the issue has been accepted and an RFC is required as the next step, move the issue to the "Waiting for RFC" column and comment on the issue that an RFC is needed. @@ -111,7 +120,7 @@ New rules and rule changes require a champion. As champion, it's your job to: * Gain [consensus](#consensus) from the ESLint team on inclusion * Guide the rule creation process until it's complete (so only champion a rule that you have time to implement or help another contributor implement) -Once consensus has been reached on inclusion, add the "accepted" and, optionally, "help wanted" and "good first issue" labels, as necessary. +Once consensus has been reached on inclusion, add the "accepted" label. Optionally, add "help wanted" and "good first issue" labels, as necessary. ## Consensus @@ -121,8 +130,8 @@ Consensus is reached on issues when there are at least three team members who be If consensus cannot be reached on an issue, or an issue's progress has been stalled and it's not clear if the issue should be closed, then you can refer the issue to the TSC for resolution. To do so, add the "tsc agenda" label to the issue and add a comment including the following information: -1. A one-paragraph summary of the discussion to this point. -2. The question you would like the TSC to answer. +1. A one-paragraph summary of the discussion to this point. This should begin with "TSC Summary:". +2. The question you would like the TSC to answer. This should begin with "TSC Question:". The issue will be discussed at the next TSC meeting and the resolution will be posted back to the issue. @@ -130,9 +139,9 @@ The issue will be discussed at the next TSC meeting and the resolution will be p In addition to the above, changes to the core (including CLI changes) that would result in a minor or major version release must be approved by the TSC by standard TSC motion. Add the label "tsc agenda" to the issue and it will be discussed at the next TSC meeting. In general, requests should meet the following criteria to be considered: -1. The feature or enhancement is in scope for the project and should be added to the roadmap -1. Someone is committed to including the change within the next year -1. There is reasonable certainty about who will do the work +1. The feature or enhancement is in scope for the project and should be added to the roadmap. +1. Someone is committed to including the change within the next year. +1. There is reasonable certainty about who will do the work. When a suggestion is too ambitious or would take too much time to complete, it's better not to accept the proposal. Stick to small, incremental changes and lay out a roadmap of where you'd like the project to go eventually. Don't let the project get bogged down in big features that will take a long time to complete. diff --git a/eslint/docs/src/maintainer-guide/releases.md b/eslint/docs/src/maintain/manage-releases.md similarity index 67% rename from eslint/docs/src/maintainer-guide/releases.md rename to eslint/docs/src/maintain/manage-releases.md index 975717d..7a5e763 100644 --- a/eslint/docs/src/maintainer-guide/releases.md +++ b/eslint/docs/src/maintain/manage-releases.md @@ -1,11 +1,10 @@ --- -title: Managing Releases -layout: doc +title: Manage Releases eleventyNavigation: - key: managing releases - parent: maintainer guide - title: Managing Releases - order: 3 + key: manage releases + parent: maintain eslint + title: Manage Releases + order: 4 --- @@ -14,18 +13,20 @@ Releases are when a project formally publishes a new version so the community ca * Regular releases that follow [semantic versioning](https://semver.org/) and are considered production-ready. * Prereleases that are not considered production-ready and are intended to give the community a preview of upcoming changes. -## Release Team +## Release Manager -A two-person release team is assigned to each scheduled release. This two-person team is responsible for: +One member of the Technical Steering Committee (TSC) is assigned to manage each scheduled release. The release manager is determined at the TSC meeting the day before the release. + +The release manager is responsible for: 1. The scheduled release on Friday 1. Monitoring issues over the weekend 1. Determining if a patch release is necessary on Monday 1. Publishing the patch release (if necessary) -The two-person team should seek input from the whole team on the Monday following a release to double-check if a patch release is necessary. +The release manager should seek input from the whole team on the Monday following a release to double-check if a patch release is necessary. -At least one member of the release team needs to have access to eslint's two-factor authentication for npm in order to do a release. +The release manager needs to have access to ESLint's two-factor authentication for npm in order to do a release. ## Release Communication @@ -33,10 +34,10 @@ Each scheduled release should be associated with a release issue ([example](http ## Process -On the day of a scheduled release, the release team should follow these steps: +On the day of a scheduled release, the release manager should follow these steps: 1. Review open pull requests to see if any should be merged. In general, you can merge pull requests that: - * Have been open at least two days and have been reviewed (these are just waiting for merge). + * Have been open for at least two days and approved (these are just waiting for merge). * Important pull requests (as determined by the team). You should stop and have people review before merging if they haven't been already. * Documentation changes. * Small bugfixes written by a team member. @@ -50,19 +51,23 @@ On the day of a scheduled release, the release team should follow these steps: 1. Make a release announcement on the release issue. Document any problems that occurred during the release, and remind the team not to merge anything other than documentation changes and bugfixes. Leave the release issue open. 1. Add the `patch release pending` label to the release issue. (When this label is present, `eslint-github-bot` will create a pending status check on non-semver-patch pull requests, to ensure that they aren't accidentally merged while a patch release is pending.) -On the Monday following the scheduled release, the release team needs to determine if a patch release is necessary. A patch release is considered necessary if any of the following occurred since the scheduled release: +All release-related communications occur in the `#team` channel on Discord. + +On the Monday following the scheduled release, the release manager needs to determine if a patch release is necessary. A patch release is considered necessary if any of the following occurred since the scheduled release: * A regression bug is causing people's lint builds to fail when it previously passed. * Any bug that is causing a lot of problems for users (frequently happens due to new functionality). The patch release decision should be made as early on Monday as possible. If a patch release is necessary, then follow the same steps as the scheduled release process. -In rare cases, a second patch release might be necessary if the release is known to have a severe regression that hasn't been fixed by Monday. If this occurs, the release team should announce the situation on the release issue, and leave the issue open until all patch releases are complete. However, it's usually better to fix bugs for the next release cycle rather than doing a second patch release. +In rare cases, a second patch release might be necessary if the release is known to have a severe regression that hasn't been fixed by Monday. If this occurs, the release manager should announce the situation on the release issue, and leave the issue open until all patch releases are complete. However, it's usually better to fix bugs for the next release cycle rather than doing a second patch release. After the patch release has been published (or no patch release is necessary), close the release issue and inform the team that they can start merging in semver-minor changes again. ## Emergency Releases -In general, we try not to do emergency releases (an emergency release is unplanned and isn't the regularly scheduled release or the anticipated patch release). Even if there is a regression, it's best to wait the weekend to see if any other problems arise so a patch release can fix as many issues as possible. +An emergency release is unplanned and isn't the regularly scheduled release or the anticipated patch release. + +In general, we try not to do emergency releases. Even if there is a regression, it's best to wait until Monday to see if any other problems arise so a patch release can fix as many issues as possible. The only real exception is if ESLint is completely unusable by most of the current users. For instance, we once pushed a release that errored for everyone because it was missing some core files. In that case, an emergency release is appropriate. diff --git a/eslint/docs/src/maintain/overview.md b/eslint/docs/src/maintain/overview.md new file mode 100644 index 0000000..362637a --- /dev/null +++ b/eslint/docs/src/maintain/overview.md @@ -0,0 +1,44 @@ +--- +title: How ESLint is Maintained +eleventyNavigation: + key: how eslint is maintained + parent: maintain eslint + title: How ESLint is Maintained + order: 1 + +--- + +This page explains the different roles and structures involved in maintaining ESLint. + +## The ESLint Team + +The ESLint team works together to develop and maintain ESLint. To learn more about the different roles on the ESLint team, refer to [Governance](../contribute/governance). To see the current team members, refer to [Team](/team/). + +## Organization Structure + +ESLint is part of the [OpenJS Foundation](https://openjsf.org/), a nonprofit organization that supports open-source projects and communities in the JavaScript ecosystem. + +The OpenJS Foundation provides legal infrastructure for JavaScript projects like ESLint. It is the owner of the intellectual property related to ESLint, including copyrights and trademarks, and ensures the independence of the project. They are also a resource for ESLint if we need legal advice or representation. + +The OpenJS Foundation does not participate in the day-to-day functioning of ESLint. + +## Funding + +ESLint is funded through several sources, including: + +* [**Open Collective**](https://opencollective.com/eslint): A platform for financing open source projects. +* [**GitHub Sponsors**](https://github.com/sponsors/eslint): A platform for funding open source projects associated with Github. +* [**Tidelift**](https://tidelift.com/subscription/pkg/npm-eslint): A subscription service that lets enterprises manage and fund the open source projects that their organization uses. +* [**Carbon Ads**](https://www.carbonads.net/open-source): Developer-centric advertising provider used on [eslint.org](https://eslint.org/). +* [**Stackaid.us**](https://simulation.stackaid.us/github/eslint/eslint): Tool that developers can use to allocate funding to the open source projects they use. + +ESLint uses this funding for the following purposes: + +* Pay team members and contractors +* Fund projects +* Pay for services that keep ESLint running (web hosting, software subscriptions, etc.) +* Provide financial support to our dependencies and ecosystem + +## Joining the Maintainer Team + +ESLint is an open-source project, and anyone can contribute to the project. If you're interested in becoming part of the maintainer team, stop by our [Discord](https://eslint.org/chat) and introduce yourself. diff --git a/eslint/docs/src/maintainer-guide/pullrequests.md b/eslint/docs/src/maintain/review-pull-requests.md similarity index 89% rename from eslint/docs/src/maintainer-guide/pullrequests.md rename to eslint/docs/src/maintain/review-pull-requests.md index 3c0b064..c1711df 100644 --- a/eslint/docs/src/maintainer-guide/pullrequests.md +++ b/eslint/docs/src/maintain/review-pull-requests.md @@ -1,11 +1,10 @@ --- -title: Reviewing Pull Requests -layout: doc +title: Review Pull Requests eleventyNavigation: - key: reviewing pull requests - parent: maintainer guide - title: Reviewing Pull Requests - order: 2 + key: review pull requests + parent: maintain eslint + title: Review Pull Requests + order: 3 --- @@ -29,10 +28,10 @@ Once the bot checks have been satisfied, you check the following: 1. Double-check that the commit message tag ("Fix:", "New:", etc.) is correct based on the issue (or, if no issue is referenced, based on the stated problem). 1. If the pull request makes a change to core, ensure that an issue exists and the pull request references the issue in the commit message. -1. Does the code follow our conventions (including header comments, JSDoc comments, etc.)? If not, please leave that feedback and reference the conventions document. +1. Does the code follow our conventions (including header comments, JSDoc comments, etc.)? If not, please leave that feedback and reference the [Code Conventions](../contribute/code-conventions) documentation. 1. For code changes: * Are there tests that verify the change? If not, please ask for them. - * Is documentation needed for the change? If yes, please let the submitter know. + * Is documentation needed for the change? If yes, please ask the submitter to add the necessary documentation. 1. Are there any automated testing errors? If yes, please ask the submitter to check on them. 1. If you've reviewed the pull request and there are no outstanding issues, leave a comment "LGTM" to indicate your approval. If you would like someone else to verify the change, comment "LGTM but would like someone else to verify." @@ -92,17 +91,17 @@ If the pull request was created from a branch on the `eslint/eslint` repository There are several times when it's appropriate to close a pull request without merging: -1. The pull request addresses an issue that is already fixed -1. The pull request hasn't been updated in 30 days +1. The pull request addresses an issue that is already fixed. +1. The pull request hasn't been updated in 17 days. 1. The pull request submitter isn't willing to follow project guidelines. In any of these cases, please be sure to leave a comment stating why the pull request is being closed. ### Example Closing Comments -If a pull request hasn't been updated in 30 days: +If a pull request hasn't been updated in 17 days: -> Closing because there hasn't been activity for 30 days. If you're still interested in submitting this code, please feel free to resubmit. +> Closing because there hasn't been activity for 17 days. If you're still interested in submitting this code, please feel free to resubmit. If a pull request submitter isn't willing to follow project guidelines. diff --git a/eslint/docs/src/maintainer-guide/working-groups.md b/eslint/docs/src/maintain/working-groups.md similarity index 77% rename from eslint/docs/src/maintainer-guide/working-groups.md rename to eslint/docs/src/maintain/working-groups.md index 4a79ff4..659ac04 100644 --- a/eslint/docs/src/maintainer-guide/working-groups.md +++ b/eslint/docs/src/maintain/working-groups.md @@ -1,10 +1,13 @@ --- title: Working Groups -layout: doc - +eleventyNavigation: + key: working groups + parent: maintain eslint + title: Working Groups + order: 5 --- -The ESLint TSC may form working groups to focus on a specific area of the project. +The ESLint [Technical Steering Committee](../contribute/governance#technical-steering-committee-tsc) (TSC) may form working groups to focus on a specific area of the project. ## Creating a Working Group diff --git a/eslint/docs/src/maintainer-guide/index.md b/eslint/docs/src/maintainer-guide/index.md deleted file mode 100644 index ecac32f..0000000 --- a/eslint/docs/src/maintainer-guide/index.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Maintainer Guide -layout: doc -eleventyNavigation: - key: maintainer guide - title: Maintainer Guide - order: 3 - ---- - -This guide is intended for those who work as part of the ESLint project team. - -## [Managing Issues](issues) - -Describes how to deal with issues when they're opened, when interacting with users, and how to close them effectively. - -## [Reviewing Pull Requests](pullrequests) - -Describes how to review incoming pull requests. - -## [Managing Releases](releases) - -Describes how to do an ESLint project release. - -## [Governance](governance) - -Describes the governance policy for ESLint, including the rights and privileges of individuals inside the project. - -## [Working Groups](working-groups) - -Describes how working groups are created and how they function within the ESLint project. diff --git a/eslint/docs/src/pages/index.md b/eslint/docs/src/pages/index.md index 43c7954..c8f5f62 100644 --- a/eslint/docs/src/pages/index.md +++ b/eslint/docs/src/pages/index.md @@ -1,21 +1,23 @@ --- title: Documentation -layout: doc permalink: /index.html --- Welcome to our documentation pages! What would you like to view? -## [User Guide](user-guide/) +## [Use ESLint in Your Project](use/) Intended for end users of ESLint. Contains information about core rules, configuration, command line options, formatters, and integrations, as well as guides for migrating from earlier versions of ESLint. -## [Developer Guide](developer-guide/) +## [Extend ESLint](extend/) -Intended for contributors to ESLint and people who wish to extend ESLint. Contains information about contributing to ESLint; creating custom -rules, configurations, plugins, and formatters; and information about our architecture and Node.js API. +Intended for people who wish to extend ESLint. Contains information about creating custom rules, configurations, plugins, and formatters; and information about our Node.js API. -## [Maintainer Guide](maintainer-guide/) +## [Contribute to ESLint](contribute/) + +Intended for people who wish to contribute to the ESLint project. Contains information about ways you can contribute, the project structure, and setting up the development environment. + +## [Maintain ESLint](maintain/) Intended for maintainers of ESLint. diff --git a/eslint/docs/src/pages/rules.md b/eslint/docs/src/pages/rules.md index 7ede8bf..996860e 100644 --- a/eslint/docs/src/pages/rules.md +++ b/eslint/docs/src/pages/rules.md @@ -1,12 +1,11 @@ --- -title: Rules -layout: doc +title: Rules Reference permalink: /rules/index.html eleventyNavigation: key: rules - parent: user guide - title: Rules - order: 4 + parent: use eslint + title: Rules Reference + order: 5 --- {% from 'components/rule-categories.macro.html' import ruleCategories, recommended, fixable, hasSuggestions %} diff --git a/eslint/docs/src/rules/accessor-pairs.md b/eslint/docs/src/rules/accessor-pairs.md index 638bbc8..b84205d 100644 --- a/eslint/docs/src/rules/accessor-pairs.md +++ b/eslint/docs/src/rules/accessor-pairs.md @@ -1,6 +1,5 @@ --- title: accessor-pairs -layout: doc rule_type: suggestion related_rules: - no-dupe-keys diff --git a/eslint/docs/src/rules/array-bracket-newline.md b/eslint/docs/src/rules/array-bracket-newline.md index 16e80e1..4ad7e27 100644 --- a/eslint/docs/src/rules/array-bracket-newline.md +++ b/eslint/docs/src/rules/array-bracket-newline.md @@ -1,6 +1,5 @@ --- title: array-bracket-newline -layout: doc rule_type: layout related_rules: - array-bracket-spacing diff --git a/eslint/docs/src/rules/array-bracket-spacing.md b/eslint/docs/src/rules/array-bracket-spacing.md index 275b32f..5b6dd8a 100644 --- a/eslint/docs/src/rules/array-bracket-spacing.md +++ b/eslint/docs/src/rules/array-bracket-spacing.md @@ -1,6 +1,5 @@ --- title: array-bracket-spacing -layout: doc rule_type: layout related_rules: - space-in-parens diff --git a/eslint/docs/src/rules/array-callback-return.md b/eslint/docs/src/rules/array-callback-return.md index b3b0fba..965d185 100644 --- a/eslint/docs/src/rules/array-callback-return.md +++ b/eslint/docs/src/rules/array-callback-return.md @@ -1,6 +1,5 @@ --- title: array-callback-return -layout: doc rule_type: problem --- @@ -27,6 +26,8 @@ This rule finds callback functions of the following methods, then checks usage o * [`Array.prototype.filter`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.filter) * [`Array.prototype.find`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.find) * [`Array.prototype.findIndex`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.findindex) +* [`Array.prototype.findLast`](https://tc39.es/ecma262/#sec-array.prototype.findlast) +* [`Array.prototype.findLastIndex`](https://tc39.es/ecma262/#sec-array.prototype.findlastindex) * [`Array.prototype.flatMap`](https://www.ecma-international.org/ecma-262/10.0/#sec-array.prototype.flatmap) * [`Array.prototype.forEach`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.foreach) (optional, based on `checkForEach` parameter) * [`Array.prototype.map`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.map) @@ -34,6 +35,7 @@ This rule finds callback functions of the following methods, then checks usage o * [`Array.prototype.reduceRight`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.reduceright) * [`Array.prototype.some`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.some) * [`Array.prototype.sort`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.sort) +* [`Array.prototype.toSorted`](https://tc39.es/ecma262/#sec-array.prototype.tosorted) * And above of typed arrays. Examples of **incorrect** code for this rule: diff --git a/eslint/docs/src/rules/array-element-newline.md b/eslint/docs/src/rules/array-element-newline.md index 73b545a..2b34bd5 100644 --- a/eslint/docs/src/rules/array-element-newline.md +++ b/eslint/docs/src/rules/array-element-newline.md @@ -1,6 +1,5 @@ --- title: array-element-newline -layout: doc rule_type: layout related_rules: - array-bracket-spacing diff --git a/eslint/docs/src/rules/arrow-body-style.md b/eslint/docs/src/rules/arrow-body-style.md index 1964b1a..1975af1 100644 --- a/eslint/docs/src/rules/arrow-body-style.md +++ b/eslint/docs/src/rules/arrow-body-style.md @@ -1,6 +1,5 @@ --- title: arrow-body-style -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/arrow-parens.md b/eslint/docs/src/rules/arrow-parens.md index b3214ce..8b956c3 100644 --- a/eslint/docs/src/rules/arrow-parens.md +++ b/eslint/docs/src/rules/arrow-parens.md @@ -1,6 +1,5 @@ --- title: arrow-parens -layout: doc rule_type: layout further_reading: - https://github.com/airbnb/javascript#arrows--one-arg-parens @@ -134,7 +133,7 @@ var b = 0; if ((a) => b) { console.log('truthy value returned'); } else { - console.log('falsey value returned'); + console.log('falsy value returned'); } // outputs 'truthy value returned' ``` diff --git a/eslint/docs/src/rules/arrow-spacing.md b/eslint/docs/src/rules/arrow-spacing.md index e24b168..fa5a424 100644 --- a/eslint/docs/src/rules/arrow-spacing.md +++ b/eslint/docs/src/rules/arrow-spacing.md @@ -1,6 +1,5 @@ --- title: arrow-spacing -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/block-scoped-var.md b/eslint/docs/src/rules/block-scoped-var.md index 566d602..699ce70 100644 --- a/eslint/docs/src/rules/block-scoped-var.md +++ b/eslint/docs/src/rules/block-scoped-var.md @@ -1,6 +1,5 @@ --- title: block-scoped-var -layout: doc rule_type: suggestion further_reading: - https://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html diff --git a/eslint/docs/src/rules/block-spacing.md b/eslint/docs/src/rules/block-spacing.md index 99b3540..415fc7d 100644 --- a/eslint/docs/src/rules/block-spacing.md +++ b/eslint/docs/src/rules/block-spacing.md @@ -1,6 +1,5 @@ --- title: block-spacing -layout: doc rule_type: layout related_rules: - space-before-blocks diff --git a/eslint/docs/src/rules/brace-style.md b/eslint/docs/src/rules/brace-style.md index ccca32f..456ccf2 100644 --- a/eslint/docs/src/rules/brace-style.md +++ b/eslint/docs/src/rules/brace-style.md @@ -1,6 +1,5 @@ --- title: brace-style -layout: doc rule_type: layout related_rules: - block-spacing diff --git a/eslint/docs/src/rules/callback-return.md b/eslint/docs/src/rules/callback-return.md index 2b7ded0..92ec026 100644 --- a/eslint/docs/src/rules/callback-return.md +++ b/eslint/docs/src/rules/callback-return.md @@ -1,6 +1,5 @@ --- title: callback-return -layout: doc rule_type: suggestion related_rules: - handle-callback-err @@ -10,7 +9,7 @@ further_reading: --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). The callback pattern is at the heart of most I/O and event-driven programming in JavaScript. diff --git a/eslint/docs/src/rules/camelcase.md b/eslint/docs/src/rules/camelcase.md index 7678aab..46757fd 100644 --- a/eslint/docs/src/rules/camelcase.md +++ b/eslint/docs/src/rules/camelcase.md @@ -1,6 +1,5 @@ --- title: camelcase -layout: doc rule_type: suggestion --- @@ -127,6 +126,8 @@ Examples of **correct** code for this rule with the `{ "properties": "never" }` var obj = { my_pref: 1 }; + +obj.foo_bar = "baz"; ``` ::: diff --git a/eslint/docs/src/rules/capitalized-comments.md b/eslint/docs/src/rules/capitalized-comments.md index 8c62ae5..66d03d7 100644 --- a/eslint/docs/src/rules/capitalized-comments.md +++ b/eslint/docs/src/rules/capitalized-comments.md @@ -1,6 +1,5 @@ --- title: capitalized-comments -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/class-methods-use-this.md b/eslint/docs/src/rules/class-methods-use-this.md index 49c74e6..2269fac 100644 --- a/eslint/docs/src/rules/class-methods-use-this.md +++ b/eslint/docs/src/rules/class-methods-use-this.md @@ -1,6 +1,5 @@ --- title: class-methods-use-this -layout: doc rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes @@ -119,9 +118,9 @@ This rule has two options: "class-methods-use-this": [, { "exceptMethods": [<...exceptions>] }] ``` -The `exceptMethods` option allows you to pass an array of method names for which you would like to ignore warnings. For example, you might have a spec from an external library that requires you to overwrite a method as a regular function (and not as a static method) and does not use `this` inside the function body. In this case, you can add that method to ignore in the warnings. +The `"exceptMethods"` option allows you to pass an array of method names for which you would like to ignore warnings. For example, you might have a spec from an external library that requires you to overwrite a method as a regular function (and not as a static method) and does not use `this` inside the function body. In this case, you can add that method to ignore in the warnings. -Examples of **incorrect** code for this rule when used without exceptMethods: +Examples of **incorrect** code for this rule when used without `"exceptMethods"`: ::: incorrect diff --git a/eslint/docs/src/rules/comma-dangle.md b/eslint/docs/src/rules/comma-dangle.md index b3f27a2..1370874 100644 --- a/eslint/docs/src/rules/comma-dangle.md +++ b/eslint/docs/src/rules/comma-dangle.md @@ -1,6 +1,5 @@ --- title: comma-dangle -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/comma-spacing.md b/eslint/docs/src/rules/comma-spacing.md index 169077d..b693785 100644 --- a/eslint/docs/src/rules/comma-spacing.md +++ b/eslint/docs/src/rules/comma-spacing.md @@ -1,6 +1,5 @@ --- title: comma-spacing -layout: doc rule_type: layout related_rules: - array-bracket-spacing diff --git a/eslint/docs/src/rules/comma-style.md b/eslint/docs/src/rules/comma-style.md index ef3b358..99d14b2 100644 --- a/eslint/docs/src/rules/comma-style.md +++ b/eslint/docs/src/rules/comma-style.md @@ -1,6 +1,5 @@ --- title: comma-style -layout: doc rule_type: layout related_rules: - operator-linebreak diff --git a/eslint/docs/src/rules/complexity.md b/eslint/docs/src/rules/complexity.md index 33b367b..08dd839 100644 --- a/eslint/docs/src/rules/complexity.md +++ b/eslint/docs/src/rules/complexity.md @@ -1,6 +1,5 @@ --- title: complexity -layout: doc rule_type: suggestion related_rules: - max-depth diff --git a/eslint/docs/src/rules/computed-property-spacing.md b/eslint/docs/src/rules/computed-property-spacing.md index 58849da..065270d 100644 --- a/eslint/docs/src/rules/computed-property-spacing.md +++ b/eslint/docs/src/rules/computed-property-spacing.md @@ -1,6 +1,5 @@ --- title: computed-property-spacing -layout: doc rule_type: layout related_rules: - array-bracket-spacing diff --git a/eslint/docs/src/rules/consistent-return.md b/eslint/docs/src/rules/consistent-return.md index 068d3b9..2d5f4e3 100644 --- a/eslint/docs/src/rules/consistent-return.md +++ b/eslint/docs/src/rules/consistent-return.md @@ -1,6 +1,5 @@ --- title: consistent-return -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/consistent-this.md b/eslint/docs/src/rules/consistent-this.md index 8a5a56b..8f029ad 100644 --- a/eslint/docs/src/rules/consistent-this.md +++ b/eslint/docs/src/rules/consistent-this.md @@ -1,6 +1,5 @@ --- title: consistent-this -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/constructor-super.md b/eslint/docs/src/rules/constructor-super.md index c3cf06e..93019eb 100644 --- a/eslint/docs/src/rules/constructor-super.md +++ b/eslint/docs/src/rules/constructor-super.md @@ -1,6 +1,5 @@ --- title: constructor-super -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/curly.md b/eslint/docs/src/rules/curly.md index fe48feb..20b2308 100644 --- a/eslint/docs/src/rules/curly.md +++ b/eslint/docs/src/rules/curly.md @@ -1,6 +1,5 @@ --- title: curly -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/default-case-last.md b/eslint/docs/src/rules/default-case-last.md index 2502a83..b490577 100644 --- a/eslint/docs/src/rules/default-case-last.md +++ b/eslint/docs/src/rules/default-case-last.md @@ -1,6 +1,5 @@ --- title: default-case-last -layout: doc rule_type: suggestion related_rules: - default-case diff --git a/eslint/docs/src/rules/default-case.md b/eslint/docs/src/rules/default-case.md index f05ae44..1bad1a0 100644 --- a/eslint/docs/src/rules/default-case.md +++ b/eslint/docs/src/rules/default-case.md @@ -1,6 +1,5 @@ --- title: default-case -layout: doc rule_type: suggestion related_rules: - no-fallthrough diff --git a/eslint/docs/src/rules/default-param-last.md b/eslint/docs/src/rules/default-param-last.md index 0343ce7..7f64f7c 100644 --- a/eslint/docs/src/rules/default-param-last.md +++ b/eslint/docs/src/rules/default-param-last.md @@ -1,6 +1,5 @@ --- title: default-param-last -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/dot-location.md b/eslint/docs/src/rules/dot-location.md index 34ab8c5..47ee575 100644 --- a/eslint/docs/src/rules/dot-location.md +++ b/eslint/docs/src/rules/dot-location.md @@ -1,6 +1,5 @@ --- title: dot-location -layout: doc rule_type: layout related_rules: - newline-after-var diff --git a/eslint/docs/src/rules/dot-notation.md b/eslint/docs/src/rules/dot-notation.md index 7637004..7514a1f 100644 --- a/eslint/docs/src/rules/dot-notation.md +++ b/eslint/docs/src/rules/dot-notation.md @@ -1,6 +1,5 @@ --- title: dot-notation -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/eol-last.md b/eslint/docs/src/rules/eol-last.md index 4e7dbc0..4c9d837 100644 --- a/eslint/docs/src/rules/eol-last.md +++ b/eslint/docs/src/rules/eol-last.md @@ -1,6 +1,5 @@ --- title: eol-last -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/eqeqeq.md b/eslint/docs/src/rules/eqeqeq.md index f89c303..3243844 100644 --- a/eslint/docs/src/rules/eqeqeq.md +++ b/eslint/docs/src/rules/eqeqeq.md @@ -1,6 +1,5 @@ --- title: eqeqeq -layout: doc rule_type: suggestion --- @@ -140,7 +139,7 @@ foo == null ### allow-null -**Deprecated:** Instead of using this option use "always" and pass a "null" option property with value "ignore". This will tell ESLint to always enforce strict equality except when comparing with the `null` literal. +**Deprecated:** Instead of using this option use `"always"` and pass a `"null"` option property with value `"ignore"`. This will tell ESLint to always enforce strict equality except when comparing with the `null` literal. ```js ["error", "always", {"null": "ignore"}] diff --git a/eslint/docs/src/rules/for-direction.md b/eslint/docs/src/rules/for-direction.md index 352ed84..832c87c 100644 --- a/eslint/docs/src/rules/for-direction.md +++ b/eslint/docs/src/rules/for-direction.md @@ -1,6 +1,5 @@ --- title: for-direction -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/func-call-spacing.md b/eslint/docs/src/rules/func-call-spacing.md index 542ac8b..082c86d 100644 --- a/eslint/docs/src/rules/func-call-spacing.md +++ b/eslint/docs/src/rules/func-call-spacing.md @@ -1,6 +1,5 @@ --- title: func-call-spacing -layout: doc rule_type: layout related_rules: - no-spaced-func diff --git a/eslint/docs/src/rules/func-name-matching.md b/eslint/docs/src/rules/func-name-matching.md index d42db46..8ef1a1c 100644 --- a/eslint/docs/src/rules/func-name-matching.md +++ b/eslint/docs/src/rules/func-name-matching.md @@ -1,6 +1,5 @@ --- title: func-name-matching -layout: doc rule_type: suggestion --- @@ -146,7 +145,7 @@ module['exports'] = function foo(name) {}; ## Options -This rule takes an optional string of "always" or "never" (when omitted, it defaults to "always"), and an optional options object with two properties `considerPropertyDescriptor` and `includeCommonJSModuleExports`. +This rule takes an optional string of `"always"` or `"never"` (when omitted, it defaults to `"always"`), and an optional options object with two properties `considerPropertyDescriptor` and `includeCommonJSModuleExports`. ### considerPropertyDescriptor diff --git a/eslint/docs/src/rules/func-names.md b/eslint/docs/src/rules/func-names.md index ceb575e..549806d 100644 --- a/eslint/docs/src/rules/func-names.md +++ b/eslint/docs/src/rules/func-names.md @@ -1,6 +1,5 @@ --- title: func-names -layout: doc rule_type: suggestion further_reading: - https://web.archive.org/web/20201112040809/http://markdaggett.com/blog/2013/02/15/functions-explained/ diff --git a/eslint/docs/src/rules/func-style.md b/eslint/docs/src/rules/func-style.md index 56a9fca..05b6677 100644 --- a/eslint/docs/src/rules/func-style.md +++ b/eslint/docs/src/rules/func-style.md @@ -1,6 +1,5 @@ --- title: func-style -layout: doc rule_type: suggestion further_reading: - https://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html diff --git a/eslint/docs/src/rules/function-call-argument-newline.md b/eslint/docs/src/rules/function-call-argument-newline.md index baba38c..be44b2e 100644 --- a/eslint/docs/src/rules/function-call-argument-newline.md +++ b/eslint/docs/src/rules/function-call-argument-newline.md @@ -1,6 +1,5 @@ --- title: function-call-argument-newline -layout: doc rule_type: layout related_rules: - function-paren-newline diff --git a/eslint/docs/src/rules/function-paren-newline.md b/eslint/docs/src/rules/function-paren-newline.md index 68d38fa..4ba5a92 100644 --- a/eslint/docs/src/rules/function-paren-newline.md +++ b/eslint/docs/src/rules/function-paren-newline.md @@ -1,6 +1,5 @@ --- title: function-paren-newline -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/generator-star-spacing.md b/eslint/docs/src/rules/generator-star-spacing.md index 82cdbcd..3ef58d8 100644 --- a/eslint/docs/src/rules/generator-star-spacing.md +++ b/eslint/docs/src/rules/generator-star-spacing.md @@ -1,6 +1,5 @@ --- title: generator-star-spacing -layout: doc rule_type: layout further_reading: - https://leanpub.com/understandinges6/read/#leanpub-auto-generators @@ -52,14 +51,14 @@ This rule aims to enforce spacing around the `*` of generator functions. ## Options -The rule takes one option, an object, which has two keys `before` and `after` having boolean values `true` or `false`. +The rule takes one option, an object, which has two keys `"before"` and `"after"` having boolean values `true` or `false`. -* `before` enforces spacing between the `*` and the `function` keyword. +* `"before"` enforces spacing between the `*` and the `function` keyword. If it is `true`, a space is required, otherwise spaces are disallowed. In object literal shorthand methods, spacing before the `*` is not checked, as they lack a `function` keyword. -* `after` enforces spacing between the `*` and the function name (or the opening parenthesis for anonymous generator functions). +* `"after"` enforces spacing between the `*` and the function name (or the opening parenthesis for anonymous generator functions). If it is `true`, a space is required, otherwise spaces are disallowed. The default is `{"before": true, "after": false}`. @@ -100,9 +99,9 @@ An example of a configuration with overrides: }] ``` -In the example configuration above, the top level "before" and "after" options define the default behavior of -the rule, while the "anonymous" and "method" options override the default behavior. -Overrides can be either an object with "before" and "after", or a shorthand string as above. +In the example configuration above, the top level `"before"` and `"after"` options define the default behavior of +the rule, while the `"anonymous"` and `"method"` options override the default behavior. +Overrides can be either an object with `"before"` and `"after"`, or a shorthand string as above. ## Examples diff --git a/eslint/docs/src/rules/generator-star.md b/eslint/docs/src/rules/generator-star.md index 60b1780..c2ddb18 100644 --- a/eslint/docs/src/rules/generator-star.md +++ b/eslint/docs/src/rules/generator-star.md @@ -1,6 +1,5 @@ --- title: generator-star -layout: doc further_reading: - https://leanpub.com/understandinges6/read/#leanpub-auto-generators --- diff --git a/eslint/docs/src/rules/getter-return.md b/eslint/docs/src/rules/getter-return.md index b35f0d5..0c8937d 100644 --- a/eslint/docs/src/rules/getter-return.md +++ b/eslint/docs/src/rules/getter-return.md @@ -1,6 +1,5 @@ --- title: getter-return -layout: doc rule_type: problem further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get diff --git a/eslint/docs/src/rules/global-require.md b/eslint/docs/src/rules/global-require.md index 0fddee7..e960468 100644 --- a/eslint/docs/src/rules/global-require.md +++ b/eslint/docs/src/rules/global-require.md @@ -1,11 +1,10 @@ --- title: global-require -layout: doc rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). In Node.js, module dependencies are included using the `require()` function, such as: diff --git a/eslint/docs/src/rules/global-strict.md b/eslint/docs/src/rules/global-strict.md index 08c0c02..80cbf9a 100644 --- a/eslint/docs/src/rules/global-strict.md +++ b/eslint/docs/src/rules/global-strict.md @@ -1,6 +1,5 @@ --- title: global-strict -layout: doc --- diff --git a/eslint/docs/src/rules/grouped-accessor-pairs.md b/eslint/docs/src/rules/grouped-accessor-pairs.md index a8e40da..cfb274d 100644 --- a/eslint/docs/src/rules/grouped-accessor-pairs.md +++ b/eslint/docs/src/rules/grouped-accessor-pairs.md @@ -1,6 +1,5 @@ --- title: grouped-accessor-pairs -layout: doc rule_type: suggestion related_rules: - accessor-pairs diff --git a/eslint/docs/src/rules/guard-for-in.md b/eslint/docs/src/rules/guard-for-in.md index c6cdbab..0cdecb4 100644 --- a/eslint/docs/src/rules/guard-for-in.md +++ b/eslint/docs/src/rules/guard-for-in.md @@ -1,8 +1,8 @@ --- title: guard-for-in -layout: doc rule_type: suggestion related_rules: +- prefer-object-has-own - no-prototype-builtins further_reading: - https://javascriptweblog.wordpress.com/2011/01/04/exploring-javascript-for-in-loops/ @@ -18,6 +18,10 @@ for (key in foo) { } ``` +For codebases that do not support ES2022, `Object.prototype.hasOwnProperty.call(foo, key)` can be used as a check that the property is not inherited. + +For codebases that do support ES2022, `Object.hasOwn(foo, key)` can be used as a shorter alternative; see [prefer-object-has-own](prefer-object-has-own). + Note that simply checking `foo.hasOwnProperty(key)` is likely to cause an error in some cases; see [no-prototype-builtins](no-prototype-builtins). ## Rule Details @@ -45,6 +49,12 @@ Examples of **correct** code for this rule: ```js /*eslint guard-for-in: "error"*/ +for (key in foo) { + if (Object.hasOwn(foo, key)) { + doSomething(key); + } +} + for (key in foo) { if (Object.prototype.hasOwnProperty.call(foo, key)) { doSomething(key); diff --git a/eslint/docs/src/rules/handle-callback-err.md b/eslint/docs/src/rules/handle-callback-err.md index 4f4b8c9..3f66ad2 100644 --- a/eslint/docs/src/rules/handle-callback-err.md +++ b/eslint/docs/src/rules/handle-callback-err.md @@ -1,6 +1,5 @@ --- title: handle-callback-err -layout: doc rule_type: suggestion further_reading: - https://github.com/maxogden/art-of-node#callbacks @@ -8,7 +7,7 @@ further_reading: --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). In Node.js, a common pattern for dealing with asynchronous behavior is called the callback pattern. This pattern expects an `Error` object or `null` as the first argument of the callback. diff --git a/eslint/docs/src/rules/id-blacklist.md b/eslint/docs/src/rules/id-blacklist.md index 267eece..649b22e 100644 --- a/eslint/docs/src/rules/id-blacklist.md +++ b/eslint/docs/src/rules/id-blacklist.md @@ -1,6 +1,5 @@ --- title: id-blacklist -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/id-denylist.md b/eslint/docs/src/rules/id-denylist.md index 271d2a3..850f592 100644 --- a/eslint/docs/src/rules/id-denylist.md +++ b/eslint/docs/src/rules/id-denylist.md @@ -1,6 +1,5 @@ --- title: id-denylist -layout: doc rule_type: suggestion --- @@ -38,7 +37,7 @@ For example, to restrict the use of common generic identifiers: } ``` -**Note:** The first element of the array is for the rule severity (see [configuring rules](/docs/latest/user-guide/configuring/rules). The other elements in the array are the identifiers that you want to disallow. +**Note:** The first element of the array is for the rule severity (see [Configure Rules](../use/configure/rules). The other elements in the array are the identifiers that you want to disallow. Examples of **incorrect** code for this rule with sample `"data", "callback"` restricted identifiers: diff --git a/eslint/docs/src/rules/id-length.md b/eslint/docs/src/rules/id-length.md index fb69db1..adf5e89 100644 --- a/eslint/docs/src/rules/id-length.md +++ b/eslint/docs/src/rules/id-length.md @@ -1,6 +1,5 @@ --- title: id-length -layout: doc rule_type: suggestion related_rules: - max-len @@ -20,6 +19,8 @@ var x = 5; // too short; difficult to understand its purpose without context This rule enforces a minimum and/or maximum identifier length convention. +This rule counts [graphemes](https://unicode.org/reports/tr29/#Default_Grapheme_Cluster_Table) instead of using [`String length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length). + ## Options Examples of **incorrect** code for this rule with the default options: diff --git a/eslint/docs/src/rules/id-match.md b/eslint/docs/src/rules/id-match.md index 8e4bea9..08d5ff3 100644 --- a/eslint/docs/src/rules/id-match.md +++ b/eslint/docs/src/rules/id-match.md @@ -1,6 +1,5 @@ --- title: id-match -layout: doc rule_type: suggestion --- @@ -92,10 +91,10 @@ This rule has an object option: * `"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": false` (default) does not check 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": true` requires only `var`, `const`, `let`, `function`, and `class` declarations to match the specified regular expression * `"ignoreDestructuring": false` (default) enforces `id-match` for destructured identifiers * `"ignoreDestructuring": true` does not check destructured identifiers diff --git a/eslint/docs/src/rules/implicit-arrow-linebreak.md b/eslint/docs/src/rules/implicit-arrow-linebreak.md index c0d9f9f..09aaba1 100644 --- a/eslint/docs/src/rules/implicit-arrow-linebreak.md +++ b/eslint/docs/src/rules/implicit-arrow-linebreak.md @@ -1,6 +1,5 @@ --- title: implicit-arrow-linebreak -layout: doc rule_type: layout related_rules: - brace-style diff --git a/eslint/docs/src/rules/indent-legacy.md b/eslint/docs/src/rules/indent-legacy.md index 13ca8b6..97372eb 100644 --- a/eslint/docs/src/rules/indent-legacy.md +++ b/eslint/docs/src/rules/indent-legacy.md @@ -1,6 +1,5 @@ --- title: indent-legacy -layout: doc rule_type: layout --- @@ -8,7 +7,7 @@ rule_type: layout 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. +ESLint 4.0.0 introduced a rewrite of the [`indent`](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/src/rules/indent.md b/eslint/docs/src/rules/indent.md index 461abbe..7f81c0f 100644 --- a/eslint/docs/src/rules/indent.md +++ b/eslint/docs/src/rules/indent.md @@ -1,6 +1,5 @@ --- title: indent -layout: doc rule_type: layout --- @@ -82,7 +81,7 @@ if (a) { This rule has an object option: -* `"ignoredNodes"` can be used to disable indentation checking for any AST node. This accepts an array of [selectors](/docs/developer-guide/selectors). If an AST node is matched by any of the selectors, the indentation of tokens which are direct children of that node will be ignored. This can be used as an escape hatch to relax the rule if you disagree with the indentation that it enforces for a particular syntactic pattern. +* `"ignoredNodes"` can be used to disable indentation checking for any AST node. This accepts an array of [selectors](../extend/selectors). If an AST node is matched by any of the selectors, the indentation of tokens which are direct children of that node will be ignored. This can be used as an escape hatch to relax the rule if you disagree with the indentation that it enforces for a particular syntactic pattern. * `"SwitchCase"` (default: 0) enforces indentation level for `case` clauses in `switch` statements * `"VariableDeclarator"` (default: 1) enforces indentation level for `var` declarators; can also take an object to define separate rules for `var`, `let` and `const` declarations. It can also be `"first"`, indicating all the declarators should be aligned with the first declarator. * `"outerIIFEBody"` (default: 1) enforces indentation level for file-level IIFEs. This can also be set to `"off"` to disable checking for file-level IIFEs. diff --git a/eslint/docs/src/rules/init-declarations.md b/eslint/docs/src/rules/init-declarations.md index f7612e6..569e507 100644 --- a/eslint/docs/src/rules/init-declarations.md +++ b/eslint/docs/src/rules/init-declarations.md @@ -1,6 +1,5 @@ --- title: init-declarations -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/jsx-quotes.md b/eslint/docs/src/rules/jsx-quotes.md index f1af499..4f0d78b 100644 --- a/eslint/docs/src/rules/jsx-quotes.md +++ b/eslint/docs/src/rules/jsx-quotes.md @@ -1,6 +1,5 @@ --- title: jsx-quotes -layout: doc rule_type: layout related_rules: - quotes diff --git a/eslint/docs/src/rules/key-spacing.md b/eslint/docs/src/rules/key-spacing.md index 4b96fc0..d6e5000 100644 --- a/eslint/docs/src/rules/key-spacing.md +++ b/eslint/docs/src/rules/key-spacing.md @@ -1,6 +1,5 @@ --- title: key-spacing -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/keyword-spacing.md b/eslint/docs/src/rules/keyword-spacing.md index ada2208..a77e3dd 100644 --- a/eslint/docs/src/rules/keyword-spacing.md +++ b/eslint/docs/src/rules/keyword-spacing.md @@ -1,6 +1,5 @@ --- title: keyword-spacing -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/line-comment-position.md b/eslint/docs/src/rules/line-comment-position.md index 21c8b68..d032ecc 100644 --- a/eslint/docs/src/rules/line-comment-position.md +++ b/eslint/docs/src/rules/line-comment-position.md @@ -1,6 +1,5 @@ --- title: line-comment-position -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/linebreak-style.md b/eslint/docs/src/rules/linebreak-style.md index 8e97515..7115811 100644 --- a/eslint/docs/src/rules/linebreak-style.md +++ b/eslint/docs/src/rules/linebreak-style.md @@ -1,6 +1,5 @@ --- title: linebreak-style -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/lines-around-comment.md b/eslint/docs/src/rules/lines-around-comment.md index 472b61b..924ba66 100644 --- a/eslint/docs/src/rules/lines-around-comment.md +++ b/eslint/docs/src/rules/lines-around-comment.md @@ -1,6 +1,5 @@ --- title: lines-around-comment -layout: doc rule_type: layout related_rules: - space-before-blocks @@ -34,6 +33,7 @@ This rule has an object option: * `"allowClassEnd": true` allows comments to appear at the end of classes * `"applyDefaultIgnorePatterns"` enables or disables the default comment patterns to be ignored by the rule * `"ignorePattern"` custom patterns to be ignored by the rule +* `"afterHashbangComment": true` requires an empty line after hashbang comments ### beforeBlockComment @@ -718,6 +718,35 @@ foo(); ::: +### afterHashbangComment + +Examples of **incorrect** code for this rule with the `{ "afterHashbangComment": true }` option: + +::: incorrect + +```js +#!foo +var day = "great" + +/*eslint lines-around-comment: ["error", { "afterHashbangComment": true }] */ +``` + +::: + +Examples of **correct** code for this rule with the `{ "afterHashbangComment": true }` option: + +::: correct + +```js +#!foo + +var day = "great" + +/*eslint lines-around-comment: ["error", { "afterHashbangComment": true }] */ +``` + +::: + ## When Not To Use It Many people enjoy a terser code style and don't mind comments bumping up against code. If you fall into that category this rule is not for you. diff --git a/eslint/docs/src/rules/lines-around-directive.md b/eslint/docs/src/rules/lines-around-directive.md index 42133a3..fb71775 100644 --- a/eslint/docs/src/rules/lines-around-directive.md +++ b/eslint/docs/src/rules/lines-around-directive.md @@ -1,6 +1,5 @@ --- title: lines-around-directive -layout: doc rule_type: layout related_rules: - lines-around-comment diff --git a/eslint/docs/src/rules/lines-between-class-members.md b/eslint/docs/src/rules/lines-between-class-members.md index 13fcbe4..8daf2a2 100644 --- a/eslint/docs/src/rules/lines-between-class-members.md +++ b/eslint/docs/src/rules/lines-between-class-members.md @@ -1,6 +1,5 @@ --- title: lines-between-class-members -layout: doc rule_type: layout related_rules: - padded-blocks diff --git a/eslint/docs/src/rules/logical-assignment-operators.md b/eslint/docs/src/rules/logical-assignment-operators.md new file mode 100644 index 0000000..3f93500 --- /dev/null +++ b/eslint/docs/src/rules/logical-assignment-operators.md @@ -0,0 +1,130 @@ +--- +title: logical-assignment-operators +rule_type: suggestion +--- + +ES2021 introduces the assignment operator shorthand for the logical operators `||`, `&&` and `??`. +Before, this was only allowed for mathematical operations such as `+` or `*` (see the rule [operator-assignment](./operator-assignment)). +The shorthand can be used if the assignment target and the left expression of a logical expression are the same. +For example `a = a || b` can be shortened to `a ||= b`. + +## Rule Details + +This rule requires or disallows logical assignment operator shorthand. + +### Options + +This rule has a string and an object option. +String option: + +* `"always"` (default) +* `"never"` + +Object option (only available if string option is set to `"always"`): + +* `"enforceForIfStatements": false`(default) Do *not* check for equivalent `if` statements +* `"enforceForIfStatements": true` Check for equivalent `if` statements + +#### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +::: incorrect + +```js +/*eslint logical-assignment-operators: ["error", "always"]*/ + +a = a || b +a = a && b +a = a ?? b +a || (a = b) +a && (a = b) +a ?? (a = b) +``` + +::: + +Examples of **correct** code for this rule with the default `"always"` option: + +::: correct + +```js +/*eslint logical-assignment-operators: ["error", "always"]*/ + +a = b +a += b +a ||= b +a = b || c +a || (b = c) + +if (a) a = b +``` + +::: + +#### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +::: incorrect + +```js +/*eslint logical-assignment-operators: ["error", "never"]*/ + +a ||= b +a &&= b +a ??= b +``` + +::: + +Examples of **correct** code for this rule with the `"never"` option: + +::: correct + +```js +/*eslint logical-assignment-operators: ["error", "never"]*/ + +a = a || b +a = a && b +a = a ?? b +``` + +::: + +#### enforceForIfStatements + +This option checks for additional patterns with if statements which could be expressed with the logical assignment operator. + +::: incorrect + +Examples of **incorrect** code for this rule with the `["always", { enforceForIfStatements: true }]` option: + +```js +/*eslint logical-assignment-operators: ["error", "always", { enforceForIfStatements: true }]*/ + +if (a) a = b // <=> a &&= b +if (!a) a = b // <=> a ||= b + +if (a == null) a = b // <=> a ??= b +if (a === null || a === undefined) a = b // <=> a ??= b +``` + +::: + +Examples of **correct** code for this rule with the `["always", { enforceForIfStatements: true }]` option: + +::: correct + +```js +/*eslint logical-assignment-operators: ["error", "always", { enforceForIfStatements: true }]*/ + +if (a) b = c +if (a === 0) a = b +``` + +::: + +## When Not To Use It + +Use of logical operator assignment shorthand is a stylistic choice. Leaving this rule turned off would allow developers to choose which style is more readable on a case-by-case basis. diff --git a/eslint/docs/src/rules/max-classes-per-file.md b/eslint/docs/src/rules/max-classes-per-file.md index d7a47ba..7727559 100644 --- a/eslint/docs/src/rules/max-classes-per-file.md +++ b/eslint/docs/src/rules/max-classes-per-file.md @@ -1,6 +1,5 @@ --- title: max-classes-per-file -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/max-depth.md b/eslint/docs/src/rules/max-depth.md index 70f441a..1e0e866 100644 --- a/eslint/docs/src/rules/max-depth.md +++ b/eslint/docs/src/rules/max-depth.md @@ -1,6 +1,5 @@ --- title: max-depth -layout: doc rule_type: suggestion related_rules: - complexity diff --git a/eslint/docs/src/rules/max-len.md b/eslint/docs/src/rules/max-len.md index ae3d527..be883a8 100644 --- a/eslint/docs/src/rules/max-len.md +++ b/eslint/docs/src/rules/max-len.md @@ -1,6 +1,5 @@ --- title: max-len -layout: doc rule_type: layout related_rules: - complexity diff --git a/eslint/docs/src/rules/max-lines-per-function.md b/eslint/docs/src/rules/max-lines-per-function.md index f0ca7cb..45b8a62 100644 --- a/eslint/docs/src/rules/max-lines-per-function.md +++ b/eslint/docs/src/rules/max-lines-per-function.md @@ -1,6 +1,5 @@ --- title: max-lines-per-function -layout: doc rule_type: suggestion related_rules: - complexity diff --git a/eslint/docs/src/rules/max-lines.md b/eslint/docs/src/rules/max-lines.md index 3064683..9aa4ae4 100644 --- a/eslint/docs/src/rules/max-lines.md +++ b/eslint/docs/src/rules/max-lines.md @@ -1,6 +1,5 @@ --- title: max-lines -layout: doc rule_type: suggestion related_rules: - complexity diff --git a/eslint/docs/src/rules/max-nested-callbacks.md b/eslint/docs/src/rules/max-nested-callbacks.md index ea83c63..759fffa 100644 --- a/eslint/docs/src/rules/max-nested-callbacks.md +++ b/eslint/docs/src/rules/max-nested-callbacks.md @@ -1,6 +1,5 @@ --- title: max-nested-callbacks -layout: doc rule_type: suggestion related_rules: - complexity diff --git a/eslint/docs/src/rules/max-params.md b/eslint/docs/src/rules/max-params.md index 5456cb1..681723f 100644 --- a/eslint/docs/src/rules/max-params.md +++ b/eslint/docs/src/rules/max-params.md @@ -1,6 +1,5 @@ --- title: max-params -layout: doc rule_type: suggestion related_rules: - complexity diff --git a/eslint/docs/src/rules/max-statements-per-line.md b/eslint/docs/src/rules/max-statements-per-line.md index efa8160..238ade7 100644 --- a/eslint/docs/src/rules/max-statements-per-line.md +++ b/eslint/docs/src/rules/max-statements-per-line.md @@ -1,6 +1,5 @@ --- title: max-statements-per-line -layout: doc rule_type: layout related_rules: - max-depth diff --git a/eslint/docs/src/rules/max-statements.md b/eslint/docs/src/rules/max-statements.md index c29a595..d41f7f2 100644 --- a/eslint/docs/src/rules/max-statements.md +++ b/eslint/docs/src/rules/max-statements.md @@ -1,6 +1,5 @@ --- title: max-statements -layout: doc rule_type: suggestion related_rules: - complexity diff --git a/eslint/docs/src/rules/multiline-comment-style.md b/eslint/docs/src/rules/multiline-comment-style.md index 657de1d..28e6648 100644 --- a/eslint/docs/src/rules/multiline-comment-style.md +++ b/eslint/docs/src/rules/multiline-comment-style.md @@ -1,6 +1,5 @@ --- title: multiline-comment-style -layout: doc rule_type: suggestion --- @@ -17,10 +16,10 @@ This rule aims to enforce a particular style for multiline comments. This rule has a string option, which can have one of the following values: * `"starred-block"` (default): Disallows consecutive line comments in favor of block comments. Additionally, requires block comments to have an aligned `*` character before each line. -* `"bare-block"`: Disallows consecutive line comments in favor of block comments, and disallows block comments from having a `"*"` character before each line. -* `"separate-lines"`: Disallows block comments in favor of consecutive line comments +* `"bare-block"`: Disallows consecutive line comments in favor of block comments, and disallows block comments from having a `"*"` character before each line. This option ignores JSDoc comments. +* `"separate-lines"`: Disallows block comments in favor of consecutive line comments. By default, this option ignores JSDoc comments. To also apply this rule to JSDoc comments, set the `checkJSDoc` option to `true`. -The rule always ignores directive comments such as `/* eslint-disable */`. Additionally, unless the mode is `"starred-block"`, the rule ignores JSDoc comments. +The rule always ignores directive comments such as `/* eslint-disable */`. Examples of **incorrect** code for this rule with the default `"starred-block"` option: @@ -147,6 +146,39 @@ foo(); ::: +Examples of **incorrect** code for this rule with the `"separate-lines"` option and `checkJSDoc` set to `true`: + +::: incorrect + +```js + +/* eslint multiline-comment-style: ["error", "separate-lines", { "checkJSDoc": true }] */ + +/** + * I am a JSDoc comment + * and I'm not allowed + */ +foo(); + +``` + +::: + +Examples of **correct** code for this rule with the `"separate-lines"` option and `checkJSDoc` set to `true`: + +::: correct + +```js +/* eslint multiline-comment-style: ["error", "separate-lines", { "checkJSDoc": true }] */ + +// I am a JSDoc comment +// and I'm not allowed +foo(); + +``` + +::: + ## When Not To Use It If you don't want to enforce a particular style for multiline comments, you can disable the rule. diff --git a/eslint/docs/src/rules/multiline-ternary.md b/eslint/docs/src/rules/multiline-ternary.md index f3ba255..7e4688d 100644 --- a/eslint/docs/src/rules/multiline-ternary.md +++ b/eslint/docs/src/rules/multiline-ternary.md @@ -1,6 +1,5 @@ --- title: multiline-ternary -layout: doc rule_type: layout related_rules: - operator-linebreak diff --git a/eslint/docs/src/rules/new-cap.md b/eslint/docs/src/rules/new-cap.md index cf49280..f75c65f 100644 --- a/eslint/docs/src/rules/new-cap.md +++ b/eslint/docs/src/rules/new-cap.md @@ -1,6 +1,5 @@ --- title: new-cap -layout: doc rule_type: suggestion --- @@ -25,6 +24,7 @@ This rule requires constructor names to begin with a capital letter. Certain bui * `RegExp` * `String` * `Symbol` +* `BigInt` Examples of **correct** code for this rule: diff --git a/eslint/docs/src/rules/new-parens.md b/eslint/docs/src/rules/new-parens.md index 02bceef..08292e8 100644 --- a/eslint/docs/src/rules/new-parens.md +++ b/eslint/docs/src/rules/new-parens.md @@ -1,6 +1,5 @@ --- title: new-parens -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/newline-after-var.md b/eslint/docs/src/rules/newline-after-var.md index a516b53..db11e25 100644 --- a/eslint/docs/src/rules/newline-after-var.md +++ b/eslint/docs/src/rules/newline-after-var.md @@ -1,6 +1,5 @@ --- title: newline-after-var -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/newline-before-return.md b/eslint/docs/src/rules/newline-before-return.md index 2851e68..41fb772 100644 --- a/eslint/docs/src/rules/newline-before-return.md +++ b/eslint/docs/src/rules/newline-before-return.md @@ -1,6 +1,5 @@ --- title: newline-before-return -layout: doc rule_type: layout related_rules: - newline-after-var diff --git a/eslint/docs/src/rules/newline-per-chained-call.md b/eslint/docs/src/rules/newline-per-chained-call.md index 4ceff02..e2482cd 100644 --- a/eslint/docs/src/rules/newline-per-chained-call.md +++ b/eslint/docs/src/rules/newline-per-chained-call.md @@ -1,6 +1,5 @@ --- title: newline-per-chained-call -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/no-alert.md b/eslint/docs/src/rules/no-alert.md index 6088cb3..fe7ffd4 100644 --- a/eslint/docs/src/rules/no-alert.md +++ b/eslint/docs/src/rules/no-alert.md @@ -1,6 +1,5 @@ --- title: no-alert -layout: doc rule_type: suggestion related_rules: - no-console diff --git a/eslint/docs/src/rules/no-array-constructor.md b/eslint/docs/src/rules/no-array-constructor.md index 730da6c..7a22df0 100644 --- a/eslint/docs/src/rules/no-array-constructor.md +++ b/eslint/docs/src/rules/no-array-constructor.md @@ -1,6 +1,5 @@ --- title: no-array-constructor -layout: doc rule_type: suggestion related_rules: - no-new-object diff --git a/eslint/docs/src/rules/no-arrow-condition.md b/eslint/docs/src/rules/no-arrow-condition.md index 6783bde..5882b3f 100644 --- a/eslint/docs/src/rules/no-arrow-condition.md +++ b/eslint/docs/src/rules/no-arrow-condition.md @@ -1,6 +1,5 @@ --- title: no-arrow-condition -layout: doc related_rules: - arrow-parens diff --git a/eslint/docs/src/rules/no-async-promise-executor.md b/eslint/docs/src/rules/no-async-promise-executor.md index 4c6a854..784c5ea 100644 --- a/eslint/docs/src/rules/no-async-promise-executor.md +++ b/eslint/docs/src/rules/no-async-promise-executor.md @@ -1,6 +1,5 @@ --- title: no-async-promise-executor -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-await-in-loop.md b/eslint/docs/src/rules/no-await-in-loop.md index f661a5b..a150f5a 100644 --- a/eslint/docs/src/rules/no-await-in-loop.md +++ b/eslint/docs/src/rules/no-await-in-loop.md @@ -1,6 +1,5 @@ --- title: no-await-in-loop -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-bitwise.md b/eslint/docs/src/rules/no-bitwise.md index 2df0b8a..3b7268f 100644 --- a/eslint/docs/src/rules/no-bitwise.md +++ b/eslint/docs/src/rules/no-bitwise.md @@ -1,6 +1,5 @@ --- title: no-bitwise -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-buffer-constructor.md b/eslint/docs/src/rules/no-buffer-constructor.md index 18f4f17..97ea191 100644 --- a/eslint/docs/src/rules/no-buffer-constructor.md +++ b/eslint/docs/src/rules/no-buffer-constructor.md @@ -1,6 +1,5 @@ --- title: no-buffer-constructor -layout: doc rule_type: problem further_reading: - https://nodejs.org/api/buffer.html @@ -9,7 +8,7 @@ further_reading: --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). In Node.js, the behavior of the `Buffer` constructor is different depending on the type of its argument. Passing an argument from user input to `Buffer()` without validating its type can lead to security vulnerabilities such as remote memory disclosure and denial of service. As a result, the `Buffer` constructor has been deprecated and should not be used. Use the producer methods `Buffer.from`, `Buffer.alloc`, and `Buffer.allocUnsafe` instead. diff --git a/eslint/docs/src/rules/no-caller.md b/eslint/docs/src/rules/no-caller.md index 5a1e2b7..13dddd0 100644 --- a/eslint/docs/src/rules/no-caller.md +++ b/eslint/docs/src/rules/no-caller.md @@ -1,6 +1,5 @@ --- title: no-caller -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-case-declarations.md b/eslint/docs/src/rules/no-case-declarations.md index b9aa9b5..49e6f41 100644 --- a/eslint/docs/src/rules/no-case-declarations.md +++ b/eslint/docs/src/rules/no-case-declarations.md @@ -1,6 +1,5 @@ --- title: no-case-declarations -layout: doc rule_type: suggestion related_rules: - no-fallthrough diff --git a/eslint/docs/src/rules/no-catch-shadow.md b/eslint/docs/src/rules/no-catch-shadow.md index abf86b9..23328e4 100644 --- a/eslint/docs/src/rules/no-catch-shadow.md +++ b/eslint/docs/src/rules/no-catch-shadow.md @@ -1,6 +1,5 @@ --- title: no-catch-shadow -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-class-assign.md b/eslint/docs/src/rules/no-class-assign.md index 153be8e..360192c 100644 --- a/eslint/docs/src/rules/no-class-assign.md +++ b/eslint/docs/src/rules/no-class-assign.md @@ -1,6 +1,5 @@ --- title: no-class-assign -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-comma-dangle.md b/eslint/docs/src/rules/no-comma-dangle.md index de0faf7..c1815bc 100644 --- a/eslint/docs/src/rules/no-comma-dangle.md +++ b/eslint/docs/src/rules/no-comma-dangle.md @@ -1,6 +1,5 @@ --- title: no-comma-dangle -layout: doc --- diff --git a/eslint/docs/src/rules/no-compare-neg-zero.md b/eslint/docs/src/rules/no-compare-neg-zero.md index 5d7da2a..18bc4ed 100644 --- a/eslint/docs/src/rules/no-compare-neg-zero.md +++ b/eslint/docs/src/rules/no-compare-neg-zero.md @@ -1,6 +1,5 @@ --- title: no-compare-neg-zero -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-cond-assign.md b/eslint/docs/src/rules/no-cond-assign.md index af04548..b72c2d0 100644 --- a/eslint/docs/src/rules/no-cond-assign.md +++ b/eslint/docs/src/rules/no-cond-assign.md @@ -1,6 +1,5 @@ --- title: no-cond-assign -layout: doc rule_type: problem related_rules: - no-extra-parens diff --git a/eslint/docs/src/rules/no-confusing-arrow.md b/eslint/docs/src/rules/no-confusing-arrow.md index 18d0563..546e303 100644 --- a/eslint/docs/src/rules/no-confusing-arrow.md +++ b/eslint/docs/src/rules/no-confusing-arrow.md @@ -1,6 +1,5 @@ --- title: no-confusing-arrow -layout: doc rule_type: suggestion related_rules: - no-constant-condition diff --git a/eslint/docs/src/rules/no-console.md b/eslint/docs/src/rules/no-console.md index 05a1dfe..9420c36 100644 --- a/eslint/docs/src/rules/no-console.md +++ b/eslint/docs/src/rules/no-console.md @@ -1,6 +1,5 @@ --- title: no-console -layout: doc rule_type: suggestion related_rules: - no-alert diff --git a/eslint/docs/src/rules/no-const-assign.md b/eslint/docs/src/rules/no-const-assign.md index 67be27e..f9f0ed1 100644 --- a/eslint/docs/src/rules/no-const-assign.md +++ b/eslint/docs/src/rules/no-const-assign.md @@ -1,6 +1,5 @@ --- title: no-const-assign -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-constant-binary-expression.md b/eslint/docs/src/rules/no-constant-binary-expression.md index 43deeae..792c61d 100644 --- a/eslint/docs/src/rules/no-constant-binary-expression.md +++ b/eslint/docs/src/rules/no-constant-binary-expression.md @@ -1,6 +1,5 @@ --- title: no-constant-binary-expression -layout: doc rule_type: problem related_rules: - no-constant-condition @@ -54,6 +53,12 @@ const value4 = new Boolean(foo) === true; const objIsEmpty = someObj === {}; const arrIsEmpty = someArr === []; + +const shortCircuit1 = condition1 && false && condition2; + +const shortCircuit2 = condition1 || true || condition2; + +const shortCircuit3 = condition1 ?? "non-nullish" ?? condition2; ``` ::: diff --git a/eslint/docs/src/rules/no-constant-condition.md b/eslint/docs/src/rules/no-constant-condition.md index a3bad57..2ac613f 100644 --- a/eslint/docs/src/rules/no-constant-condition.md +++ b/eslint/docs/src/rules/no-constant-condition.md @@ -1,6 +1,5 @@ --- title: no-constant-condition -layout: doc rule_type: problem related_rules: - no-constant-binary-expression @@ -75,6 +74,10 @@ do { } while (x = -1); var result = 0 ? a : b; + +if(input === "hello" || "bye"){ + output(input); +} ``` ::: @@ -103,6 +106,10 @@ do { } while (x); var result = x !== 0 ? a : b; + +if(input === "hello" || input === "bye"){ + output(input); +} ``` ::: diff --git a/eslint/docs/src/rules/no-constructor-return.md b/eslint/docs/src/rules/no-constructor-return.md index 1e74eb7..e429d38 100644 --- a/eslint/docs/src/rules/no-constructor-return.md +++ b/eslint/docs/src/rules/no-constructor-return.md @@ -1,6 +1,5 @@ --- title: no-constructor-return -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-continue.md b/eslint/docs/src/rules/no-continue.md index 0753e36..af5cd8f 100644 --- a/eslint/docs/src/rules/no-continue.md +++ b/eslint/docs/src/rules/no-continue.md @@ -1,6 +1,5 @@ --- title: no-continue -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-control-regex.md b/eslint/docs/src/rules/no-control-regex.md index 65149cd..18a5ce4 100644 --- a/eslint/docs/src/rules/no-control-regex.md +++ b/eslint/docs/src/rules/no-control-regex.md @@ -1,6 +1,5 @@ --- title: no-control-regex -layout: doc rule_type: problem related_rules: - no-div-regex diff --git a/eslint/docs/src/rules/no-debugger.md b/eslint/docs/src/rules/no-debugger.md index 07da9b8..f30f9ae 100644 --- a/eslint/docs/src/rules/no-debugger.md +++ b/eslint/docs/src/rules/no-debugger.md @@ -1,6 +1,5 @@ --- title: no-debugger -layout: doc rule_type: problem related_rules: - no-alert diff --git a/eslint/docs/src/rules/no-delete-var.md b/eslint/docs/src/rules/no-delete-var.md index 8d5f680..65a5aa1 100644 --- a/eslint/docs/src/rules/no-delete-var.md +++ b/eslint/docs/src/rules/no-delete-var.md @@ -1,6 +1,5 @@ --- title: no-delete-var -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-div-regex.md b/eslint/docs/src/rules/no-div-regex.md index d44b3dd..cb30420 100644 --- a/eslint/docs/src/rules/no-div-regex.md +++ b/eslint/docs/src/rules/no-div-regex.md @@ -1,6 +1,5 @@ --- title: no-div-regex -layout: doc rule_type: suggestion related_rules: - no-control-regex @@ -9,7 +8,7 @@ related_rules: -Require regex literals to escape division operators. +Characters `/=` at the beginning of a regular expression literal can be confused with a division assignment operator. ```js function bar() { return /=foo/; } @@ -17,7 +16,7 @@ function bar() { return /=foo/; } ## Rule Details -This is used to disambiguate the division operator to not confuse users. +This rule forbids equal signs (`=`) after the slash (`/`) at the beginning of a regular expression literal, because the characters `/=` can be confused with a division assignment operator. Examples of **incorrect** code for this rule: diff --git a/eslint/docs/src/rules/no-dupe-args.md b/eslint/docs/src/rules/no-dupe-args.md index 84ca185..79f791c 100644 --- a/eslint/docs/src/rules/no-dupe-args.md +++ b/eslint/docs/src/rules/no-dupe-args.md @@ -1,6 +1,5 @@ --- title: no-dupe-args -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-dupe-class-members.md b/eslint/docs/src/rules/no-dupe-class-members.md index 91b096f..aaea4ce 100644 --- a/eslint/docs/src/rules/no-dupe-class-members.md +++ b/eslint/docs/src/rules/no-dupe-class-members.md @@ -1,6 +1,5 @@ --- title: no-dupe-class-members -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-dupe-else-if.md b/eslint/docs/src/rules/no-dupe-else-if.md index 2ab75a6..54d160a 100644 --- a/eslint/docs/src/rules/no-dupe-else-if.md +++ b/eslint/docs/src/rules/no-dupe-else-if.md @@ -1,6 +1,5 @@ --- title: no-dupe-else-if -layout: doc rule_type: problem related_rules: - no-duplicate-case diff --git a/eslint/docs/src/rules/no-dupe-keys.md b/eslint/docs/src/rules/no-dupe-keys.md index aa33693..75fc949 100644 --- a/eslint/docs/src/rules/no-dupe-keys.md +++ b/eslint/docs/src/rules/no-dupe-keys.md @@ -1,6 +1,5 @@ --- title: no-dupe-keys -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-duplicate-case.md b/eslint/docs/src/rules/no-duplicate-case.md index 26ffeb0..59a2a98 100644 --- a/eslint/docs/src/rules/no-duplicate-case.md +++ b/eslint/docs/src/rules/no-duplicate-case.md @@ -1,6 +1,5 @@ --- title: no-duplicate-case -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-duplicate-imports.md b/eslint/docs/src/rules/no-duplicate-imports.md index c94a178..2d7a553 100644 --- a/eslint/docs/src/rules/no-duplicate-imports.md +++ b/eslint/docs/src/rules/no-duplicate-imports.md @@ -1,6 +1,5 @@ --- title: no-duplicate-imports -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-else-return.md b/eslint/docs/src/rules/no-else-return.md index c67c5f3..917c445 100644 --- a/eslint/docs/src/rules/no-else-return.md +++ b/eslint/docs/src/rules/no-else-return.md @@ -1,6 +1,5 @@ --- title: no-else-return -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-empty-character-class.md b/eslint/docs/src/rules/no-empty-character-class.md index d916950..984f1e1 100644 --- a/eslint/docs/src/rules/no-empty-character-class.md +++ b/eslint/docs/src/rules/no-empty-character-class.md @@ -1,6 +1,5 @@ --- title: no-empty-character-class -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-empty-class.md b/eslint/docs/src/rules/no-empty-class.md index 4326054..5a6760f 100644 --- a/eslint/docs/src/rules/no-empty-class.md +++ b/eslint/docs/src/rules/no-empty-class.md @@ -1,6 +1,5 @@ --- title: no-empty-class -layout: doc --- diff --git a/eslint/docs/src/rules/no-empty-function.md b/eslint/docs/src/rules/no-empty-function.md index 6542317..a9be4bb 100644 --- a/eslint/docs/src/rules/no-empty-function.md +++ b/eslint/docs/src/rules/no-empty-function.md @@ -1,6 +1,5 @@ --- title: no-empty-function -layout: doc rule_type: suggestion related_rules: - no-empty diff --git a/eslint/docs/src/rules/no-empty-label.md b/eslint/docs/src/rules/no-empty-label.md index 6666960..9fd7da4 100644 --- a/eslint/docs/src/rules/no-empty-label.md +++ b/eslint/docs/src/rules/no-empty-label.md @@ -1,6 +1,5 @@ --- title: no-empty-label -layout: doc related_rules: - no-labels diff --git a/eslint/docs/src/rules/no-empty-pattern.md b/eslint/docs/src/rules/no-empty-pattern.md index 374535f..d510f59 100644 --- a/eslint/docs/src/rules/no-empty-pattern.md +++ b/eslint/docs/src/rules/no-empty-pattern.md @@ -1,6 +1,5 @@ --- title: no-empty-pattern -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-empty-static-block.md b/eslint/docs/src/rules/no-empty-static-block.md new file mode 100644 index 0000000..283a4e2 --- /dev/null +++ b/eslint/docs/src/rules/no-empty-static-block.md @@ -0,0 +1,56 @@ +--- +title: no-empty-static-block +layout: doc +rule_type: suggestion +related_rules: +- no-empty +- no-empty-function +further_reading: +- https://github.com/tc39/proposal-class-static-block +--- + +Empty static blocks, while not technically errors, usually occur due to refactoring that wasn't completed. They can cause confusion when reading code. + +## Rule Details + +This rule disallows empty static blocks. This rule ignores static blocks which contain a comment. + +Examples of **incorrect** code for this rule: + +::: incorrect + +```js +/*eslint no-empty-static-block: "error"*/ + +class Foo { + static {} +} +``` + +::: + +Examples of **correct** code for this rule: + +:::correct + +```js +/*eslint no-empty-static-block: "error"*/ + +class Foo { + static { + bar(); + } +} + +class Foo { + static { + // comment + } +} +``` + +::: + +## When Not To Use It + +This rule should not be used in environments prior to ES2022. diff --git a/eslint/docs/src/rules/no-empty.md b/eslint/docs/src/rules/no-empty.md index 68a3634..64a9bc0 100644 --- a/eslint/docs/src/rules/no-empty.md +++ b/eslint/docs/src/rules/no-empty.md @@ -1,6 +1,5 @@ --- title: no-empty -layout: doc rule_type: suggestion related_rules: - no-empty-function diff --git a/eslint/docs/src/rules/no-eq-null.md b/eslint/docs/src/rules/no-eq-null.md index 6511bd7..43c9557 100644 --- a/eslint/docs/src/rules/no-eq-null.md +++ b/eslint/docs/src/rules/no-eq-null.md @@ -1,6 +1,5 @@ --- title: no-eq-null -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-eval.md b/eslint/docs/src/rules/no-eval.md index 166087d..662df5d 100644 --- a/eslint/docs/src/rules/no-eval.md +++ b/eslint/docs/src/rules/no-eval.md @@ -1,6 +1,5 @@ --- title: no-eval -layout: doc rule_type: suggestion related_rules: - no-implied-eval diff --git a/eslint/docs/src/rules/no-ex-assign.md b/eslint/docs/src/rules/no-ex-assign.md index edf2fca..395164d 100644 --- a/eslint/docs/src/rules/no-ex-assign.md +++ b/eslint/docs/src/rules/no-ex-assign.md @@ -1,6 +1,5 @@ --- title: no-ex-assign -layout: doc rule_type: problem further_reading: - https://bocoup.com/blog/the-catch-with-try-catch diff --git a/eslint/docs/src/rules/no-extend-native.md b/eslint/docs/src/rules/no-extend-native.md index abdf812..9be6a08 100644 --- a/eslint/docs/src/rules/no-extend-native.md +++ b/eslint/docs/src/rules/no-extend-native.md @@ -1,6 +1,5 @@ --- title: no-extend-native -layout: doc rule_type: suggestion related_rules: - no-global-assign diff --git a/eslint/docs/src/rules/no-extra-bind.md b/eslint/docs/src/rules/no-extra-bind.md index 4133e75..073425c 100644 --- a/eslint/docs/src/rules/no-extra-bind.md +++ b/eslint/docs/src/rules/no-extra-bind.md @@ -1,6 +1,5 @@ --- title: no-extra-bind -layout: doc rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind diff --git a/eslint/docs/src/rules/no-extra-boolean-cast.md b/eslint/docs/src/rules/no-extra-boolean-cast.md index d08a4ea..e455098 100644 --- a/eslint/docs/src/rules/no-extra-boolean-cast.md +++ b/eslint/docs/src/rules/no-extra-boolean-cast.md @@ -1,6 +1,5 @@ --- title: no-extra-boolean-cast -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-extra-label.md b/eslint/docs/src/rules/no-extra-label.md index bd2cb58..9e916aa 100644 --- a/eslint/docs/src/rules/no-extra-label.md +++ b/eslint/docs/src/rules/no-extra-label.md @@ -1,6 +1,5 @@ --- title: no-extra-label -layout: doc rule_type: suggestion related_rules: - no-labels diff --git a/eslint/docs/src/rules/no-extra-parens.md b/eslint/docs/src/rules/no-extra-parens.md index ab3fd1c..9b1b8df 100644 --- a/eslint/docs/src/rules/no-extra-parens.md +++ b/eslint/docs/src/rules/no-extra-parens.md @@ -1,6 +1,5 @@ --- title: no-extra-parens -layout: doc rule_type: layout related_rules: - arrow-parens @@ -39,6 +38,7 @@ This rule has an object option for exceptions to the `"all"` option: * `"enforceForSequenceExpressions": false` allows extra parentheses around sequence expressions * `"enforceForNewInMemberExpressions": false` allows extra parentheses around `new` expressions in member expressions * `"enforceForFunctionPrototypeMethods": false` allows extra parentheses around immediate `.call` and `.apply` method calls on function expressions and around function expressions in the same context. +* `"allowParensAfterCommentPattern": "any-string-pattern"` allows extra parentheses preceded by a comment that matches a regular expression. ### all @@ -61,6 +61,8 @@ for (a of (b)); typeof (a); +(Object.prototype.toString.call()); + (function(){} ? a() : b()); class A { @@ -83,8 +85,6 @@ Examples of **correct** code for this rule with the default `"all"` option: (0).toString(); -(Object.prototype.toString.call()); - ({}.toString.call()); (function(){}) ? a() : b(); @@ -323,6 +323,34 @@ const quux = (function () {}.apply()); ::: +### allowParensAfterCommentPattern + +To make this rule allow extra parentheses preceded by specific comments, set this option to a string pattern that will be passed to the [`RegExp` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp). + +Examples of **correct** code for this rule with the `"all"` and `{ "allowParensAfterCommentPattern": "@type" }` options: + +::: correct + +```js +/* eslint no-extra-parens: ["error", "all", { "allowParensAfterCommentPattern": "@type" }] */ + +const span = /**@type {HTMLSpanElement}*/(event.currentTarget); + +if (/** @type {Foo | Bar} */(options).baz) console.log('Lint free'); + +foo(/** @type {Bar} */ (bar), options, { + name: "name", + path: "path", +}); + +if (foo) { + /** @type {Bar} */ + (bar).prop = false; +} +``` + +::: + ### functions Examples of **incorrect** code for this rule with the `"functions"` option: diff --git a/eslint/docs/src/rules/no-extra-semi.md b/eslint/docs/src/rules/no-extra-semi.md index 6132bcb..9755c51 100644 --- a/eslint/docs/src/rules/no-extra-semi.md +++ b/eslint/docs/src/rules/no-extra-semi.md @@ -1,6 +1,5 @@ --- title: no-extra-semi -layout: doc rule_type: suggestion related_rules: - semi diff --git a/eslint/docs/src/rules/no-extra-strict.md b/eslint/docs/src/rules/no-extra-strict.md index caeff8a..db0b8da 100644 --- a/eslint/docs/src/rules/no-extra-strict.md +++ b/eslint/docs/src/rules/no-extra-strict.md @@ -1,6 +1,5 @@ --- title: no-extra-strict -layout: doc further_reading: - https://es5.github.io/#C diff --git a/eslint/docs/src/rules/no-fallthrough.md b/eslint/docs/src/rules/no-fallthrough.md index 7986b81..c49293c 100644 --- a/eslint/docs/src/rules/no-fallthrough.md +++ b/eslint/docs/src/rules/no-fallthrough.md @@ -1,6 +1,5 @@ --- title: no-fallthrough -layout: doc rule_type: problem related_rules: - default-case @@ -33,7 +32,7 @@ switch(foo) { } ``` -That works fine when you don't want a fallthrough, but what if the fallthrough is intentional, there is no way to indicate that in the language. It's considered a best practice to always indicate when a fallthrough is intentional using a comment which matches the `/falls?\s?through/i` regular expression: +That works fine when you don't want a fallthrough, but what if the fallthrough is intentional, there is no way to indicate that in the language. It's considered a best practice to always indicate when a fallthrough is intentional using a comment which matches the `/falls?\s?through/i` regular expression but isn't a directive: ```js switch(foo) { @@ -170,7 +169,7 @@ Note that the last `case` statement in these examples does not cause a warning b This rule has an object option: -* Set the `commentPattern` option to a regular expression string to change the test for intentional fallthrough comment. +* Set the `commentPattern` option to a regular expression string to change the test for intentional fallthrough comment. If the fallthrough comment matches a directive, that takes precedence over `commentPattern`. * Set the `allowEmptyCase` option to `true` to allow empty cases regardless of the layout. By default, this rule does not require a fallthrough comment after an empty `case` only if the empty `case` and the next `case` are on the same line or on consecutive lines. diff --git a/eslint/docs/src/rules/no-floating-decimal.md b/eslint/docs/src/rules/no-floating-decimal.md index e1c26d2..30a4331 100644 --- a/eslint/docs/src/rules/no-floating-decimal.md +++ b/eslint/docs/src/rules/no-floating-decimal.md @@ -1,6 +1,5 @@ --- title: no-floating-decimal -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-func-assign.md b/eslint/docs/src/rules/no-func-assign.md index 84971db..ffbcb46 100644 --- a/eslint/docs/src/rules/no-func-assign.md +++ b/eslint/docs/src/rules/no-func-assign.md @@ -1,6 +1,5 @@ --- title: no-func-assign -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-global-assign.md b/eslint/docs/src/rules/no-global-assign.md index 95ca90d..dcdd05d 100644 --- a/eslint/docs/src/rules/no-global-assign.md +++ b/eslint/docs/src/rules/no-global-assign.md @@ -1,6 +1,5 @@ --- title: no-global-assign -layout: doc rule_type: suggestion related_rules: - no-extend-native @@ -24,8 +23,8 @@ This rule disallows modifications to read-only global variables. ESLint has the capability to configure global variables as read-only. -* [Specifying Environments](../user-guide/configuring#specifying-environments) -* [Specifying Globals](../user-guide/configuring#specifying-globals) +* [Specifying Environments](../use/configure#specifying-environments) +* [Specifying Globals](../use/configure#specifying-globals) Examples of **incorrect** code for this rule: diff --git a/eslint/docs/src/rules/no-implicit-coercion.md b/eslint/docs/src/rules/no-implicit-coercion.md index 746c988..a7d3054 100644 --- a/eslint/docs/src/rules/no-implicit-coercion.md +++ b/eslint/docs/src/rules/no-implicit-coercion.md @@ -1,6 +1,5 @@ --- title: no-implicit-coercion -layout: doc rule_type: suggestion --- @@ -103,6 +102,8 @@ Examples of **correct** code for the default `{ "number": true }` option: var n = Number(foo); var n = parseFloat(foo); var n = parseInt(foo, 10); + +var n = foo * 1/4; // `* 1` is allowed when followed by the `/` operator ``` ::: diff --git a/eslint/docs/src/rules/no-implicit-globals.md b/eslint/docs/src/rules/no-implicit-globals.md index 15c0386..1e5c5fa 100644 --- a/eslint/docs/src/rules/no-implicit-globals.md +++ b/eslint/docs/src/rules/no-implicit-globals.md @@ -1,10 +1,10 @@ --- title: no-implicit-globals -layout: doc rule_type: suggestion related_rules: - no-undef - no-global-assign +- no-unused-vars further_reading: - https://benalman.com/news/2010/11/immediately-invoked-function-expression/ - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Undeclared_var @@ -122,8 +122,8 @@ A read-only global variable can be a built-in ES global (e.g. `Array`), an envir (e.g. `window` in the browser environment), or a global variable defined as `readonly` in the configuration file or in a `/*global */` comment. -* [Specifying Environments](../user-guide/configuring#specifying-environments) -* [Specifying Globals](../user-guide/configuring#specifying-globals) +* [Specifying Environments](../use/configure#specifying-environments) +* [Specifying Globals](../use/configure#specifying-globals) Examples of **incorrect** code for this rule: @@ -255,6 +255,22 @@ window.MyGlobalFunction = (function() { ::: +### exported + +You can use `/* exported variableName */` block comments in the same way as in [`no-unused-vars`](./no-unused-vars). See the [`no-unused-vars` exported section](./no-unused-vars#exported) for details. + +Examples of **correct** code for `/* exported variableName */` operation: + +::: correct + +```js +/* exported global_var */ + +var global_var = 42; +``` + +::: + ## When Not To Use It In the case of a browser script, if you want to be able to explicitly declare variables and functions in the global scope, diff --git a/eslint/docs/src/rules/no-implied-eval.md b/eslint/docs/src/rules/no-implied-eval.md index acfcfd5..542e39a 100644 --- a/eslint/docs/src/rules/no-implied-eval.md +++ b/eslint/docs/src/rules/no-implied-eval.md @@ -1,6 +1,5 @@ --- title: no-implied-eval -layout: doc rule_type: suggestion related_rules: - no-eval diff --git a/eslint/docs/src/rules/no-import-assign.md b/eslint/docs/src/rules/no-import-assign.md index c19cfd2..ca4b912 100644 --- a/eslint/docs/src/rules/no-import-assign.md +++ b/eslint/docs/src/rules/no-import-assign.md @@ -1,6 +1,5 @@ --- title: no-import-assign -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-inline-comments.md b/eslint/docs/src/rules/no-inline-comments.md index 093214c..687464e 100644 --- a/eslint/docs/src/rules/no-inline-comments.md +++ b/eslint/docs/src/rules/no-inline-comments.md @@ -1,6 +1,5 @@ --- title: no-inline-comments -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-inner-declarations.md b/eslint/docs/src/rules/no-inner-declarations.md index d098999..8828fab 100644 --- a/eslint/docs/src/rules/no-inner-declarations.md +++ b/eslint/docs/src/rules/no-inner-declarations.md @@ -1,6 +1,5 @@ --- title: no-inner-declarations -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-invalid-regexp.md b/eslint/docs/src/rules/no-invalid-regexp.md index 59fe9dc..565b13c 100644 --- a/eslint/docs/src/rules/no-invalid-regexp.md +++ b/eslint/docs/src/rules/no-invalid-regexp.md @@ -1,6 +1,5 @@ --- title: no-invalid-regexp -layout: doc rule_type: problem further_reading: - https://es5.github.io/#x7.8.5 diff --git a/eslint/docs/src/rules/no-invalid-this.md b/eslint/docs/src/rules/no-invalid-this.md index 62c11ba..f3aa6ed 100644 --- a/eslint/docs/src/rules/no-invalid-this.md +++ b/eslint/docs/src/rules/no-invalid-this.md @@ -1,6 +1,5 @@ --- title: no-invalid-this -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-irregular-whitespace.md b/eslint/docs/src/rules/no-irregular-whitespace.md index 3f27133..258733b 100644 --- a/eslint/docs/src/rules/no-irregular-whitespace.md +++ b/eslint/docs/src/rules/no-irregular-whitespace.md @@ -1,6 +1,5 @@ --- title: no-irregular-whitespace -layout: doc rule_type: problem further_reading: - https://es5.github.io/#x7.2 diff --git a/eslint/docs/src/rules/no-iterator.md b/eslint/docs/src/rules/no-iterator.md index b0335e8..7018682 100644 --- a/eslint/docs/src/rules/no-iterator.md +++ b/eslint/docs/src/rules/no-iterator.md @@ -1,6 +1,5 @@ --- title: no-iterator -layout: doc rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators diff --git a/eslint/docs/src/rules/no-label-var.md b/eslint/docs/src/rules/no-label-var.md index 422538a..885a20f 100644 --- a/eslint/docs/src/rules/no-label-var.md +++ b/eslint/docs/src/rules/no-label-var.md @@ -1,6 +1,5 @@ --- title: no-label-var -layout: doc rule_type: suggestion related_rules: - no-extra-label diff --git a/eslint/docs/src/rules/no-labels.md b/eslint/docs/src/rules/no-labels.md index c8d65c8..d2437ea 100644 --- a/eslint/docs/src/rules/no-labels.md +++ b/eslint/docs/src/rules/no-labels.md @@ -1,6 +1,5 @@ --- title: no-labels -layout: doc rule_type: suggestion related_rules: - no-extra-label diff --git a/eslint/docs/src/rules/no-lone-blocks.md b/eslint/docs/src/rules/no-lone-blocks.md index 22703f8..efce824 100644 --- a/eslint/docs/src/rules/no-lone-blocks.md +++ b/eslint/docs/src/rules/no-lone-blocks.md @@ -1,6 +1,5 @@ --- title: no-lone-blocks -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-lonely-if.md b/eslint/docs/src/rules/no-lonely-if.md index 2b306d4..9b6eb4f 100644 --- a/eslint/docs/src/rules/no-lonely-if.md +++ b/eslint/docs/src/rules/no-lonely-if.md @@ -1,6 +1,5 @@ --- title: no-lonely-if -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-loop-func.md b/eslint/docs/src/rules/no-loop-func.md index 07e22b5..659fd3e 100644 --- a/eslint/docs/src/rules/no-loop-func.md +++ b/eslint/docs/src/rules/no-loop-func.md @@ -1,6 +1,5 @@ --- title: no-loop-func -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-loss-of-precision.md b/eslint/docs/src/rules/no-loss-of-precision.md index 2eed801..1bec492 100644 --- a/eslint/docs/src/rules/no-loss-of-precision.md +++ b/eslint/docs/src/rules/no-loss-of-precision.md @@ -1,6 +1,5 @@ --- title: no-loss-of-precision -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-magic-numbers.md b/eslint/docs/src/rules/no-magic-numbers.md index fca63d6..638e0b5 100644 --- a/eslint/docs/src/rules/no-magic-numbers.md +++ b/eslint/docs/src/rules/no-magic-numbers.md @@ -1,6 +1,5 @@ --- title: no-magic-numbers -layout: doc rule_type: suggestion --- @@ -194,6 +193,45 @@ let head; ::: +### ignoreClassFieldInitialValues + +A boolean to specify if numbers used as initial values of class fields are considered okay. `false` by default. + +Examples of **correct** code for the `{ "ignoreClassFieldInitialValues": true }` option: + +::: correct + +```js +/*eslint no-magic-numbers: ["error", { "ignoreClassFieldInitialValues": true }]*/ + +class C { + foo = 2; + bar = -3; + #baz = 4; + static qux = 5; +} +``` + +::: + +Examples of **incorrect** code for the `{ "ignoreClassFieldInitialValues": true }` option: + +::: incorrect + +```js +/*eslint no-magic-numbers: ["error", { "ignoreClassFieldInitialValues": true }]*/ + +class C { + foo = 2 + 3; +} + +class D { + 2; +} +``` + +::: + ### enforceConst A boolean to specify if we should check for the const keyword in variable declaration of numbers. `false` by default. diff --git a/eslint/docs/src/rules/no-misleading-character-class.md b/eslint/docs/src/rules/no-misleading-character-class.md index 57b3ee6..a0d9e6d 100644 --- a/eslint/docs/src/rules/no-misleading-character-class.md +++ b/eslint/docs/src/rules/no-misleading-character-class.md @@ -1,6 +1,5 @@ --- title: no-misleading-character-class -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-mixed-operators.md b/eslint/docs/src/rules/no-mixed-operators.md index 17422cb..07ab40f 100644 --- a/eslint/docs/src/rules/no-mixed-operators.md +++ b/eslint/docs/src/rules/no-mixed-operators.md @@ -1,6 +1,5 @@ --- title: no-mixed-operators -layout: doc rule_type: suggestion related_rules: - no-extra-parens diff --git a/eslint/docs/src/rules/no-mixed-requires.md b/eslint/docs/src/rules/no-mixed-requires.md index 1f813d9..f747ebd 100644 --- a/eslint/docs/src/rules/no-mixed-requires.md +++ b/eslint/docs/src/rules/no-mixed-requires.md @@ -1,11 +1,10 @@ --- title: no-mixed-requires -layout: doc rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). In the Node.js community it is often customary to separate initializations with calls to `require` modules from other variable declarations, sometimes also grouping them by the type of module. This rule helps you enforce this convention. diff --git a/eslint/docs/src/rules/no-mixed-spaces-and-tabs.md b/eslint/docs/src/rules/no-mixed-spaces-and-tabs.md index 4f34752..29f82e2 100644 --- a/eslint/docs/src/rules/no-mixed-spaces-and-tabs.md +++ b/eslint/docs/src/rules/no-mixed-spaces-and-tabs.md @@ -1,6 +1,5 @@ --- title: no-mixed-spaces-and-tabs -layout: doc rule_type: layout further_reading: - https://www.emacswiki.org/emacs/SmartTabs diff --git a/eslint/docs/src/rules/no-multi-assign.md b/eslint/docs/src/rules/no-multi-assign.md index 351e4a6..7cc4581 100644 --- a/eslint/docs/src/rules/no-multi-assign.md +++ b/eslint/docs/src/rules/no-multi-assign.md @@ -1,6 +1,5 @@ --- title: no-multi-assign -layout: doc rule_type: suggestion related_rules: - max-statements-per-line diff --git a/eslint/docs/src/rules/no-multi-spaces.md b/eslint/docs/src/rules/no-multi-spaces.md index 0345336..b23a979 100644 --- a/eslint/docs/src/rules/no-multi-spaces.md +++ b/eslint/docs/src/rules/no-multi-spaces.md @@ -1,6 +1,5 @@ --- title: no-multi-spaces -layout: doc rule_type: layout related_rules: - key-spacing diff --git a/eslint/docs/src/rules/no-multi-str.md b/eslint/docs/src/rules/no-multi-str.md index c118a51..67444b4 100644 --- a/eslint/docs/src/rules/no-multi-str.md +++ b/eslint/docs/src/rules/no-multi-str.md @@ -1,6 +1,5 @@ --- title: no-multi-str -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-multiple-empty-lines.md b/eslint/docs/src/rules/no-multiple-empty-lines.md index 8ca5d23..5457c54 100644 --- a/eslint/docs/src/rules/no-multiple-empty-lines.md +++ b/eslint/docs/src/rules/no-multiple-empty-lines.md @@ -1,6 +1,5 @@ --- title: no-multiple-empty-lines -layout: doc rule_type: layout --- @@ -32,6 +31,7 @@ Examples of **incorrect** code for this rule with the default `{ "max": 2 }` opt var foo = 5; + var bar = 3; ``` @@ -46,6 +46,7 @@ Examples of **correct** code for this rule with the default `{ "max": 2 }` optio var foo = 5; + var bar = 3; ``` @@ -62,8 +63,10 @@ Examples of **incorrect** code for this rule with the `{ max: 2, maxEOF: 0 }` op var foo = 5; + var bar = 3; + ``` ::: @@ -77,6 +80,7 @@ Examples of **correct** code for this rule with the `{ max: 2, maxEOF: 0 }` opti var foo = 5; + var bar = 3; ``` @@ -86,29 +90,37 @@ var bar = 3; **Incorrect**: +::: incorrect + ```js -1 /*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/⏎ -2 ⏎ -3 var foo = 5;⏎ -4 ⏎ -5 ⏎ -6 var bar = 3;⏎ -7 ⏎ -8 +/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/⏎ +⏎ +var foo = 5;⏎ +⏎ +⏎ +var bar = 3;⏎ +⏎ + ``` +::: + **Correct**: +::: correct + ```js -1 /*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/⏎ -2 ⏎ -3 var foo = 5;⏎ -4 ⏎ -5 ⏎ -6 var bar = 3;⏎ -7 +/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/⏎ +⏎ +var foo = 5;⏎ +⏎ +⏎ +var bar = 3;⏎ + ``` +::: + ### maxBOF Examples of **incorrect** code for this rule with the `{ max: 2, maxBOF: 1 }` options: @@ -118,8 +130,10 @@ Examples of **incorrect** code for this rule with the `{ max: 2, maxBOF: 1 }` op ```js /*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxBOF": 1 }]*/ + var foo = 5; + var bar = 3; ``` @@ -134,6 +148,7 @@ Examples of **correct** code for this rule with the `{ max: 2, maxBOF: 1 }` opti var foo = 5; + var bar = 3; ``` diff --git a/eslint/docs/src/rules/no-native-reassign.md b/eslint/docs/src/rules/no-native-reassign.md index 226a173..c2c658f 100644 --- a/eslint/docs/src/rules/no-native-reassign.md +++ b/eslint/docs/src/rules/no-native-reassign.md @@ -1,6 +1,5 @@ --- title: no-native-reassign -layout: doc rule_type: suggestion related_rules: - no-extend-native @@ -25,8 +24,8 @@ This rule disallows modifications to read-only global variables. ESLint has the capability to configure global variables as read-only. -* [Specifying Environments](../user-guide/configuring#specifying-environments) -* [Specifying Globals](../user-guide/configuring#specifying-globals) +* [Specifying Environments](../use/configure#specifying-environments) +* [Specifying Globals](../use/configure#specifying-globals) Examples of **incorrect** code for this rule: diff --git a/eslint/docs/src/rules/no-negated-condition.md b/eslint/docs/src/rules/no-negated-condition.md index ab9e226..932c2e8 100644 --- a/eslint/docs/src/rules/no-negated-condition.md +++ b/eslint/docs/src/rules/no-negated-condition.md @@ -1,6 +1,5 @@ --- title: no-negated-condition -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-negated-in-lhs.md b/eslint/docs/src/rules/no-negated-in-lhs.md index da39b05..2a27116 100644 --- a/eslint/docs/src/rules/no-negated-in-lhs.md +++ b/eslint/docs/src/rules/no-negated-in-lhs.md @@ -1,6 +1,5 @@ --- title: no-negated-in-lhs -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-nested-ternary.md b/eslint/docs/src/rules/no-nested-ternary.md index 9716be8..58bcad3 100644 --- a/eslint/docs/src/rules/no-nested-ternary.md +++ b/eslint/docs/src/rules/no-nested-ternary.md @@ -1,6 +1,5 @@ --- title: no-nested-ternary -layout: doc rule_type: suggestion related_rules: - no-ternary diff --git a/eslint/docs/src/rules/no-new-func.md b/eslint/docs/src/rules/no-new-func.md index abe4d00..8289e3e 100644 --- a/eslint/docs/src/rules/no-new-func.md +++ b/eslint/docs/src/rules/no-new-func.md @@ -1,6 +1,5 @@ --- title: no-new-func -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-new-native-nonconstructor.md b/eslint/docs/src/rules/no-new-native-nonconstructor.md new file mode 100644 index 0000000..c9b7567 --- /dev/null +++ b/eslint/docs/src/rules/no-new-native-nonconstructor.md @@ -0,0 +1,74 @@ +--- +title: no-new-native-nonconstructor +layout: doc +rule_type: problem +related_rules: +- no-obj-calls +further_reading: +- https://tc39.es/ecma262/#sec-symbol-constructor +- https://tc39.es/ecma262/#sec-bigint-constructor +--- + + + +It is a convention in JavaScript that global variables beginning with an uppercase letter typically represent classes that can be instantiated using the `new` operator, such as `new Array` and `new Map`. Confusingly, JavaScript also provides some global variables that begin with an uppercase letter that cannot be called using the `new` operator and will throw an error if you attempt to do so. These are typically functions that are related to data types and are easy to mistake for classes. Consider the following example: + +```js +// throws a TypeError +let foo = new Symbol("foo"); + +// throws a TypeError +let result = new BigInt(9007199254740991); +``` + +Both `new Symbol` and `new BigInt` throw a type error because they are functions and not classes. It is easy to make this mistake by assuming the uppercase letters indicate classes. + +## Rule Details + +This rule is aimed at preventing the accidental calling of native JavaScript global functions with the `new` operator. These functions are: + +* `Symbol` +* `BigInt` + +## Examples + +Examples of **incorrect** code for this rule: + +::: incorrect + +```js +/*eslint no-new-native-nonconstructor: "error"*/ +/*eslint-env es2022*/ + +var foo = new Symbol('foo'); +var bar = new BigInt(9007199254740991); +``` + +::: + +Examples of **correct** code for this rule: + +::: correct + +```js +/*eslint no-new-native-nonconstructor: "error"*/ +/*eslint-env es2022*/ + +var foo = Symbol('foo'); +var bar = BigInt(9007199254740991); + +// Ignores shadowed Symbol. +function baz(Symbol) { + const qux = new Symbol("baz"); +} +function quux(BigInt) { + const corge = new BigInt(9007199254740991); +} + +``` + +::: + +## When Not To Use It + +This rule should not be used in ES3/5 environments. diff --git a/eslint/docs/src/rules/no-new-object.md b/eslint/docs/src/rules/no-new-object.md index 377a682..41b02f8 100644 --- a/eslint/docs/src/rules/no-new-object.md +++ b/eslint/docs/src/rules/no-new-object.md @@ -1,6 +1,5 @@ --- title: no-new-object -layout: doc rule_type: suggestion related_rules: - no-array-constructor diff --git a/eslint/docs/src/rules/no-new-require.md b/eslint/docs/src/rules/no-new-require.md index f168795..494d488 100644 --- a/eslint/docs/src/rules/no-new-require.md +++ b/eslint/docs/src/rules/no-new-require.md @@ -1,11 +1,10 @@ --- title: no-new-require -layout: doc rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). The `require` function is used to include modules that exist in separate files, such as: diff --git a/eslint/docs/src/rules/no-new-symbol.md b/eslint/docs/src/rules/no-new-symbol.md index f182f27..44c34a4 100644 --- a/eslint/docs/src/rules/no-new-symbol.md +++ b/eslint/docs/src/rules/no-new-symbol.md @@ -1,6 +1,5 @@ --- title: no-new-symbol -layout: doc rule_type: problem further_reading: - https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-objects diff --git a/eslint/docs/src/rules/no-new-wrappers.md b/eslint/docs/src/rules/no-new-wrappers.md index b15488d..da0a860 100644 --- a/eslint/docs/src/rules/no-new-wrappers.md +++ b/eslint/docs/src/rules/no-new-wrappers.md @@ -1,6 +1,5 @@ --- title: no-new-wrappers -layout: doc rule_type: suggestion related_rules: - no-array-constructor diff --git a/eslint/docs/src/rules/no-new.md b/eslint/docs/src/rules/no-new.md index 15f4b9e..c8cea29 100644 --- a/eslint/docs/src/rules/no-new.md +++ b/eslint/docs/src/rules/no-new.md @@ -1,6 +1,5 @@ --- title: no-new -layout: doc rule_type: suggestion --- @@ -44,7 +43,7 @@ Examples of **correct** code for this rule: var thing = new Thing(); -Thing(); +Foo(); ``` ::: diff --git a/eslint/docs/src/rules/no-nonoctal-decimal-escape.md b/eslint/docs/src/rules/no-nonoctal-decimal-escape.md index ec267e1..6ca59fd 100644 --- a/eslint/docs/src/rules/no-nonoctal-decimal-escape.md +++ b/eslint/docs/src/rules/no-nonoctal-decimal-escape.md @@ -1,6 +1,5 @@ --- title: no-nonoctal-decimal-escape -layout: doc rule_type: suggestion related_rules: - no-octal-escape diff --git a/eslint/docs/src/rules/no-obj-calls.md b/eslint/docs/src/rules/no-obj-calls.md index 78f4de0..8d72e4c 100644 --- a/eslint/docs/src/rules/no-obj-calls.md +++ b/eslint/docs/src/rules/no-obj-calls.md @@ -1,6 +1,5 @@ --- title: no-obj-calls -layout: doc rule_type: problem further_reading: - https://es5.github.io/#x15.8 @@ -18,13 +17,17 @@ The [ECMAScript 2015 specification](https://www.ecma-international.org/ecma-262/ > The Reflect object also does not have a `[[Call]]` internal method; it is not possible to invoke the Reflect object as a function. -And the [ECMAScript 2017 specification](https://www.ecma-international.org/ecma-262/8.0/index.html#sec-atomics-object) makes it clear that `Atomics` cannot be invoked: +The [ECMAScript 2017 specification](https://www.ecma-international.org/ecma-262/8.0/index.html#sec-atomics-object) makes it clear that `Atomics` cannot be invoked: > The Atomics object does not have a `[[Call]]` internal method; it is not possible to invoke the Atomics object as a function. +And the [ECMAScript Internationalization API Specification](https://tc39.es/ecma402/#intl-object) makes it clear that `Intl` cannot be invoked: + +> The Intl object does not have a `[[Call]]` internal method; it is not possible to invoke the Intl object as a function. + ## Rule Details -This rule disallows calling the `Math`, `JSON`, `Reflect` and `Atomics` objects as functions. +This rule disallows calling the `Math`, `JSON`, `Reflect`, `Atomics` and `Intl` objects as functions. This rule also disallows using these objects as constructors with the `new` operator. @@ -34,7 +37,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-obj-calls: "error"*/ -/*eslint-env es2017*/ +/*eslint-env es2017, browser */ var math = Math(); @@ -51,6 +54,10 @@ var newReflect = new Reflect(); var atomics = Atomics(); var newAtomics = new Atomics(); + +var intl = Intl(); + +var newIntl = new Intl(); ``` ::: @@ -61,7 +68,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-obj-calls: "error"*/ -/*eslint-env es2017*/ +/*eslint-env es2017, browser*/ function area(r) { return Math.PI * r * r; @@ -72,6 +79,8 @@ var object = JSON.parse("{}"); var value = Reflect.get({ x: 1, y: 2 }, "x"); var first = Atomics.load(foo, 0); + +var segmenterFr = new Intl.Segmenter("fr", { granularity: "word" }); ``` ::: diff --git a/eslint/docs/src/rules/no-octal-escape.md b/eslint/docs/src/rules/no-octal-escape.md index 38fa4fc..ae8fecd 100644 --- a/eslint/docs/src/rules/no-octal-escape.md +++ b/eslint/docs/src/rules/no-octal-escape.md @@ -1,6 +1,5 @@ --- title: no-octal-escape -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-octal.md b/eslint/docs/src/rules/no-octal.md index 2c961a4..6a8fa77 100644 --- a/eslint/docs/src/rules/no-octal.md +++ b/eslint/docs/src/rules/no-octal.md @@ -1,6 +1,5 @@ --- title: no-octal -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-param-reassign.md b/eslint/docs/src/rules/no-param-reassign.md index db740ea..2bddf7b 100644 --- a/eslint/docs/src/rules/no-param-reassign.md +++ b/eslint/docs/src/rules/no-param-reassign.md @@ -1,6 +1,5 @@ --- title: no-param-reassign -layout: doc rule_type: suggestion further_reading: - https://spin.atomicobject.com/2011/04/10/javascript-don-t-reassign-your-function-arguments/ diff --git a/eslint/docs/src/rules/no-path-concat.md b/eslint/docs/src/rules/no-path-concat.md index ebde85c..1042f31 100644 --- a/eslint/docs/src/rules/no-path-concat.md +++ b/eslint/docs/src/rules/no-path-concat.md @@ -1,11 +1,10 @@ --- title: no-path-concat -layout: doc rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). In Node.js, the `__dirname` and `__filename` global variables contain the directory path and the file path of the currently executing script file, respectively. Sometimes, developers try to use these variables to create paths to other files, such as: diff --git a/eslint/docs/src/rules/no-plusplus.md b/eslint/docs/src/rules/no-plusplus.md index 47a1038..ba787f1 100644 --- a/eslint/docs/src/rules/no-plusplus.md +++ b/eslint/docs/src/rules/no-plusplus.md @@ -1,6 +1,5 @@ --- title: no-plusplus -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-process-env.md b/eslint/docs/src/rules/no-process-env.md index a08bc25..99cd1f4 100644 --- a/eslint/docs/src/rules/no-process-env.md +++ b/eslint/docs/src/rules/no-process-env.md @@ -1,6 +1,5 @@ --- title: no-process-env -layout: doc rule_type: suggestion further_reading: - https://stackoverflow.com/questions/5869216/how-to-store-node-js-deployment-settings-configuration-files @@ -8,7 +7,7 @@ further_reading: --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). The `process.env` object in Node.js is used to store deployment/configuration parameters. Littering it through out a project could lead to maintenance issues as it's another kind of global dependency. As such, it could lead to merge conflicts in a multi-user setup and deployment issues in a multi-server setup. Instead, one of the best practices is to define all those parameters in a single configuration/settings file which could be accessed throughout the project. diff --git a/eslint/docs/src/rules/no-process-exit.md b/eslint/docs/src/rules/no-process-exit.md index f224218..90e312b 100644 --- a/eslint/docs/src/rules/no-process-exit.md +++ b/eslint/docs/src/rules/no-process-exit.md @@ -1,11 +1,10 @@ --- title: no-process-exit -layout: doc rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). The `process.exit()` method in Node.js is used to immediately stop the Node.js process and exit. This is a dangerous operation because it can occur in any method at any point in time, potentially stopping a Node.js application completely when an error occurs. For example: diff --git a/eslint/docs/src/rules/no-promise-executor-return.md b/eslint/docs/src/rules/no-promise-executor-return.md index 95283d7..2889162 100644 --- a/eslint/docs/src/rules/no-promise-executor-return.md +++ b/eslint/docs/src/rules/no-promise-executor-return.md @@ -1,6 +1,5 @@ --- title: no-promise-executor-return -layout: doc rule_type: problem related_rules: - no-async-promise-executor diff --git a/eslint/docs/src/rules/no-proto.md b/eslint/docs/src/rules/no-proto.md index 7b6c8c4..629cbbf 100644 --- a/eslint/docs/src/rules/no-proto.md +++ b/eslint/docs/src/rules/no-proto.md @@ -1,6 +1,5 @@ --- title: no-proto -layout: doc rule_type: suggestion further_reading: - https://johnresig.com/blog/objectgetprototypeof/ diff --git a/eslint/docs/src/rules/no-prototype-builtins.md b/eslint/docs/src/rules/no-prototype-builtins.md index 43e9373..e3da23e 100644 --- a/eslint/docs/src/rules/no-prototype-builtins.md +++ b/eslint/docs/src/rules/no-prototype-builtins.md @@ -1,6 +1,5 @@ --- title: no-prototype-builtins -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-redeclare.md b/eslint/docs/src/rules/no-redeclare.md index 1c16b12..e66f057 100644 --- a/eslint/docs/src/rules/no-redeclare.md +++ b/eslint/docs/src/rules/no-redeclare.md @@ -1,6 +1,5 @@ --- title: no-redeclare -layout: doc rule_type: suggestion related_rules: - no-shadow diff --git a/eslint/docs/src/rules/no-regex-spaces.md b/eslint/docs/src/rules/no-regex-spaces.md index 48d3e45..f6ef344 100644 --- a/eslint/docs/src/rules/no-regex-spaces.md +++ b/eslint/docs/src/rules/no-regex-spaces.md @@ -1,6 +1,5 @@ --- title: no-regex-spaces -layout: doc rule_type: suggestion related_rules: - no-div-regex diff --git a/eslint/docs/src/rules/no-reserved-keys.md b/eslint/docs/src/rules/no-reserved-keys.md index 1b4f048..bef6b37 100644 --- a/eslint/docs/src/rules/no-reserved-keys.md +++ b/eslint/docs/src/rules/no-reserved-keys.md @@ -1,6 +1,5 @@ --- title: no-reserved-keys -layout: doc further_reading: - https://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names diff --git a/eslint/docs/src/rules/no-restricted-exports.md b/eslint/docs/src/rules/no-restricted-exports.md index b1b2065..2f83643 100644 --- a/eslint/docs/src/rules/no-restricted-exports.md +++ b/eslint/docs/src/rules/no-restricted-exports.md @@ -1,6 +1,5 @@ --- title: no-restricted-exports -layout: doc rule_type: suggestion --- @@ -18,8 +17,16 @@ By default, this rule doesn't disallow any names. Only the names you specify in This rule has an object option: * `"restrictedNamedExports"` is an array of strings, where each string is a name to be restricted. +* `"restrictDefaultExports"` is an object option with boolean properties to restrict certain default export declarations. The option works only if the `restrictedNamedExports` option does not contain the `"default"` value. The following properties are allowed: + * `direct`: restricts `export default` declarations. + * `named`: restricts `export { foo as default };` declarations. + * `defaultFrom`: restricts `export { default } from 'foo';` declarations. + * `namedFrom`: restricts `export { foo as default } from 'foo';` declarations. + * `namespaceFrom`: restricts `export * as default from 'foo';` declarations. -Examples of **incorrect** code for this rule: +### restrictedNamedExports + +Examples of **incorrect** code for the `"restrictedNamedExports"` option: ::: incorrect @@ -51,7 +58,7 @@ export { "👍" } from "some_module"; ::: -Examples of **correct** code for this rule: +Examples of **correct** code for the `"restrictedNamedExports"` option: ::: correct @@ -83,11 +90,11 @@ export { "👍" as thumbsUp } from "some_module"; ::: -### Default exports +#### Default exports -By design, this rule doesn't disallow `export default` declarations. If you configure `"default"` as a restricted name, that restriction will apply only to named export declarations. +By design, the `"restrictedNamedExports"` option doesn't disallow `export default` declarations. If you configure `"default"` as a restricted name, that restriction will apply only to named export declarations. -Examples of additional **incorrect** code for this rule: +Examples of additional **incorrect** code for the `"restrictedNamedExports": ["default"]` option: ::: incorrect @@ -111,7 +118,7 @@ export { default } from "some_module"; ::: -Examples of additional **correct** code for this rule: +Examples of additional **correct** code for the `"restrictedNamedExports": ["default"]` option: ::: correct @@ -123,6 +130,85 @@ export default function foo() {} ::: +### restrictDefaultExports + +This option allows you to restrict certain `default` declarations. The option works only if the `restrictedNamedExports` option does not contain the `"default"` value. This option accepts the following properties: + +#### direct + +Examples of **incorrect** code for the `"restrictDefaultExports": { "direct": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "direct": true } }]*/ + +export default foo; +export default 42; +export default function foo() {} +``` + +::: + +#### named + +Examples of **incorrect** code for the `"restrictDefaultExports": { "named": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "named": true } }]*/ + +const foo = 123; + +export { foo as default }; +``` + +::: + +#### defaultFrom + +Examples of **incorrect** code for the `"restrictDefaultExports": { "defaultFrom": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "defaultFrom": true } }]*/ + +export { default } from 'foo'; +export { default as default } from 'foo'; +``` + +::: + +#### namedFrom + +Examples of **incorrect** code for the `"restrictDefaultExports": { "namedFrom": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "namedFrom": true } }]*/ + +export { foo as default } from 'foo'; +``` + +::: + +#### namespaceFrom + +Examples of **incorrect** code for the `"restrictDefaultExports": { "namespaceFrom": true }` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "namespaceFrom": true } }]*/ + +export * as default from 'foo'; +``` + +::: + ## Known Limitations This rule doesn't inspect the content of source modules in re-export declarations. In particular, if you are re-exporting everything from another module's export, that export may include a restricted name. This rule cannot detect such cases. diff --git a/eslint/docs/src/rules/no-restricted-globals.md b/eslint/docs/src/rules/no-restricted-globals.md index 0bcf27d..58a4ad0 100644 --- a/eslint/docs/src/rules/no-restricted-globals.md +++ b/eslint/docs/src/rules/no-restricted-globals.md @@ -1,6 +1,5 @@ --- title: no-restricted-globals -layout: doc rule_type: suggestion related_rules: - no-restricted-properties diff --git a/eslint/docs/src/rules/no-restricted-imports.md b/eslint/docs/src/rules/no-restricted-imports.md index 646e1b0..ece0ceb 100644 --- a/eslint/docs/src/rules/no-restricted-imports.md +++ b/eslint/docs/src/rules/no-restricted-imports.md @@ -1,6 +1,5 @@ --- title: no-restricted-imports -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-restricted-modules.md b/eslint/docs/src/rules/no-restricted-modules.md index c8f77bf..eb2ca03 100644 --- a/eslint/docs/src/rules/no-restricted-modules.md +++ b/eslint/docs/src/rules/no-restricted-modules.md @@ -1,11 +1,10 @@ --- title: no-restricted-modules -layout: doc rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). A module in Node.js is a simple or complex functionality organized in a JavaScript file which can be reused throughout the Node.js application. The keyword `require` is used in Node.js/CommonJS to import modules into an application. This way you can have dynamic loading where the loaded module name isn't predefined /static, or where you conditionally load a module only if it's "truly required". diff --git a/eslint/docs/src/rules/no-restricted-properties.md b/eslint/docs/src/rules/no-restricted-properties.md index 758bd5c..77eecc6 100644 --- a/eslint/docs/src/rules/no-restricted-properties.md +++ b/eslint/docs/src/rules/no-restricted-properties.md @@ -1,6 +1,5 @@ --- title: no-restricted-properties -layout: doc rule_type: suggestion related_rules: - no-restricted-globals diff --git a/eslint/docs/src/rules/no-restricted-syntax.md b/eslint/docs/src/rules/no-restricted-syntax.md index bb700f0..25d419c 100644 --- a/eslint/docs/src/rules/no-restricted-syntax.md +++ b/eslint/docs/src/rules/no-restricted-syntax.md @@ -1,6 +1,5 @@ --- title: no-restricted-syntax -layout: doc rule_type: suggestion related_rules: - no-alert @@ -14,7 +13,7 @@ JavaScript has a lot of language features, and not everyone likes all of them. A Rather than creating separate rules for every language feature you want to turn off, this rule allows you to configure the syntax elements you want to restrict use of. These elements are represented by their [ESTree](https://github.com/estree/estree) node types. For example, a function declaration is represented by `FunctionDeclaration` and the `with` statement is represented by `WithStatement`. You may find the full list of AST node names you can use [on GitHub](https://github.com/eslint/eslint-visitor-keys/blob/main/lib/visitor-keys.js) and use [AST Explorer](https://astexplorer.net/) with the espree parser to see what type of nodes your code consists of. -You can also specify [AST selectors](../developer-guide/selectors) to restrict, allowing much more precise control over syntax patterns. +You can also specify [AST selectors](../extend/selectors) to restrict, allowing much more precise control over syntax patterns. ## Rule Details diff --git a/eslint/docs/src/rules/no-return-assign.md b/eslint/docs/src/rules/no-return-assign.md index 0a63724..8927758 100644 --- a/eslint/docs/src/rules/no-return-assign.md +++ b/eslint/docs/src/rules/no-return-assign.md @@ -1,6 +1,5 @@ --- title: no-return-assign -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-return-await.md b/eslint/docs/src/rules/no-return-await.md index 164843d..8f42100 100644 --- a/eslint/docs/src/rules/no-return-await.md +++ b/eslint/docs/src/rules/no-return-await.md @@ -1,6 +1,5 @@ --- title: no-return-await -layout: doc rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function diff --git a/eslint/docs/src/rules/no-script-url.md b/eslint/docs/src/rules/no-script-url.md index 6255cf9..838a2f4 100644 --- a/eslint/docs/src/rules/no-script-url.md +++ b/eslint/docs/src/rules/no-script-url.md @@ -1,6 +1,5 @@ --- title: no-script-url -layout: doc rule_type: suggestion further_reading: - https://stackoverflow.com/questions/13497971/what-is-the-matter-with-script-targeted-urls diff --git a/eslint/docs/src/rules/no-self-assign.md b/eslint/docs/src/rules/no-self-assign.md index a65b242..ebdd586 100644 --- a/eslint/docs/src/rules/no-self-assign.md +++ b/eslint/docs/src/rules/no-self-assign.md @@ -1,6 +1,5 @@ --- title: no-self-assign -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-self-compare.md b/eslint/docs/src/rules/no-self-compare.md index 12b2776..bf05629 100644 --- a/eslint/docs/src/rules/no-self-compare.md +++ b/eslint/docs/src/rules/no-self-compare.md @@ -1,6 +1,5 @@ --- title: no-self-compare -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-sequences.md b/eslint/docs/src/rules/no-sequences.md index dcc13f1..cb2b99c 100644 --- a/eslint/docs/src/rules/no-sequences.md +++ b/eslint/docs/src/rules/no-sequences.md @@ -1,6 +1,5 @@ --- title: no-sequences -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-setter-return.md b/eslint/docs/src/rules/no-setter-return.md index 2ed21d3..ceb4558 100644 --- a/eslint/docs/src/rules/no-setter-return.md +++ b/eslint/docs/src/rules/no-setter-return.md @@ -1,6 +1,5 @@ --- title: no-setter-return -layout: doc rule_type: problem related_rules: - getter-return diff --git a/eslint/docs/src/rules/no-shadow-restricted-names.md b/eslint/docs/src/rules/no-shadow-restricted-names.md index cea6a12..308772e 100644 --- a/eslint/docs/src/rules/no-shadow-restricted-names.md +++ b/eslint/docs/src/rules/no-shadow-restricted-names.md @@ -1,6 +1,5 @@ --- title: no-shadow-restricted-names -layout: doc rule_type: suggestion related_rules: - no-shadow diff --git a/eslint/docs/src/rules/no-shadow.md b/eslint/docs/src/rules/no-shadow.md index 229cdcb..e4a081f 100644 --- a/eslint/docs/src/rules/no-shadow.md +++ b/eslint/docs/src/rules/no-shadow.md @@ -1,6 +1,5 @@ --- title: no-shadow -layout: doc rule_type: suggestion related_rules: - no-shadow-restricted-names diff --git a/eslint/docs/src/rules/no-space-before-semi.md b/eslint/docs/src/rules/no-space-before-semi.md index 5e50520..4c6650f 100644 --- a/eslint/docs/src/rules/no-space-before-semi.md +++ b/eslint/docs/src/rules/no-space-before-semi.md @@ -1,6 +1,5 @@ --- title: no-space-before-semi -layout: doc related_rules: - semi diff --git a/eslint/docs/src/rules/no-spaced-func.md b/eslint/docs/src/rules/no-spaced-func.md index c844996..099efcf 100644 --- a/eslint/docs/src/rules/no-spaced-func.md +++ b/eslint/docs/src/rules/no-spaced-func.md @@ -1,6 +1,5 @@ --- title: no-spaced-func -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/no-sparse-arrays.md b/eslint/docs/src/rules/no-sparse-arrays.md index cbeb57c..ec41bb2 100644 --- a/eslint/docs/src/rules/no-sparse-arrays.md +++ b/eslint/docs/src/rules/no-sparse-arrays.md @@ -1,6 +1,5 @@ --- title: no-sparse-arrays -layout: doc rule_type: problem further_reading: - https://www.nczonline.net/blog/2007/09/09/inconsistent-array-literals/ diff --git a/eslint/docs/src/rules/no-sync.md b/eslint/docs/src/rules/no-sync.md index 4b5eb57..2549744 100644 --- a/eslint/docs/src/rules/no-sync.md +++ b/eslint/docs/src/rules/no-sync.md @@ -1,11 +1,10 @@ --- title: no-sync -layout: doc rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). In Node.js, most I/O is done through asynchronous methods. However, there are often synchronous versions of the asynchronous methods. For example, `fs.exists()` and `fs.existsSync()`. In some contexts, using synchronous operations is okay (if, as with ESLint, you are writing a command line utility). However, in other contexts the use of synchronous operations is considered a bad practice that should be avoided. For example, if you are running a high-travel web server on Node.js, you should consider carefully if you want to allow any synchronous operations that could lock up the server. diff --git a/eslint/docs/src/rules/no-tabs.md b/eslint/docs/src/rules/no-tabs.md index 279e0c8..316db62 100644 --- a/eslint/docs/src/rules/no-tabs.md +++ b/eslint/docs/src/rules/no-tabs.md @@ -1,6 +1,5 @@ --- title: no-tabs -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/no-template-curly-in-string.md b/eslint/docs/src/rules/no-template-curly-in-string.md index 79f42e7..699e0a8 100644 --- a/eslint/docs/src/rules/no-template-curly-in-string.md +++ b/eslint/docs/src/rules/no-template-curly-in-string.md @@ -1,6 +1,5 @@ --- title: no-template-curly-in-string -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-ternary.md b/eslint/docs/src/rules/no-ternary.md index 7502c5b..b4ff258 100644 --- a/eslint/docs/src/rules/no-ternary.md +++ b/eslint/docs/src/rules/no-ternary.md @@ -1,6 +1,5 @@ --- title: no-ternary -layout: doc rule_type: suggestion related_rules: - no-nested-ternary diff --git a/eslint/docs/src/rules/no-this-before-super.md b/eslint/docs/src/rules/no-this-before-super.md index e6e26dd..f1425b5 100644 --- a/eslint/docs/src/rules/no-this-before-super.md +++ b/eslint/docs/src/rules/no-this-before-super.md @@ -1,6 +1,5 @@ --- title: no-this-before-super -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-throw-literal.md b/eslint/docs/src/rules/no-throw-literal.md index 6f57e36..621bb11 100644 --- a/eslint/docs/src/rules/no-throw-literal.md +++ b/eslint/docs/src/rules/no-throw-literal.md @@ -1,6 +1,5 @@ --- title: no-throw-literal -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-trailing-spaces.md b/eslint/docs/src/rules/no-trailing-spaces.md index 831ab33..fba4f7d 100644 --- a/eslint/docs/src/rules/no-trailing-spaces.md +++ b/eslint/docs/src/rules/no-trailing-spaces.md @@ -1,6 +1,5 @@ --- title: no-trailing-spaces -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/no-undef-init.md b/eslint/docs/src/rules/no-undef-init.md index 9f36bc4..f49ee8c 100644 --- a/eslint/docs/src/rules/no-undef-init.md +++ b/eslint/docs/src/rules/no-undef-init.md @@ -1,6 +1,5 @@ --- title: no-undef-init -layout: doc rule_type: suggestion related_rules: - no-undefined diff --git a/eslint/docs/src/rules/no-undef.md b/eslint/docs/src/rules/no-undef.md index 94a7352..0bc7a28 100644 --- a/eslint/docs/src/rules/no-undef.md +++ b/eslint/docs/src/rules/no-undef.md @@ -1,6 +1,5 @@ --- title: no-undef -layout: doc rule_type: problem related_rules: - no-global-assign @@ -13,7 +12,7 @@ This rule can help you locate potential ReferenceErrors resulting from misspelli ## Rule Details -Any reference to an undeclared variable causes a warning, unless the variable is explicitly mentioned in a `/*global ...*/` comment, or specified in the [`globals` key in the configuration file](../user-guide/configuring/language-options#using-configuration-files-1). A common use case for these is if you intentionally use globals that are defined elsewhere (e.g. in a script sourced from HTML). +Any reference to an undeclared variable causes a warning, unless the variable is explicitly mentioned in a `/*global ...*/` comment, or specified in the [`globals` key in the configuration file](../use/configure/language-options#using-configuration-files-1). A common use case for these is if you intentionally use globals that are defined elsewhere (e.g. in a script sourced from HTML). Examples of **incorrect** code for this rule: @@ -97,7 +96,7 @@ if(typeof a === "string"){} ## Environments -For convenience, ESLint provides shortcuts that pre-define global variables exposed by popular libraries and runtime environments. This rule supports these environments, as listed in [Specifying Environments](../user-guide/configuring/language-options#specifying-environments). A few examples are given below. +For convenience, ESLint provides shortcuts that pre-define global variables exposed by popular libraries and runtime environments. This rule supports these environments, as listed in [Specifying Environments](../use/configure/language-options#specifying-environments). A few examples are given below. ### browser diff --git a/eslint/docs/src/rules/no-undefined.md b/eslint/docs/src/rules/no-undefined.md index 978fc42..9b0be20 100644 --- a/eslint/docs/src/rules/no-undefined.md +++ b/eslint/docs/src/rules/no-undefined.md @@ -1,6 +1,5 @@ --- title: no-undefined -layout: doc rule_type: suggestion related_rules: - no-undef-init @@ -58,6 +57,8 @@ if (foo === undefined) { function foo(undefined) { // ... } + +bar(undefined, "lorem"); ``` ::: @@ -78,6 +79,8 @@ if (typeof foo === "undefined") { } global.undefined = "foo"; + +bar(void 0, "lorem"); ``` ::: diff --git a/eslint/docs/src/rules/no-underscore-dangle.md b/eslint/docs/src/rules/no-underscore-dangle.md index f74beac..d35aefe 100644 --- a/eslint/docs/src/rules/no-underscore-dangle.md +++ b/eslint/docs/src/rules/no-underscore-dangle.md @@ -1,6 +1,5 @@ --- title: no-underscore-dangle -layout: doc rule_type: suggestion --- @@ -61,6 +60,8 @@ This rule has an object option: * `"allowAfterThisConstructor": false` (default) disallows dangling underscores in members of the `this.constructor` object * `"enforceInMethodNames": false` (default) allows dangling underscores in method names * `"enforceInClassFields": false` (default) allows dangling underscores in es2022 class fields names +* `"allowInArrayDestructuring": true` (default) allows dangling underscores in variable names assigned by array destructuring +* `"allowInObjectDestructuring": true` (default) allows dangling underscores in variable names assigned by object destructuring * `"allowFunctionParams": true` (default) allows dangling underscores in function parameter names ### allow @@ -183,6 +184,50 @@ class Foo { ::: +### allowInArrayDestructuring + +Examples of **incorrect** code for this rule with the `{ "allowInArrayDestructuring": false }` option: + +::: incorrect + +```js +/*eslint no-underscore-dangle: ["error", { "allowInArrayDestructuring": false }]*/ + +const [_foo, _bar] = list; +const [foo_, ..._bar] = list; +const [foo, [bar, _baz]] = list; +``` + +::: + +### allowInObjectDestructuring + +Examples of **incorrect** code for this rule with the `{ "allowInObjectDestructuring": false }` option: + +::: incorrect + +```js +/*eslint no-underscore-dangle: ["error", { "allowInObjectDestructuring": false }]*/ + +const { foo, bar: _bar } = collection; +const { foo, bar, _baz } = collection; +``` + +::: + +Examples of **correct** code for this rule with the `{ "allowInObjectDestructuring": false }` option: + +::: correct + +```js +/*eslint no-underscore-dangle: ["error", { "allowInObjectDestructuring": false }]*/ + +const { foo, bar, _baz: { a, b } } = collection; +const { foo, bar, _baz: baz } = collection; +``` + +::: + ### allowFunctionParams Examples of **incorrect** code for this rule with the `{ "allowFunctionParams": false }` option: diff --git a/eslint/docs/src/rules/no-unexpected-multiline.md b/eslint/docs/src/rules/no-unexpected-multiline.md index 6a51c23..158a386 100644 --- a/eslint/docs/src/rules/no-unexpected-multiline.md +++ b/eslint/docs/src/rules/no-unexpected-multiline.md @@ -1,6 +1,5 @@ --- title: no-unexpected-multiline -layout: doc rule_type: problem related_rules: - func-call-spacing diff --git a/eslint/docs/src/rules/no-unmodified-loop-condition.md b/eslint/docs/src/rules/no-unmodified-loop-condition.md index f214c6c..2993209 100644 --- a/eslint/docs/src/rules/no-unmodified-loop-condition.md +++ b/eslint/docs/src/rules/no-unmodified-loop-condition.md @@ -1,6 +1,5 @@ --- title: no-unmodified-loop-condition -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-unneeded-ternary.md b/eslint/docs/src/rules/no-unneeded-ternary.md index e1b0ba2..ca27581 100644 --- a/eslint/docs/src/rules/no-unneeded-ternary.md +++ b/eslint/docs/src/rules/no-unneeded-ternary.md @@ -1,6 +1,5 @@ --- title: no-unneeded-ternary -layout: doc rule_type: suggestion related_rules: - no-ternary diff --git a/eslint/docs/src/rules/no-unreachable-loop.md b/eslint/docs/src/rules/no-unreachable-loop.md index 4f24e57..89b9a76 100644 --- a/eslint/docs/src/rules/no-unreachable-loop.md +++ b/eslint/docs/src/rules/no-unreachable-loop.md @@ -1,6 +1,5 @@ --- title: no-unreachable-loop -layout: doc rule_type: problem related_rules: - no-unreachable diff --git a/eslint/docs/src/rules/no-unreachable.md b/eslint/docs/src/rules/no-unreachable.md index 456fea4..4f76208 100644 --- a/eslint/docs/src/rules/no-unreachable.md +++ b/eslint/docs/src/rules/no-unreachable.md @@ -1,6 +1,5 @@ --- title: no-unreachable -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-unsafe-finally.md b/eslint/docs/src/rules/no-unsafe-finally.md index a2540a0..661fcdf 100644 --- a/eslint/docs/src/rules/no-unsafe-finally.md +++ b/eslint/docs/src/rules/no-unsafe-finally.md @@ -1,6 +1,5 @@ --- title: no-unsafe-finally -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-unsafe-negation.md b/eslint/docs/src/rules/no-unsafe-negation.md index 1d5a22a..522e4ab 100644 --- a/eslint/docs/src/rules/no-unsafe-negation.md +++ b/eslint/docs/src/rules/no-unsafe-negation.md @@ -1,6 +1,5 @@ --- title: no-unsafe-negation -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-unsafe-optional-chaining.md b/eslint/docs/src/rules/no-unsafe-optional-chaining.md index 1161acc..e4cc6f8 100644 --- a/eslint/docs/src/rules/no-unsafe-optional-chaining.md +++ b/eslint/docs/src/rules/no-unsafe-optional-chaining.md @@ -1,6 +1,5 @@ --- title: no-unsafe-optional-chaining -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-unused-expressions.md b/eslint/docs/src/rules/no-unused-expressions.md index cc9f311..c67a2a6 100644 --- a/eslint/docs/src/rules/no-unused-expressions.md +++ b/eslint/docs/src/rules/no-unused-expressions.md @@ -1,6 +1,5 @@ --- title: no-unused-expressions -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-unused-labels.md b/eslint/docs/src/rules/no-unused-labels.md index 2b3ce7c..971c567 100644 --- a/eslint/docs/src/rules/no-unused-labels.md +++ b/eslint/docs/src/rules/no-unused-labels.md @@ -1,6 +1,5 @@ --- title: no-unused-labels -layout: doc rule_type: suggestion related_rules: - no-extra-label diff --git a/eslint/docs/src/rules/no-unused-private-class-members.md b/eslint/docs/src/rules/no-unused-private-class-members.md index bd4accb..c92a882 100644 --- a/eslint/docs/src/rules/no-unused-private-class-members.md +++ b/eslint/docs/src/rules/no-unused-private-class-members.md @@ -1,6 +1,5 @@ --- title: no-unused-private-class-members -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-unused-vars.md b/eslint/docs/src/rules/no-unused-vars.md index 8fd4b68..1b3e59c 100644 --- a/eslint/docs/src/rules/no-unused-vars.md +++ b/eslint/docs/src/rules/no-unused-vars.md @@ -1,6 +1,5 @@ --- title: no-unused-vars -layout: doc rule_type: problem --- @@ -248,25 +247,6 @@ Examples of **correct** code for the `{ "args": "none" }` option: ::: -### ignoreRestSiblings - -The `ignoreRestSiblings` option is a boolean (default: `false`). Using a [Rest Property](https://github.com/tc39/proposal-object-rest-spread) it is possible to "omit" properties from an object, but by default the sibling properties are marked as "unused". With this option enabled the rest property's siblings are ignored. - -Examples of **correct** code for the `{ "ignoreRestSiblings": true }` option: - -::: correct - -```js -/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/ -// 'foo' and 'bar' were ignored because they have a rest property sibling. -var { foo, ...coords } = data; - -var bar; -({ bar, ...coords } = data); -``` - -::: - ### argsIgnorePattern The `argsIgnorePattern` option specifies exceptions not to check for usage: arguments whose names match a regexp pattern. For example, variables whose names begin with an underscore. @@ -286,47 +266,6 @@ foo(); ::: -### destructuredArrayIgnorePattern - -The `destructuredArrayIgnorePattern` option specifies exceptions not to check for usage: elements of array destructuring patterns whose names match a regexp pattern. For example, variables whose names begin with an underscore. - -Examples of **correct** code for the `{ "destructuredArrayIgnorePattern": "^_" }` option: - -::: correct - -```js -/*eslint no-unused-vars: ["error", { "destructuredArrayIgnorePattern": "^_" }]*/ - -const [a, _b, c] = ["a", "b", "c"]; -console.log(a+c); - -const { x: [_a, foo] } = bar; -console.log(foo); - -function baz([_c, x]) { - x; -} -baz(); - -function test({p: [_q, r]}) { - r; -} -test(); - -let _m, n; -foo.forEach(item => { - [_m, n] = item; - console.log(n); -}); - -let _o, p; -_o = 1; -[_o, p] = foo; -p; -``` - -::: - ### caughtErrors The `caughtErrors` option is used for `catch` block arguments validation. @@ -396,6 +335,66 @@ try { ::: +### destructuredArrayIgnorePattern + +The `destructuredArrayIgnorePattern` option specifies exceptions not to check for usage: elements of array destructuring patterns whose names match a regexp pattern. For example, variables whose names begin with an underscore. + +Examples of **correct** code for the `{ "destructuredArrayIgnorePattern": "^_" }` option: + +::: correct + +```js +/*eslint no-unused-vars: ["error", { "destructuredArrayIgnorePattern": "^_" }]*/ + +const [a, _b, c] = ["a", "b", "c"]; +console.log(a+c); + +const { x: [_a, foo] } = bar; +console.log(foo); + +function baz([_c, x]) { + x; +} +baz(); + +function test({p: [_q, r]}) { + r; +} +test(); + +let _m, n; +foo.forEach(item => { + [_m, n] = item; + console.log(n); +}); + +let _o, p; +_o = 1; +[_o, p] = foo; +p; +``` + +::: + +### ignoreRestSiblings + +The `ignoreRestSiblings` option is a boolean (default: `false`). Using a [Rest Property](https://github.com/tc39/proposal-object-rest-spread) it is possible to "omit" properties from an object, but by default the sibling properties are marked as "unused". With this option enabled the rest property's siblings are ignored. + +Examples of **correct** code for the `{ "ignoreRestSiblings": true }` option: + +::: correct + +```js +/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/ +// 'foo' and 'bar' were ignored because they have a rest property sibling. +var { foo, ...coords } = data; + +var bar; +({ bar, ...coords } = data); +``` + +::: + ## When Not To Use It If you don't want to be notified about unused variables or function arguments, you can safely turn this rule off. diff --git a/eslint/docs/src/rules/no-use-before-define.md b/eslint/docs/src/rules/no-use-before-define.md index 9c71d09..726f5fd 100644 --- a/eslint/docs/src/rules/no-use-before-define.md +++ b/eslint/docs/src/rules/no-use-before-define.md @@ -1,6 +1,5 @@ --- title: no-use-before-define -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/no-useless-backreference.md b/eslint/docs/src/rules/no-useless-backreference.md index e534ada..d97f989 100644 --- a/eslint/docs/src/rules/no-useless-backreference.md +++ b/eslint/docs/src/rules/no-useless-backreference.md @@ -1,6 +1,5 @@ --- title: no-useless-backreference -layout: doc rule_type: problem related_rules: - no-control-regex diff --git a/eslint/docs/src/rules/no-useless-call.md b/eslint/docs/src/rules/no-useless-call.md index 65691d6..76ae7ee 100644 --- a/eslint/docs/src/rules/no-useless-call.md +++ b/eslint/docs/src/rules/no-useless-call.md @@ -1,6 +1,5 @@ --- title: no-useless-call -layout: doc rule_type: suggestion related_rules: - prefer-spread diff --git a/eslint/docs/src/rules/no-useless-catch.md b/eslint/docs/src/rules/no-useless-catch.md index ee53ab6..3e312fc 100644 --- a/eslint/docs/src/rules/no-useless-catch.md +++ b/eslint/docs/src/rules/no-useless-catch.md @@ -1,6 +1,5 @@ --- title: no-useless-catch -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-useless-computed-key.md b/eslint/docs/src/rules/no-useless-computed-key.md index 2516d19..f0acb66 100644 --- a/eslint/docs/src/rules/no-useless-computed-key.md +++ b/eslint/docs/src/rules/no-useless-computed-key.md @@ -1,6 +1,5 @@ --- title: no-useless-computed-key -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-useless-concat.md b/eslint/docs/src/rules/no-useless-concat.md index 63062a2..4cdf32d 100644 --- a/eslint/docs/src/rules/no-useless-concat.md +++ b/eslint/docs/src/rules/no-useless-concat.md @@ -1,6 +1,5 @@ --- title: no-useless-concat -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-useless-constructor.md b/eslint/docs/src/rules/no-useless-constructor.md index b01a86d..cfdae43 100644 --- a/eslint/docs/src/rules/no-useless-constructor.md +++ b/eslint/docs/src/rules/no-useless-constructor.md @@ -1,6 +1,5 @@ --- title: no-useless-constructor -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-useless-escape.md b/eslint/docs/src/rules/no-useless-escape.md index 46add21..cd28fda 100644 --- a/eslint/docs/src/rules/no-useless-escape.md +++ b/eslint/docs/src/rules/no-useless-escape.md @@ -1,6 +1,5 @@ --- title: no-useless-escape -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-useless-rename.md b/eslint/docs/src/rules/no-useless-rename.md index 73a5c79..d65541c 100644 --- a/eslint/docs/src/rules/no-useless-rename.md +++ b/eslint/docs/src/rules/no-useless-rename.md @@ -1,6 +1,5 @@ --- title: no-useless-rename -layout: doc rule_type: suggestion related_rules: - object-shorthand diff --git a/eslint/docs/src/rules/no-useless-return.md b/eslint/docs/src/rules/no-useless-return.md index fa82a2f..961f0ae 100644 --- a/eslint/docs/src/rules/no-useless-return.md +++ b/eslint/docs/src/rules/no-useless-return.md @@ -1,6 +1,5 @@ --- title: no-useless-return -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-var.md b/eslint/docs/src/rules/no-var.md index 7ddd173..d1e02ad 100644 --- a/eslint/docs/src/rules/no-var.md +++ b/eslint/docs/src/rules/no-var.md @@ -1,6 +1,5 @@ --- title: no-var -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-void.md b/eslint/docs/src/rules/no-void.md index 5f9d24d..6120cf4 100644 --- a/eslint/docs/src/rules/no-void.md +++ b/eslint/docs/src/rules/no-void.md @@ -1,6 +1,5 @@ --- title: no-void -layout: doc rule_type: suggestion related_rules: - no-undef-init diff --git a/eslint/docs/src/rules/no-warning-comments.md b/eslint/docs/src/rules/no-warning-comments.md index e3f65af..966d42e 100644 --- a/eslint/docs/src/rules/no-warning-comments.md +++ b/eslint/docs/src/rules/no-warning-comments.md @@ -1,6 +1,5 @@ --- title: no-warning-comments -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/no-whitespace-before-property.md b/eslint/docs/src/rules/no-whitespace-before-property.md index d22e45b..db79ca2 100644 --- a/eslint/docs/src/rules/no-whitespace-before-property.md +++ b/eslint/docs/src/rules/no-whitespace-before-property.md @@ -1,6 +1,5 @@ --- title: no-whitespace-before-property -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/no-with.md b/eslint/docs/src/rules/no-with.md index f72fbdb..4fc3f75 100644 --- a/eslint/docs/src/rules/no-with.md +++ b/eslint/docs/src/rules/no-with.md @@ -1,6 +1,5 @@ --- title: no-with -layout: doc rule_type: suggestion further_reading: - https://web.archive.org/web/20200717110117/https://yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/ diff --git a/eslint/docs/src/rules/no-wrap-func.md b/eslint/docs/src/rules/no-wrap-func.md index 76a8406..b0b64be 100644 --- a/eslint/docs/src/rules/no-wrap-func.md +++ b/eslint/docs/src/rules/no-wrap-func.md @@ -1,6 +1,5 @@ --- title: no-wrap-func -layout: doc --- diff --git a/eslint/docs/src/rules/nonblock-statement-body-position.md b/eslint/docs/src/rules/nonblock-statement-body-position.md index fc1189b..78d3320 100644 --- a/eslint/docs/src/rules/nonblock-statement-body-position.md +++ b/eslint/docs/src/rules/nonblock-statement-body-position.md @@ -1,6 +1,5 @@ --- title: nonblock-statement-body-position -layout: doc rule_type: layout further_reading: - https://jscs-dev.github.io/rule/requireNewlineBeforeSingleStatementsInIf @@ -35,7 +34,7 @@ if (foo) bar(); This rule aims to enforce a consistent location for single-line statements. -Note that this rule does not enforce the usage of single-line statements in general. If you would like to disallow single-line statements, use the [`curly`](/docs/rules/curly) rule instead. +Note that this rule does not enforce the usage of single-line statements in general. If you would like to disallow single-line statements, use the [`curly`](curly) rule instead. ### Options @@ -183,4 +182,4 @@ while (foo) ## When Not To Use It -If you're not concerned about consistent locations of single-line statements, you should not turn on this rule. You can also disable this rule if you're using the `"all"` option for the [`curly`](/docs/rules/curly) rule, because this will disallow single-line statements entirely. +If you're not concerned about consistent locations of single-line statements, you should not turn on this rule. You can also disable this rule if you're using the `"all"` option for the [`curly`](curly) rule, because this will disallow single-line statements entirely. diff --git a/eslint/docs/src/rules/object-curly-newline.md b/eslint/docs/src/rules/object-curly-newline.md index ece2fca..a9b8875 100644 --- a/eslint/docs/src/rules/object-curly-newline.md +++ b/eslint/docs/src/rules/object-curly-newline.md @@ -1,6 +1,5 @@ --- title: object-curly-newline -layout: doc rule_type: layout related_rules: - comma-spacing diff --git a/eslint/docs/src/rules/object-curly-spacing.md b/eslint/docs/src/rules/object-curly-spacing.md index 2df4727..4027c9a 100644 --- a/eslint/docs/src/rules/object-curly-spacing.md +++ b/eslint/docs/src/rules/object-curly-spacing.md @@ -1,6 +1,5 @@ --- title: object-curly-spacing -layout: doc rule_type: layout related_rules: - array-bracket-spacing diff --git a/eslint/docs/src/rules/object-property-newline.md b/eslint/docs/src/rules/object-property-newline.md index 314fa7b..50214bd 100644 --- a/eslint/docs/src/rules/object-property-newline.md +++ b/eslint/docs/src/rules/object-property-newline.md @@ -1,6 +1,5 @@ --- title: object-property-newline -layout: doc rule_type: layout related_rules: - brace-style diff --git a/eslint/docs/src/rules/object-shorthand.md b/eslint/docs/src/rules/object-shorthand.md index 0865126..d694b57 100644 --- a/eslint/docs/src/rules/object-shorthand.md +++ b/eslint/docs/src/rules/object-shorthand.md @@ -1,6 +1,5 @@ --- title: object-shorthand -layout: doc rule_type: suggestion related_rules: - no-useless-rename diff --git a/eslint/docs/src/rules/one-var-declaration-per-line.md b/eslint/docs/src/rules/one-var-declaration-per-line.md index 46ee9ab..e9b159f 100644 --- a/eslint/docs/src/rules/one-var-declaration-per-line.md +++ b/eslint/docs/src/rules/one-var-declaration-per-line.md @@ -1,6 +1,5 @@ --- title: one-var-declaration-per-line -layout: doc rule_type: suggestion related_rules: - one-var diff --git a/eslint/docs/src/rules/one-var.md b/eslint/docs/src/rules/one-var.md index 8d95633..d9a50ed 100644 --- a/eslint/docs/src/rules/one-var.md +++ b/eslint/docs/src/rules/one-var.md @@ -1,6 +1,5 @@ --- title: one-var -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/operator-assignment.md b/eslint/docs/src/rules/operator-assignment.md index c071b19..2c77137 100644 --- a/eslint/docs/src/rules/operator-assignment.md +++ b/eslint/docs/src/rules/operator-assignment.md @@ -1,6 +1,5 @@ --- title: operator-assignment -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/operator-linebreak.md b/eslint/docs/src/rules/operator-linebreak.md index 3994cb4..f3cab6e 100644 --- a/eslint/docs/src/rules/operator-linebreak.md +++ b/eslint/docs/src/rules/operator-linebreak.md @@ -1,6 +1,5 @@ --- title: operator-linebreak -layout: doc rule_type: layout related_rules: - comma-style diff --git a/eslint/docs/src/rules/padded-blocks.md b/eslint/docs/src/rules/padded-blocks.md index faa5333..fd57f2a 100644 --- a/eslint/docs/src/rules/padded-blocks.md +++ b/eslint/docs/src/rules/padded-blocks.md @@ -1,6 +1,5 @@ --- title: padded-blocks -layout: doc rule_type: layout related_rules: - lines-between-class-members diff --git a/eslint/docs/src/rules/padding-line-between-statements.md b/eslint/docs/src/rules/padding-line-between-statements.md index a66fa89..1b16881 100644 --- a/eslint/docs/src/rules/padding-line-between-statements.md +++ b/eslint/docs/src/rules/padding-line-between-statements.md @@ -1,6 +1,5 @@ --- title: padding-line-between-statements -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/prefer-arrow-callback.md b/eslint/docs/src/rules/prefer-arrow-callback.md index 0534ccd..0d72493 100644 --- a/eslint/docs/src/rules/prefer-arrow-callback.md +++ b/eslint/docs/src/rules/prefer-arrow-callback.md @@ -1,6 +1,5 @@ --- title: prefer-arrow-callback -layout: doc rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions diff --git a/eslint/docs/src/rules/prefer-const.md b/eslint/docs/src/rules/prefer-const.md index 2892bea..19801eb 100644 --- a/eslint/docs/src/rules/prefer-const.md +++ b/eslint/docs/src/rules/prefer-const.md @@ -1,6 +1,5 @@ --- title: prefer-const -layout: doc rule_type: suggestion related_rules: - no-var @@ -106,7 +105,7 @@ for (const a of [1, 2, 3]) { // `end` is never reassigned, but we cannot separate the declarations without modifying the scope. for (let i = 0, end = 10; i < end; ++i) { - console.log(a); + console.log(i); } // `predicate` is only assigned once but cannot be separately declared as `const` diff --git a/eslint/docs/src/rules/prefer-destructuring.md b/eslint/docs/src/rules/prefer-destructuring.md index da9e358..c98abdc 100644 --- a/eslint/docs/src/rules/prefer-destructuring.md +++ b/eslint/docs/src/rules/prefer-destructuring.md @@ -1,6 +1,5 @@ --- title: prefer-destructuring -layout: doc rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment diff --git a/eslint/docs/src/rules/prefer-exponentiation-operator.md b/eslint/docs/src/rules/prefer-exponentiation-operator.md index d29d467..4544010 100644 --- a/eslint/docs/src/rules/prefer-exponentiation-operator.md +++ b/eslint/docs/src/rules/prefer-exponentiation-operator.md @@ -1,6 +1,5 @@ --- title: prefer-exponentiation-operator -layout: doc rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation diff --git a/eslint/docs/src/rules/prefer-named-capture-group.md b/eslint/docs/src/rules/prefer-named-capture-group.md index 459e063..4f60319 100644 --- a/eslint/docs/src/rules/prefer-named-capture-group.md +++ b/eslint/docs/src/rules/prefer-named-capture-group.md @@ -1,6 +1,5 @@ --- title: prefer-named-capture-group -layout: doc rule_type: suggestion related_rules: - no-invalid-regexp diff --git a/eslint/docs/src/rules/prefer-numeric-literals.md b/eslint/docs/src/rules/prefer-numeric-literals.md index 33109f5..a90fd7d 100644 --- a/eslint/docs/src/rules/prefer-numeric-literals.md +++ b/eslint/docs/src/rules/prefer-numeric-literals.md @@ -1,6 +1,5 @@ --- title: prefer-numeric-literals -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/prefer-object-has-own.md b/eslint/docs/src/rules/prefer-object-has-own.md index 504847f..911702f 100644 --- a/eslint/docs/src/rules/prefer-object-has-own.md +++ b/eslint/docs/src/rules/prefer-object-has-own.md @@ -1,6 +1,5 @@ --- title: prefer-object-has-own -layout: doc rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn diff --git a/eslint/docs/src/rules/prefer-object-spread.md b/eslint/docs/src/rules/prefer-object-spread.md index 7b447e8..b192735 100644 --- a/eslint/docs/src/rules/prefer-object-spread.md +++ b/eslint/docs/src/rules/prefer-object-spread.md @@ -1,6 +1,5 @@ --- title: prefer-object-spread -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/prefer-promise-reject-errors.md b/eslint/docs/src/rules/prefer-promise-reject-errors.md index 90164bd..c4874c2 100644 --- a/eslint/docs/src/rules/prefer-promise-reject-errors.md +++ b/eslint/docs/src/rules/prefer-promise-reject-errors.md @@ -1,6 +1,5 @@ --- title: prefer-promise-reject-errors -layout: doc rule_type: suggestion related_rules: - no-throw-literal diff --git a/eslint/docs/src/rules/prefer-reflect.md b/eslint/docs/src/rules/prefer-reflect.md index ea2ee7d..e2a632e 100644 --- a/eslint/docs/src/rules/prefer-reflect.md +++ b/eslint/docs/src/rules/prefer-reflect.md @@ -1,6 +1,5 @@ --- title: prefer-reflect -layout: doc rule_type: suggestion related_rules: - no-useless-call @@ -11,6 +10,8 @@ related_rules: This rule was **deprecated** in ESLint v3.9.0 and will not be replaced. The original intent of this rule now seems misguided as we have come to understand that `Reflect` methods are not actually intended to replace the `Object` counterparts the rule suggests, but rather exist as low-level primitives to be used with proxies in order to replicate the default behavior of various previously existing functionality. +**Please note**: This rule contains an incorrect behavior - it will suggest you to use `Reflect.getOwnPropertyNames` rather than `Object.getOwnPropertyNames`, but the former one doesn't exist in the [specification](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflection). We suggest using the `exceptions` option with `"getOwnPropertyNames"` to avoid this false suggestion. + The ES6 Reflect API comes with a handful of methods which somewhat deprecate methods on old constructors: * [`Reflect.apply`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflect.apply) effectively deprecates [`Function.prototype.apply`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-function.prototype.apply) and [`Function.prototype.call`](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-function.prototype.call) @@ -320,47 +321,6 @@ Reflect.isExtensible({}) ::: -### Reflect.getOwnPropertyNames - -Deprecates `Object.getOwnPropertyNames()` - -Examples of **incorrect** code for this rule when used without exceptions: - -::: incorrect - -```js -/*eslint prefer-reflect: "error"*/ - -Object.getOwnPropertyNames({}) -``` - -::: - -Examples of **correct** code for this rule when used without exceptions: - -::: correct - -```js -/*eslint prefer-reflect: "error"*/ - -Reflect.getOwnPropertyNames({}) -``` - -::: - -Examples of **correct** code for this rule with the `{ "exceptions": ["getOwnPropertyNames"] }` option: - -::: correct - -```js -/*eslint prefer-reflect: ["error", { "exceptions": ["getOwnPropertyNames"] }]*/ - -Object.getOwnPropertyNames({}) -Reflect.getOwnPropertyNames({}) -``` - -::: - ### Reflect.preventExtensions Deprecates `Object.preventExtensions()` diff --git a/eslint/docs/src/rules/prefer-regex-literals.md b/eslint/docs/src/rules/prefer-regex-literals.md index ed477c5..2159c97 100644 --- a/eslint/docs/src/rules/prefer-regex-literals.md +++ b/eslint/docs/src/rules/prefer-regex-literals.md @@ -1,6 +1,5 @@ --- title: prefer-regex-literals -layout: doc rule_type: suggestion further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions diff --git a/eslint/docs/src/rules/prefer-rest-params.md b/eslint/docs/src/rules/prefer-rest-params.md index 2c2f05c..cf23426 100644 --- a/eslint/docs/src/rules/prefer-rest-params.md +++ b/eslint/docs/src/rules/prefer-rest-params.md @@ -1,6 +1,5 @@ --- title: prefer-rest-params -layout: doc rule_type: suggestion related_rules: - prefer-spread diff --git a/eslint/docs/src/rules/prefer-spread.md b/eslint/docs/src/rules/prefer-spread.md index 6d6bc2b..4b696b5 100644 --- a/eslint/docs/src/rules/prefer-spread.md +++ b/eslint/docs/src/rules/prefer-spread.md @@ -1,6 +1,5 @@ --- title: prefer-spread -layout: doc rule_type: suggestion related_rules: - no-useless-call diff --git a/eslint/docs/src/rules/prefer-template.md b/eslint/docs/src/rules/prefer-template.md index 15a4c98..c2397b5 100644 --- a/eslint/docs/src/rules/prefer-template.md +++ b/eslint/docs/src/rules/prefer-template.md @@ -1,6 +1,5 @@ --- title: prefer-template -layout: doc rule_type: suggestion related_rules: - no-useless-concat diff --git a/eslint/docs/src/rules/quote-props.md b/eslint/docs/src/rules/quote-props.md index f6a17e7..b994b82 100644 --- a/eslint/docs/src/rules/quote-props.md +++ b/eslint/docs/src/rules/quote-props.md @@ -1,6 +1,5 @@ --- title: quote-props -layout: doc rule_type: suggestion further_reading: - https://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names diff --git a/eslint/docs/src/rules/quotes.md b/eslint/docs/src/rules/quotes.md index afa65a1..38d21b4 100644 --- a/eslint/docs/src/rules/quotes.md +++ b/eslint/docs/src/rules/quotes.md @@ -1,6 +1,5 @@ --- title: quotes -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/radix.md b/eslint/docs/src/rules/radix.md index de8b51a..8c11f87 100644 --- a/eslint/docs/src/rules/radix.md +++ b/eslint/docs/src/rules/radix.md @@ -1,6 +1,5 @@ --- title: radix -layout: doc rule_type: suggestion further_reading: - https://davidwalsh.name/parseint-radix diff --git a/eslint/docs/src/rules/require-atomic-updates.md b/eslint/docs/src/rules/require-atomic-updates.md index fe0254d..fac686b 100644 --- a/eslint/docs/src/rules/require-atomic-updates.md +++ b/eslint/docs/src/rules/require-atomic-updates.md @@ -1,6 +1,5 @@ --- title: require-atomic-updates -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/require-await.md b/eslint/docs/src/rules/require-await.md index d311518..a17eb97 100644 --- a/eslint/docs/src/rules/require-await.md +++ b/eslint/docs/src/rules/require-await.md @@ -1,6 +1,5 @@ --- title: require-await -layout: doc rule_type: suggestion related_rules: - require-yield diff --git a/eslint/docs/src/rules/require-jsdoc.md b/eslint/docs/src/rules/require-jsdoc.md index 4ff17df..20516fb 100644 --- a/eslint/docs/src/rules/require-jsdoc.md +++ b/eslint/docs/src/rules/require-jsdoc.md @@ -1,6 +1,5 @@ --- title: require-jsdoc -layout: doc rule_type: suggestion related_rules: - valid-jsdoc diff --git a/eslint/docs/src/rules/require-unicode-regexp.md b/eslint/docs/src/rules/require-unicode-regexp.md index 0efe1a8..f327706 100644 --- a/eslint/docs/src/rules/require-unicode-regexp.md +++ b/eslint/docs/src/rules/require-unicode-regexp.md @@ -1,6 +1,5 @@ --- title: require-unicode-regexp -layout: doc rule_type: suggestion --- diff --git a/eslint/docs/src/rules/require-yield.md b/eslint/docs/src/rules/require-yield.md index ce3cbe8..e4f975f 100644 --- a/eslint/docs/src/rules/require-yield.md +++ b/eslint/docs/src/rules/require-yield.md @@ -1,6 +1,5 @@ --- title: require-yield -layout: doc rule_type: suggestion related_rules: - require-await diff --git a/eslint/docs/src/rules/rest-spread-spacing.md b/eslint/docs/src/rules/rest-spread-spacing.md index 8850bea..b27b537 100644 --- a/eslint/docs/src/rules/rest-spread-spacing.md +++ b/eslint/docs/src/rules/rest-spread-spacing.md @@ -1,6 +1,5 @@ --- title: rest-spread-spacing -layout: doc rule_type: layout further_reading: - https://github.com/tc39/proposal-object-rest-spread @@ -59,7 +58,7 @@ This rule aims to enforce consistent spacing between rest and spread operators a } ``` -Please read the user guide's section on [configuring parser options](/docs/user-guide/configuring#specifying-parser-options) to learn more. +Please read the user guide's section on [configuring parser options](../use/configure#specifying-parser-options) to learn more. ## Options diff --git a/eslint/docs/src/rules/semi-spacing.md b/eslint/docs/src/rules/semi-spacing.md index 01b3c3f..4ed43ac 100644 --- a/eslint/docs/src/rules/semi-spacing.md +++ b/eslint/docs/src/rules/semi-spacing.md @@ -1,6 +1,5 @@ --- title: semi-spacing -layout: doc rule_type: layout related_rules: - semi diff --git a/eslint/docs/src/rules/semi-style.md b/eslint/docs/src/rules/semi-style.md index 0d82f30..66db3af 100644 --- a/eslint/docs/src/rules/semi-style.md +++ b/eslint/docs/src/rules/semi-style.md @@ -1,6 +1,5 @@ --- title: semi-style -layout: doc rule_type: layout related_rules: - no-extra-semi diff --git a/eslint/docs/src/rules/semi.md b/eslint/docs/src/rules/semi.md index be01835..6fd0550 100644 --- a/eslint/docs/src/rules/semi.md +++ b/eslint/docs/src/rules/semi.md @@ -1,6 +1,5 @@ --- title: semi -layout: doc rule_type: layout related_rules: - no-extra-semi @@ -77,17 +76,18 @@ This rule has two options, a string option and an object option. String option: * `"always"` (default) requires semicolons at the end of statements -* `"never"` disallows semicolons as the end of statements (except to disambiguate statements beginning with `[`, `(`, `/`, `+`, or `-`) +* `"never"` disallows semicolons at the end of statements (except to disambiguate statements beginning with `[`, `(`, `/`, `+`, or `-`) Object option (when `"always"`): -* `"omitLastInOneLineBlock": true` ignores the last semicolon in a block in which its braces (and therefore the content of the block) are in the same line +* `"omitLastInOneLineBlock": true` disallows the last semicolon in a block in which its braces (and therefore the content of the block) are in the same line +* `"omitLastInOneLineClassBody": true` disallows the last semicolon in a class body in which its braces (and therefore the content of the class body) are in the same line Object option (when `"never"`): * `"beforeStatementContinuationChars": "any"` (default) ignores semicolons (or lacking semicolon) at the end of statements if the next line starts with `[`, `(`, `/`, `+`, or `-`. * `"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 `-`. +* `"beforeStatementContinuationChars": "never"` disallows semicolons at 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. @@ -133,6 +133,52 @@ class Foo { ::: +#### omitLastInOneLineBlock + +Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineBlock": true }` options: + +::: correct + +```js +/*eslint semi: ["error", "always", { "omitLastInOneLineBlock": true}] */ + +if (foo) { bar() } + +if (foo) { bar(); baz() } + +function f() { bar(); baz() } + +class C { + foo() { bar(); baz() } + + static { bar(); baz() } +} +``` + +::: + +#### omitLastInOneLineClassBody + +Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineClassBody": true }` options: + +::: correct + +```js +/*eslint semi: ["error", "always", { "omitLastInOneLineClassBody": true}] */ + +export class SomeClass{ + logType(){ + console.log(this.type); + console.log(this.anotherType); + } +} + +export class Variant1 extends SomeClass{type=1} +export class Variant2 extends SomeClass{type=2; anotherType=3} +``` + +::: + ### never Examples of **incorrect** code for this rule with the `"never"` option: @@ -191,30 +237,6 @@ class Foo { ::: -#### omitLastInOneLineBlock - -Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineBlock": true }` options: - -::: correct - -```js -/*eslint semi: ["error", "always", { "omitLastInOneLineBlock": true}] */ - -if (foo) { bar() } - -if (foo) { bar(); baz() } - -function f() { bar(); baz() } - -class C { - foo() { bar(); baz() } - - static { bar(); baz() } -} -``` - -::: - #### beforeStatementContinuationChars Examples of additional **incorrect** code for this rule with the `"never", { "beforeStatementContinuationChars": "always" }` options: diff --git a/eslint/docs/src/rules/sort-imports.md b/eslint/docs/src/rules/sort-imports.md index d42bd97..d48ead6 100644 --- a/eslint/docs/src/rules/sort-imports.md +++ b/eslint/docs/src/rules/sort-imports.md @@ -1,6 +1,5 @@ --- title: sort-imports -layout: doc rule_type: suggestion related_rules: - sort-keys diff --git a/eslint/docs/src/rules/sort-keys.md b/eslint/docs/src/rules/sort-keys.md index 4c90e6c..0fd00ae 100644 --- a/eslint/docs/src/rules/sort-keys.md +++ b/eslint/docs/src/rules/sort-keys.md @@ -1,6 +1,5 @@ --- title: sort-keys -layout: doc rule_type: suggestion related_rules: - sort-imports diff --git a/eslint/docs/src/rules/sort-vars.md b/eslint/docs/src/rules/sort-vars.md index cc85f10..e952704 100644 --- a/eslint/docs/src/rules/sort-vars.md +++ b/eslint/docs/src/rules/sort-vars.md @@ -1,6 +1,5 @@ --- title: sort-vars -layout: doc rule_type: suggestion related_rules: - sort-keys diff --git a/eslint/docs/src/rules/space-after-function-name.md b/eslint/docs/src/rules/space-after-function-name.md index b90f316..65809c2 100644 --- a/eslint/docs/src/rules/space-after-function-name.md +++ b/eslint/docs/src/rules/space-after-function-name.md @@ -1,6 +1,5 @@ --- title: space-after-function-name -layout: doc --- diff --git a/eslint/docs/src/rules/space-after-keywords.md b/eslint/docs/src/rules/space-after-keywords.md index ac619ed..7f5d63d 100644 --- a/eslint/docs/src/rules/space-after-keywords.md +++ b/eslint/docs/src/rules/space-after-keywords.md @@ -1,6 +1,5 @@ --- title: space-after-keywords -layout: doc --- @@ -8,7 +7,7 @@ Enforces consistent spacing after keywords. (removed) This rule was **removed** in ESLint v2.0 and replaced by the [keyword-spacing](keyword-spacing) 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](../use/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/src/rules/space-before-blocks.md b/eslint/docs/src/rules/space-before-blocks.md index de90dc0..a7bcad2 100644 --- a/eslint/docs/src/rules/space-before-blocks.md +++ b/eslint/docs/src/rules/space-before-blocks.md @@ -1,6 +1,5 @@ --- title: space-before-blocks -layout: doc rule_type: layout related_rules: - keyword-spacing diff --git a/eslint/docs/src/rules/space-before-function-paren.md b/eslint/docs/src/rules/space-before-function-paren.md index e141005..1948d90 100644 --- a/eslint/docs/src/rules/space-before-function-paren.md +++ b/eslint/docs/src/rules/space-before-function-paren.md @@ -1,10 +1,8 @@ --- title: space-before-function-paren -layout: doc rule_type: layout related_rules: -- space-after-keywords -- space-return-throw-case +- keyword-spacing --- diff --git a/eslint/docs/src/rules/space-before-function-parentheses.md b/eslint/docs/src/rules/space-before-function-parentheses.md index f60bd44..a5c0ece 100644 --- a/eslint/docs/src/rules/space-before-function-parentheses.md +++ b/eslint/docs/src/rules/space-before-function-parentheses.md @@ -1,6 +1,5 @@ --- title: space-before-function-parentheses -layout: doc related_rules: - space-after-keywords diff --git a/eslint/docs/src/rules/space-before-keywords.md b/eslint/docs/src/rules/space-before-keywords.md index efd2b35..9c2f735 100644 --- a/eslint/docs/src/rules/space-before-keywords.md +++ b/eslint/docs/src/rules/space-before-keywords.md @@ -1,6 +1,5 @@ --- title: space-before-keywords -layout: doc related_rules: - space-after-keywords @@ -14,7 +13,7 @@ Enforces consistent spacing before keywords. (removed) This rule was **removed** in ESLint v2.0 and **replaced** by the [keyword-spacing](keyword-spacing) 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](../use/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/src/rules/space-in-brackets.md b/eslint/docs/src/rules/space-in-brackets.md index bbd2216..d07023d 100644 --- a/eslint/docs/src/rules/space-in-brackets.md +++ b/eslint/docs/src/rules/space-in-brackets.md @@ -1,6 +1,5 @@ --- title: space-in-brackets -layout: doc related_rules: - array-bracket-spacing diff --git a/eslint/docs/src/rules/space-in-parens.md b/eslint/docs/src/rules/space-in-parens.md index 5389637..1d6ca52 100644 --- a/eslint/docs/src/rules/space-in-parens.md +++ b/eslint/docs/src/rules/space-in-parens.md @@ -1,6 +1,5 @@ --- title: space-in-parens -layout: doc rule_type: layout related_rules: - array-bracket-spacing diff --git a/eslint/docs/src/rules/space-infix-ops.md b/eslint/docs/src/rules/space-infix-ops.md index 856a87e..984e3b3 100644 --- a/eslint/docs/src/rules/space-infix-ops.md +++ b/eslint/docs/src/rules/space-infix-ops.md @@ -1,6 +1,5 @@ --- title: space-infix-ops -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/space-return-throw-case.md b/eslint/docs/src/rules/space-return-throw-case.md index 898a4d6..8a92422 100644 --- a/eslint/docs/src/rules/space-return-throw-case.md +++ b/eslint/docs/src/rules/space-return-throw-case.md @@ -1,6 +1,5 @@ --- title: space-return-throw-case -layout: doc --- @@ -8,7 +7,7 @@ Requires spaces after `return`, `throw`, and `case` keywords. (removed) This rule was **removed** in ESLint v2.0 and **replaced** by the [keyword-spacing](keyword-spacing) 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](../use/command-line-interface#--fix) automatically fixed problems reported by this rule. Require spaces following `return`, `throw`, and `case`. diff --git a/eslint/docs/src/rules/space-unary-ops.md b/eslint/docs/src/rules/space-unary-ops.md index e775e9f..a33735b 100644 --- a/eslint/docs/src/rules/space-unary-ops.md +++ b/eslint/docs/src/rules/space-unary-ops.md @@ -1,6 +1,5 @@ --- title: space-unary-ops -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/space-unary-word-ops.md b/eslint/docs/src/rules/space-unary-word-ops.md index 145141c..4bb1262 100644 --- a/eslint/docs/src/rules/space-unary-word-ops.md +++ b/eslint/docs/src/rules/space-unary-word-ops.md @@ -1,6 +1,5 @@ --- title: space-unary-word-ops -layout: doc --- diff --git a/eslint/docs/src/rules/spaced-comment.md b/eslint/docs/src/rules/spaced-comment.md index f9d6381..f15e16c 100644 --- a/eslint/docs/src/rules/spaced-comment.md +++ b/eslint/docs/src/rules/spaced-comment.md @@ -1,6 +1,5 @@ --- title: spaced-comment -layout: doc rule_type: suggestion related_rules: - spaced-line-comment diff --git a/eslint/docs/src/rules/spaced-line-comment.md b/eslint/docs/src/rules/spaced-line-comment.md index ca5a99f..9ddc648 100644 --- a/eslint/docs/src/rules/spaced-line-comment.md +++ b/eslint/docs/src/rules/spaced-line-comment.md @@ -1,6 +1,5 @@ --- title: spaced-line-comment -layout: doc related_rules: - spaced-comment diff --git a/eslint/docs/src/rules/strict.md b/eslint/docs/src/rules/strict.md index f811a60..c559b00 100644 --- a/eslint/docs/src/rules/strict.md +++ b/eslint/docs/src/rules/strict.md @@ -1,6 +1,5 @@ --- title: strict -layout: doc rule_type: suggestion --- @@ -48,7 +47,7 @@ In **ECMAScript** modules, which always have strict mode semantics, the directiv This rule requires or disallows strict mode directives. -This rule disallows strict mode directives, no matter which option is specified, if ESLint configuration specifies either of the following as [parser options](/docs/user-guide/configuring/language-options#specifying-parser-options): +This rule disallows strict mode directives, no matter which option is specified, if ESLint configuration specifies either of the following as [parser options](../use/configure/language-options#specifying-parser-options): * `"sourceType": "module"` that is, files are **ECMAScript** modules * `"impliedStrict": true` property in the `ecmaFeatures` object @@ -74,8 +73,8 @@ This rule has a string option: The `"safe"` option corresponds to the `"global"` option if ESLint considers a file to be a **Node.js** or **CommonJS** module because the configuration specifies either of the following: -* `node` or `commonjs` [environments](/docs/user-guide/configuring/language-options#specifying-environments) -* `"globalReturn": true` property in the `ecmaFeatures` object of [parser options](/docs/user-guide/configuring/language-options#specifying-parser-options) +* `node` or `commonjs` [environments](../use/configure/language-options#specifying-environments) +* `"globalReturn": true` property in the `ecmaFeatures` object of [parser options](../use/configure/language-options#specifying-parser-options) Otherwise the `"safe"` option corresponds to the `"function"` option. Note that if `"globalReturn": false` is explicitly specified in the configuration, the `"safe"` option will correspond to the `"function"` option regardless of the specified environment. @@ -341,4 +340,4 @@ function foo() { ## When Not To Use It -In a codebase that has both strict and non-strict code, either turn this rule off, or [selectively disable it](/docs/user-guide/configuring/rules#disabling-rules) where necessary. For example, functions referencing `arguments.callee` are invalid in strict mode. A [full list of strict mode differences](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode/Transitioning_to_strict_mode#Differences_from_non-strict_to_strict) is available on MDN. +In a codebase that has both strict and non-strict code, either turn this rule off, or [selectively disable it](../use/configure/rules#disabling-rules) where necessary. For example, functions referencing `arguments.callee` are invalid in strict mode. A [full list of strict mode differences](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode/Transitioning_to_strict_mode#Differences_from_non-strict_to_strict) is available on MDN. diff --git a/eslint/docs/src/rules/switch-colon-spacing.md b/eslint/docs/src/rules/switch-colon-spacing.md index 6942aad..63df48f 100644 --- a/eslint/docs/src/rules/switch-colon-spacing.md +++ b/eslint/docs/src/rules/switch-colon-spacing.md @@ -1,6 +1,5 @@ --- title: switch-colon-spacing -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/symbol-description.md b/eslint/docs/src/rules/symbol-description.md index 78e4e66..438cb01 100644 --- a/eslint/docs/src/rules/symbol-description.md +++ b/eslint/docs/src/rules/symbol-description.md @@ -1,6 +1,5 @@ --- title: symbol-description -layout: doc rule_type: suggestion further_reading: - https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-description diff --git a/eslint/docs/src/rules/template-curly-spacing.md b/eslint/docs/src/rules/template-curly-spacing.md index f0079ee..b96a30c 100644 --- a/eslint/docs/src/rules/template-curly-spacing.md +++ b/eslint/docs/src/rules/template-curly-spacing.md @@ -1,6 +1,5 @@ --- title: template-curly-spacing -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/template-tag-spacing.md b/eslint/docs/src/rules/template-tag-spacing.md index 309474f..da0cfa8 100644 --- a/eslint/docs/src/rules/template-tag-spacing.md +++ b/eslint/docs/src/rules/template-tag-spacing.md @@ -1,6 +1,5 @@ --- title: template-tag-spacing -layout: doc rule_type: layout further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals diff --git a/eslint/docs/src/rules/unicode-bom.md b/eslint/docs/src/rules/unicode-bom.md index 1b8683c..1bae51c 100644 --- a/eslint/docs/src/rules/unicode-bom.md +++ b/eslint/docs/src/rules/unicode-bom.md @@ -1,6 +1,5 @@ --- title: unicode-bom -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/use-isnan.md b/eslint/docs/src/rules/use-isnan.md index e76dcb4..d6eda9d 100644 --- a/eslint/docs/src/rules/use-isnan.md +++ b/eslint/docs/src/rules/use-isnan.md @@ -1,6 +1,5 @@ --- title: use-isnan -layout: doc rule_type: problem --- diff --git a/eslint/docs/src/rules/valid-jsdoc.md b/eslint/docs/src/rules/valid-jsdoc.md index 1eca97e..33b48dc 100644 --- a/eslint/docs/src/rules/valid-jsdoc.md +++ b/eslint/docs/src/rules/valid-jsdoc.md @@ -1,6 +1,5 @@ --- title: valid-jsdoc -layout: doc rule_type: suggestion related_rules: - require-jsdoc diff --git a/eslint/docs/src/rules/valid-typeof.md b/eslint/docs/src/rules/valid-typeof.md index 66f9724..5269cfd 100644 --- a/eslint/docs/src/rules/valid-typeof.md +++ b/eslint/docs/src/rules/valid-typeof.md @@ -1,6 +1,5 @@ --- title: valid-typeof -layout: doc rule_type: problem further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof diff --git a/eslint/docs/src/rules/vars-on-top.md b/eslint/docs/src/rules/vars-on-top.md index 9700600..20d1440 100644 --- a/eslint/docs/src/rules/vars-on-top.md +++ b/eslint/docs/src/rules/vars-on-top.md @@ -1,6 +1,5 @@ --- title: vars-on-top -layout: doc rule_type: suggestion further_reading: - https://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html diff --git a/eslint/docs/src/rules/wrap-iife.md b/eslint/docs/src/rules/wrap-iife.md index 22b3b9e..af8ec85 100644 --- a/eslint/docs/src/rules/wrap-iife.md +++ b/eslint/docs/src/rules/wrap-iife.md @@ -1,6 +1,5 @@ --- title: wrap-iife -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/wrap-regex.md b/eslint/docs/src/rules/wrap-regex.md index 450a7d6..d4cff7d 100644 --- a/eslint/docs/src/rules/wrap-regex.md +++ b/eslint/docs/src/rules/wrap-regex.md @@ -1,6 +1,5 @@ --- title: wrap-regex -layout: doc rule_type: layout --- diff --git a/eslint/docs/src/rules/yield-star-spacing.md b/eslint/docs/src/rules/yield-star-spacing.md index 3c4c43e..c5e4738 100644 --- a/eslint/docs/src/rules/yield-star-spacing.md +++ b/eslint/docs/src/rules/yield-star-spacing.md @@ -1,6 +1,5 @@ --- title: yield-star-spacing -layout: doc rule_type: layout further_reading: - https://leanpub.com/understandinges6/read/#leanpub-auto-generators diff --git a/eslint/docs/src/rules/yoda.md b/eslint/docs/src/rules/yoda.md index 60cf3e4..28b86dd 100644 --- a/eslint/docs/src/rules/yoda.md +++ b/eslint/docs/src/rules/yoda.md @@ -1,6 +1,5 @@ --- title: yoda -layout: doc rule_type: suggestion further_reading: - https://en.wikipedia.org/wiki/Yoda_conditions diff --git a/eslint/docs/src/static/feed.njk b/eslint/docs/src/static/feed.njk index da59847..d32fb47 100644 --- a/eslint/docs/src/static/feed.njk +++ b/eslint/docs/src/static/feed.njk @@ -1,6 +1,7 @@ ---json { "permalink": "feed.xml", + "layout": false, "eleventyExcludeFromCollections": true, "metadata": { "title": "ESLint Blog", diff --git a/eslint/docs/src/static/sitemap.njk b/eslint/docs/src/static/sitemap.njk index e92a4e5..47ba266 100644 --- a/eslint/docs/src/static/sitemap.njk +++ b/eslint/docs/src/static/sitemap.njk @@ -1,5 +1,6 @@ --- permalink: /sitemap.xml +layout: false eleventyExcludeFromCollections: true --- diff --git a/eslint/docs/src/use/command-line-interface.md b/eslint/docs/src/use/command-line-interface.md new file mode 100644 index 0000000..d88e35c --- /dev/null +++ b/eslint/docs/src/use/command-line-interface.md @@ -0,0 +1,763 @@ +--- +title: Command Line Interface Reference +eleventyNavigation: + key: command line interface + parent: use eslint + title: Command Line Interface Reference + order: 4 + +--- + +The ESLint Command Line Interface (CLI) lets you execute linting from the terminal. The CLI has a variety of options that you can pass to configure ESLint. + +## Run the CLI + +ESLint requires Node.js for installation. Follow the instructions in the [Getting Started Guide](getting-started) to install ESLint. + +Most users use [`npx`](https://docs.npmjs.com/cli/v8/commands/npx) to run ESLint on the command line like this: + +```shell +npx eslint [options] [file|dir|glob]* +``` + +Such as: + +```shell +# Run on two files +npx eslint file1.js file2.js + +# Run on multiple files +npx eslint lib/** +``` + +Please note that when passing a glob as a parameter, it is expanded by your shell. The results of the expansion can vary depending on your shell, and its configuration. If you want to use node `glob` syntax, you have to quote your parameter (using double quotes if you need it to run in Windows), as follows: + +```shell +npx eslint "lib/**" +``` + +**Note:** You can also use alternative package managers such as [Yarn](https://yarnpkg.com/) or [pnpm](https://pnpm.io/) to run ESLint. Please refer to your package manager's documentation for the correct syntax. + +## Pass Multiple Values to an Option + +Options that accept multiple values can be specified by repeating the option or with a comma-delimited list (other than [`--ignore-pattern`](#--ignore-pattern), which does not allow the second style). + +Examples of options that accept multiple values: + +```shell +npx eslint --ext .jsx --ext .js lib/ +# OR +npx eslint --ext .jsx,.js lib/ +``` + +## Options + +You can view all the CLI options by running `npx eslint -h`. + +```txt +eslint [options] file.js [file.js] [dir] + +Basic configuration: + --no-eslintrc Disable use of configuration from .eslintrc.* + -c, --config path::String Use this configuration, overriding .eslintrc.* config options if present + --env [String] Specify environments + --ext [String] Specify JavaScript file extensions + --global [String] Define global variables + --parser String Specify the parser to be used + --parser-options Object Specify parser options + --resolve-plugins-relative-to path::String A folder where plugins should be resolved from, CWD by default + +Specify rules and plugins: + --plugin [String] Specify plugins + --rule Object Specify rules + --rulesdir [path::String] Load additional rules from this directory. Deprecated: Use rules from plugins + +Fix 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 (directive, problem, suggestion, layout) + +Ignore files: + --ignore-path path::String Specify path of ignore file + --no-ignore Disable use of ignore files and patterns + --ignore-pattern [String] Pattern of files to ignore (in addition to those in .eslintignore) + +Use stdin: + --stdin Lint code provided on - default: false + --stdin-filename String Specify filename to process STDIN as + +Handle warnings: + --quiet Report errors only - default: false + --max-warnings Int Number of warnings to trigger nonzero exit code - default: -1 + +Output: + -o, --output-file path::String Specify file to write report to + -f, --format String Use a specific output format - default: stylish + --color, --no-color Force enabling/disabling of color + +Inline configuration comments: + --no-inline-config Prevent comments from changing config or rules + --report-unused-disable-directives Adds reported errors for unused eslint-disable directives + +Caching: + --cache Only check changed files - default: false + --cache-file path::String Path to the cache file. Deprecated: use --cache-location - default: .eslintcache + --cache-location path::String Path to the cache file or directory + --cache-strategy String Strategy to use for detecting changed files in the cache - either: metadata or content - default: metadata + +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 + --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 + --print-config path::String Print the configuration for the given file +``` + +### Basic Configuration + +#### `--no-eslintrc` + +Disables use of configuration from `.eslintrc.*` and `package.json` files. + +* **Argument Type**: No argument. + +##### `--no-eslintrc` example + +```shell +npx eslint --no-eslintrc file.js +``` + +#### `-c`, `--config` + +This option allows you to specify an additional configuration file for ESLint (see [Configure ESLint](configure/) for more). + +* **Argument Type**: String. Path to file. +* **Multiple Arguments**: No + +##### `-c`, `--config` example + +```shell +npx eslint -c ~/my-eslint.json file.js +``` + +This example uses the configuration file at `~/my-eslint.json`. + +If `.eslintrc.*` and/or `package.json` files are also used for configuration (i.e., `--no-eslintrc` was not specified), the configurations are merged. Options from this configuration file have precedence over the options from `.eslintrc.*` and `package.json` files. + +#### `--env` + +This option enables specific environments. + +* **Argument Type**: String. One of the available environments. +* **Multiple Arguments**: Yes + +Details about the global variables defined by each environment are available in the [Specifying Environments](configure/language-options#specifying-environments) documentation. This option only enables environments. It does not disable environments set in other configuration files. To specify multiple environments, separate them using commas, or use the option multiple times. + +##### `--env` example + +```shell +npx eslint --env browser,node file.js +npx eslint --env browser --env node file.js +``` + +#### `--ext` + +This option allows you to specify which file extensions ESLint uses when searching for target files in the directories you specify. + +* **Argument Type**: String. File extension. +* **Multiple Arguments**: Yes +* **Default Value**: `.js` and the files that match the `overrides` entries of your configuration. + +`--ext` is only used when the patterns to lint are directories. If you use glob patterns or file names, then `--ext` is ignored. For example, `npx eslint "lib/*" --ext .js` matches all files within the `lib/` directory, regardless of extension. + +##### `--ext` example + +```shell +# Use only .ts extension +npx eslint . --ext .ts + +# Use both .js and .ts +npx eslint . --ext .js --ext .ts + +# Also use both .js and .ts +npx eslint . --ext .js,.ts +``` + +#### `--global` + +This option defines global variables so that they are not flagged as undefined by the [`no-undef`](../rules/no-undef) rule. + +* **Argument Type**: String. Name of the global variable. Any specified global variables are assumed to be read-only by default, but appending `:true` to a variable's name ensures that `no-undef` also allows writes. +* **Multiple Arguments**: Yes + +##### `--global` example + +```shell +npx eslint --global require,exports:true file.js +npx eslint --global require --global exports:true +``` + +#### `--parser` + +This option allows you to specify a parser to be used by ESLint. + +* **Argument Type**: String. Parser to be used by ESLint. +* **Multiple Arguments**: No +* **Default Value**: `espree` + +##### `--parser` example + +```shell +# Use TypeScript ESLint parser +npx eslint --parser @typescript-eslint/parser file.ts +``` + +#### `--parser-options` + +This option allows you to specify parser options to be used by ESLint. The available parser options are determined by the parser being used. + +* **Argument Type**: Key/value pair separated by colon (`:`). +* **Multiple Arguments**: Yes + +##### `--parser-options` example + +```shell +echo '3 ** 4' | npx eslint --stdin --parser-options ecmaVersion:6 # fails with a parsing error +echo '3 ** 4' | npx eslint --stdin --parser-options ecmaVersion:7 # succeeds, yay! +``` + +#### `--resolve-plugins-relative-to` + +Changes the directory where plugins are resolved from. + +* **Argument Type**: String. Path to directory. +* **Multiple Arguments**: No +* **Default Value**: By default, plugins are resolved from the directory in which your configuration file is found. + +This option should be used when plugins were installed by someone other than the end user. It should be set to the project directory of the project that has a dependency on the necessary plugins. + +For example: + +* When using a config file that is located outside of the current project (with the `--config` flag), if the config uses plugins which are installed locally to itself, `--resolve-plugins-relative-to` should be set to the directory containing the config file. +* If an integration has dependencies on ESLint and a set of plugins, and the tool invokes ESLint on behalf of the user with a preset configuration, the tool should set `--resolve-plugins-relative-to` to the top-level directory of the tool. + +##### `--resolve-plugins-relative-to` example + +```shell +npx eslint --config ~/personal-eslintrc.js \ +--resolve-plugins-relative-to /usr/local/lib/ +``` + +### Specify Rules and Plugins + +#### `--plugin` + +This option specifies a plugin to load. + +* **Argument Type**: String. Plugin name. You can optionally omit the prefix `eslint-plugin-` from the plugin name. +* **Multiple Arguments**: Yes + +Before using the plugin, you have to install it using npm. + +##### `--plugin` example + +```shell +npx eslint --plugin jquery file.js +npx eslint --plugin eslint-plugin-mocha file.js +``` + +#### `--rule` + +This option specifies the rules to be used. + +* **Argument Type**: Rules and their configuration specified with [levn](https://github.com/gkz/levn#levn--) format. +* **Multiple Arguments**: Yes + +These rules are merged with any rules specified with configuration files. If the rule is defined in a plugin, you have to prefix the rule ID with the plugin name and a `/`. + +To ignore rules in `.eslintrc` configuration files and only run rules specified in the command line, use the `--rules` flag in combination with the [`--no-eslintrc`](#--no-eslintrc) flag. + +##### `--rule` example + +```shell +# Apply single rule +npx eslint --rule 'quotes: [error, double]' +# Apply multiple rules +npx eslint --rule 'guard-for-in: error' --rule 'brace-style: [error, 1tbs]' +# Apply rule from jquery plugin +npx eslint --rule 'jquery/dollar-sign: error' +# Only apply rule from the command line +npx eslint --rule 'quotes: [error, double]' --no-eslintrc +``` + +#### `--rulesdir` + +**Deprecated**: Use rules from plugins instead. + +This option allows you to specify another directory from which to load rules files. This allows you to dynamically load new rules at run time. This is useful when you have custom rules that aren't suitable for being bundled with ESLint. + +* **Argument Type**: String. Path to directory. The rules in your custom rules directory must follow the same format as bundled rules to work properly. +* **Multiple Arguments**: Yes. + +Note that, as with core rules and plugin rules, you still need to enable the rules in configuration or via the `--rule` CLI option in order to actually run those rules during linting. Specifying a rules directory with `--rulesdir` does not automatically enable the rules within that directory. + +##### `--rulesdir` example + +```shell +npx eslint --rulesdir my-rules/ file.js +npx eslint --rulesdir my-rules/ --rulesdir my-other-rules/ file.js +``` + +### Fix Problems + +#### `--fix` + +This option instructs ESLint to try to fix as many issues as possible. The fixes are made to the actual files themselves and only the remaining unfixed issues are output. + +* **Argument Type**: No argument. + +Not all problems are fixable using this option, and the option does not work in these situations: + +1. This option throws an error when code is piped to ESLint. +1. This option has no effect on code that uses a processor, unless the processor opts into allowing autofixes. + +If you want to fix code from `stdin` or otherwise want to get the fixes without actually writing them to the file, use the [`--fix-dry-run`](#--fix-dry-run) option. + +##### `--fix` example + +```shell +npx eslint --fix file.js +``` + +#### `--fix-dry-run` + +This option has the same effect as `--fix` with the difference that the fixes are not saved to the file system. Because the default formatter does not output the fixed code, you'll have to use another formatter (e.g. `--format json`) to get the fixes. + +* **Argument Type**: No argument. + +This makes it possible to fix code from `stdin` when used with the `--stdin` flag. + +This flag can be useful for integrations (e.g. editor plugins) which need to autofix text from the command line without saving it to the filesystem. + +##### `--fix-dry-run` example + +```shell +getSomeText | npx eslint --stdin --fix-dry-run --format json +``` + +#### `--fix-type` + +This option allows you to specify the type of fixes to apply when using either `--fix` or `--fix-dry-run`. + +* **Argument Type**: String. One of the following fix types: + 1. `problem` - fix potential errors in the code + 1. `suggestion` - apply fixes to the code that improve it + 1. `layout` - apply fixes that do not change the program structure (AST) + 1. `directive` - apply fixes to inline directives such as `// eslint-disable` +* **Multiple Arguments**: Yes + +This option is helpful if you are using another program to format your code, but you would still like ESLint to apply other types of fixes. + +##### `--fix-type` example + +```shell +npx eslint --fix --fix-type suggestion . +npx eslint --fix --fix-type suggestion --fix-type problem . +npx eslint --fix --fix-type suggestion,layout . +``` + +### Ignore Files + +#### `--ignore-path` + +This option allows you to specify the file to use as your `.eslintignore`. + +* **Argument Type**: String. Path to file. +* **Multiple Arguments**: No +* **Default Value**: By default, ESLint looks for `.eslintignore` in the current working directory. + +**Note:** `--ignore-path` is not supported when using [flat configuration](./configure/configuration-files-new) (`eslint.config.js`). + +##### `--ignore-path` example + +```shell +npx eslint --ignore-path tmp/.eslintignore file.js +npx eslint --ignore-path .gitignore file.js +``` + +#### `--no-ignore` + +Disables excluding of files from `.eslintignore` files, `--ignore-path` flags, `--ignore-pattern` flags, and the `ignorePatterns` property in config files. + +* **Argument Type**: No argument. + +##### `--no-ignore` example + +```shell +npx eslint --no-ignore file.js +``` + +#### `--ignore-pattern` + +This option allows you to specify patterns of files to ignore (in addition to those in `.eslintignore`). + +* **Argument Type**: String. The supported syntax is the same as for [`.eslintignore` files](configure/ignore#the-eslintignore-file), which use the same patterns as the [`.gitignore` specification](https://git-scm.com/docs/gitignore). You should quote your patterns in order to avoid shell interpretation of glob patterns. +* **Multiple Arguments**: Yes + +##### `--ignore-pattern` example + +```shell +npx eslint --ignore-pattern "/lib/" --ignore-pattern "/src/vendor/*" . +``` + +### Use stdin + +#### `--stdin` + +This option tells ESLint to read and lint source code from STDIN instead of from files. You can use this to pipe code to ESLint. + +* **Argument Type**: No argument. + +##### `--stdin` example + +```shell +cat myfile.js | npx eslint --stdin +``` + +#### `--stdin-filename` + +This option allows you to specify a filename to process STDIN as. + +* **Argument Type**: String. Path to file. +* **Multiple Arguments**: No + +This is useful when processing files from STDIN and you have rules which depend on the filename. + +##### `--stdin-filename` example + +```shell +cat myfile.js | npx eslint --stdin --stdin-filename myfile.js +``` + +### Handle Warnings + +#### `--quiet` + +This option allows you to disable reporting on warnings. If you enable this option, only errors are reported by ESLint. + +* **Argument Type**: No argument. + +##### `--quiet` example + +```shell +npx eslint --quiet file.js +``` + +#### `--max-warnings` + +This option allows you to specify a warning threshold, which can be used to force ESLint to exit with an error status if there are too many warning-level rule violations in your project. + +* **Argument Type**: Integer. The maximum number of warnings to allow. To prevent this behavior, do not use this option or specify `-1` as the argument. +* **Multiple Arguments**: No + +Normally, if ESLint runs and finds no errors (only warnings), it exits with a success exit status. However, if `--max-warnings` is specified and the total warning count is greater than the specified threshold, ESLint exits with an error status. + +##### `--max-warnings` example + +```shell +npx eslint --max-warnings 10 file.js +``` + +### Output + +#### `-o`, `--output-file` + +Write the output of linting results to a specified file. + +* **Argument Type**: String. Path to file. +* **Multiple Arguments**: No + +##### `-o`, `--output-file` example + +```shell +npx eslint -o ./test/test.html +``` + +#### `-f`, `--format` + +This option specifies the output format for the console. + +* **Argument Type**: String. One of the [built-in formatters](formatters/) or a custom formatter. +* **Multiple Arguments**: No +* **Default Value**: [`stylish`](formatters/#stylish) + +If you are using a custom formatter defined in a local file, you can specify the path to the custom formatter file. + +An npm-installed formatter is resolved with or without `eslint-formatter-` prefix. + +When specified, the given format is output to the console. If you'd like to save that output into a file, you can do so on the command line like so: + +```shell +# Saves the output into the `results.txt` file. +npx eslint -f compact file.js > results.txt +``` + +##### `-f`, `--format` example + +Use the built-in `compact` formatter: + +```shell +npx eslint --format compact file.js +``` + +Use a local custom formatter: + +```shell +npx eslint -f ./customformat.js file.js +``` + +Use an npm-installed formatter: + +```shell +npm install eslint-formatter-pretty + +# Then run one of the following commands +npx eslint -f pretty file.js +# or alternatively +npx eslint -f eslint-formatter-pretty file.js +``` + +#### `--color` and `--no-color` + +These options force the enabling/disabling of colorized output. + +* **Argument Type**: No argument. + +You can use these options to override the default behavior, which is to enable colorized output unless no TTY is detected, such as when piping `eslint` through `cat` or `less`. + +##### `--color` and `--no-color` example + +```shell +npx eslint --color file.js | cat +npx eslint --no-color file.js +``` + +### Inline Configuration Comments + +#### `--no-inline-config` + +This option prevents inline comments like `/*eslint-disable*/` or +`/*global foo*/` from having any effect. + +* **Argument Type**: No argument. + +This allows you to set an ESLint config without files modifying it. All inline config comments are ignored, such as: + +* `/*eslint-disable*/` +* `/*eslint-enable*/` +* `/*global*/` +* `/*eslint*/` +* `/*eslint-env*/` +* `// eslint-disable-line` +* `// eslint-disable-next-line` + +##### `--no-inline-config` example + +```shell +npx eslint --no-inline-config file.js +``` + +#### `--report-unused-disable-directives` + +This option causes ESLint to report directive comments like `// eslint-disable-line` when no errors would have been reported on that line anyway. + +* **Argument Type**: No argument. + +This can be useful to prevent future errors from unexpectedly being suppressed, by cleaning up old `eslint-disable` comments which are no longer applicable. + +::: warning +When using this option, it is possible that new errors start being reported whenever ESLint or custom rules are upgraded. + +For example, suppose a rule has a bug that causes it to report a false positive, and an `eslint-disable` comment is added to suppress the incorrect report. If the bug is then fixed in a patch release of ESLint, the `eslint-disable` comment becomes unused since ESLint is no longer generating an incorrect report. This results in a new reported error for the unused directive if the `report-unused-disable-directives` option is used. +::: + +##### `--report-unused-disable-directives` example + +```shell +npx eslint --report-unused-disable-directives file.js +``` + +### Caching + +#### `--cache` + +Store the info about processed files in order to only operate on the changed ones. Enabling this option can dramatically improve ESLint's run time performance by ensuring that only changed files are linted. +The cache is stored in `.eslintcache` by default. + +* **Argument Type**: No argument. + +If you run ESLint with `--cache` and then run ESLint without `--cache`, the `.eslintcache` file will be deleted. This is necessary because the results of the lint might change and make `.eslintcache` invalid. If you want to control when the cache file is deleted, then use `--cache-location` to specify an alternate location for the cache file. + +Autofixed files are not placed in the cache. Subsequent linting that does not trigger an autofix will place it in the cache. + +##### `--cache` example + +```shell +npx eslint --cache file.js +``` + +#### `--cache-file` + +**Deprecated**: Use `--cache-location` instead. + +Path to the cache file. If none specified `.eslintcache` is used. The file is created in the directory where the `eslint` command is executed. + +#### `--cache-location` + +Specify the path to the cache location. Can be a file or a directory. + +* **Argument Type**: String. Path to file or directory. If a directory is specified, a cache file is created inside the specified folder. The name of the file is based on the hash of the current working directory, e.g.: `.cache_hashOfCWD`. +* **Multiple Arguments**: No +* **Default Value**: If no location is specified, `.eslintcache` is used. The file is created in the directory where the `eslint` command is executed. + +If the directory for the cache does not exist make sure you add a trailing `/` on \*nix systems or `\` on Windows. Otherwise, the path is assumed to be a file. + +##### `--cache-location` example + +```shell +npx eslint "src/**/*.js" --cache --cache-location "/Users/user/.eslintcache/" +``` + +#### `--cache-strategy` + +Strategy for the cache to use for detecting changed files. + +* **Argument Type**: String. One of the following values: + 1. `metadata` + 1. `content` +* **Multiple Arguments**: No +* **Default Value**: `metadata` + +The `content` strategy can be useful in cases where the modification time of your files changes even if their contents have not. For example, this can happen during git operations like `git clone` because git does not track file modification time. + +##### `--cache-strategy` example + +```shell +npx eslint "src/**/*.js" --cache --cache-strategy content +``` + +### Miscellaneous + +#### `--init` + +This option runs `npm init @eslint/config` to start the config initialization wizard. It's designed to help new users quickly create an `.eslintrc` file by answering a few questions. When you use this flag, the CLI does not perform linting. + +* **Argument Type**: No argument. + +The resulting configuration file is created in the current directory. + +##### `--init` example + +```shell +npx eslint --init +``` + +#### `--env-info` + +This option outputs information about the execution environment, including the version of Node.js, npm, and local and global installations of ESLint. + +* **Argument Type**: No argument. + +The ESLint team may ask for this information to help solve bugs. When you use this flag, the CLI does not perform linting. + +##### `--env-info` example + +```shell +npx eslint --env-info +``` + +#### `--no-error-on-unmatched-pattern` + +This option prevents errors when a quoted glob pattern or `--ext` is unmatched. This does not prevent errors when your shell can't match a glob. + +* **Argument Type**: No argument. + +##### `--no-error-on-unmatched-pattern` example + +```shell +npx eslint --no-error-on-unmatched-pattern --ext .ts "lib/*" +``` + +#### `--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, ESLint reports fatal parsing errors as rule violations. + +* **Argument Type**: No argument. + +##### `--exit-on-fatal-error` example + +```shell +npx eslint --exit-on-fatal-error file.js +``` + +#### `--debug` + +This option outputs debugging information to the console. Add this flag to an ESLint command line invocation in order to get extra debugging information while the command runs. + +* **Argument Type**: No argument. + +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. + +##### `--debug` example + +```shell +npx eslint --debug test.js +``` + +#### `-h`, `--help` + +This option outputs the help menu, displaying all of the available options. All other options are ignored when this is present. When you use this flag, the CLI does not perform linting. + +* **Argument Type**: No argument. + +##### `-h`, `--help` example + +```shell +npx eslint --help +``` + +#### `-v`, `--version` + +This option outputs the current ESLint version onto the console. All other options are ignored when this is present. When you use this flag, the CLI does not perform linting. + +* **Argument Type**: No argument. + +##### `-v`, `--version` example + +```shell +npx eslint --version +``` + +#### `--print-config` + +This option outputs the configuration to be used for the file passed. When present, no linting is performed and only config-related options are valid. When you use this flag, the CLI does not perform linting. + +* **Argument Type**: String. Path to file. +* **Multiple Arguments**: No + +##### `--print-config` example + +```shell +npx eslint --print-config file.js +``` + +## Exit Codes + +When linting files, ESLint exits with one of the following exit codes: + +* `0`: Linting was successful and there are no linting errors. If the [`--max-warnings`](#--max-warnings) flag is set to `n`, the number of linting warnings is at most `n`. +* `1`: Linting was successful and there is at least one linting error, or there are more linting warnings than allowed by the `--max-warnings` option. +* `2`: Linting was unsuccessful due to a configuration problem or an internal error. diff --git a/eslint/docs/src/user-guide/configuring/configuration-files-new.md b/eslint/docs/src/use/configure/configuration-files-new.md similarity index 68% rename from eslint/docs/src/user-guide/configuring/configuration-files-new.md rename to eslint/docs/src/use/configure/configuration-files-new.md index 87844f5..bf854f0 100644 --- a/eslint/docs/src/user-guide/configuring/configuration-files-new.md +++ b/eslint/docs/src/use/configure/configuration-files-new.md @@ -1,21 +1,22 @@ --- title: Configuration Files (New) -layout: doc eleventyNavigation: key: configuration files - parent: configuring + parent: configure title: Configuration Files (New) order: 1 --- ::: warning -This is an experimental feature. To opt-in, place a `eslint.config.js` file in the root of your project. If you are using the API, you can use the configuration system described on this page by using the `FlatESLint` class, the `FlatRuleTester` class, or by setting `configType: "flat"` in the `Linter` class. +This config system is feature complete but not enabled by default. To opt-in, place an `eslint.config.js` file in the root of your project or set the `ESLINT_USE_FLAT_CONFIG` environment variable to `true`. To opt-out, even in the presence of an `eslint.config.js` file, set the environment variable to `false`. If you are using the API, you can use the configuration system described on this page by using the `FlatESLint` class, the `FlatRuleTester` class, or by setting `configType: "flat"` in the `Linter` class. ::: +You can put your ESLint project configuration in a configuration file. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. + ## Configuration File -The ESLint configuration file is named `eslint.config.js` and should be placed in the root directory of your project and export an array of configuration objects. Here's an example: +The ESLint configuration file is named `eslint.config.js`. It should be placed in the root directory of your project and export an array of [configuration objects](#configuration-objects). Here's an example: ```js export default [ @@ -28,20 +29,20 @@ export default [ ] ``` -Here, the configuration array contains just one configuration object. The configuration object enables two rules: `semi` and `prefer-const`. These rules will be applied to all of the files ESLint processes using this config file. +In this example, the configuration array contains just one configuration object. The configuration object enables two rules: `semi` and `prefer-const`. These rules are applied to all of the files ESLint processes using this config file. ## Configuration Objects Each configuration object contains all of the information ESLint needs to execute on a set of files. Each configuration object is made up of these properties: -* `files` - An array of glob patterns indicating the files that the configuration object should apply to. If not specified, the configuration object applies to all files. +* `files` - An array of glob patterns indicating the files that the configuration object should apply to. If not specified, the configuration object applies to all files matched by any other configuration object. * `ignores` - An array of glob patterns indicating the files that the configuration object should not apply to. If not specified, the configuration object applies to all files matched by `files`. * `languageOptions` - An object containing settings related to how JavaScript is configured for linting. * `ecmaVersion` - The version of ECMAScript to support. May be any year (i.e., `2022`) or version (i.e., `5`). Set to `"latest"` for the most recent supported version. (default: `"latest"`) * `sourceType` - The type of JavaScript source code. Possible values are `"script"` for traditional script files, `"module"` for ECMAScript modules (ESM), and `"commonjs"` for CommonJS files. (default: `"module"` for `.js` and `.mjs` files; `"commonjs"` for `.cjs` files) * `globals` - An object specifying additional objects that should be added to the global scope during linting. - * `parser` - Either an object containing a `parse()` method or a string indicating the name of a parser inside of a plugin (i.e., `"pluginName/parserName"`). (default: `"@/espree"`) - * `parserOptions` - An object specifying additional options that are passed directly to the `parser()` method on the parser. The available options are parser-dependent. + * `parser` - An object containing a `parse()` method or a `parseForESLint()` method. (default: [`espree`](https://github.com/eslint/espree)) + * `parserOptions` - An object specifying additional options that are passed directly to the `parse()` or `parseForESLint()` method on the parser. The available options are parser-dependent. * `linterOptions` - An object containing settings related to the linting process. * `noInlineConfig` - A Boolean value indicating if inline configuration is allowed. * `reportUnusedDisableDirectives` - A Boolean value indicating if unused disable directives should be tracked and reported. @@ -56,7 +57,7 @@ Each configuration object contains all of the information ESLint needs to execut Patterns specified in `files` and `ignores` use [`minimatch`](https://www.npmjs.com/package/minimatch) syntax and are evaluated relative to the location of the `eslint.config.js` file. ::: -You can use a combination of `files` and `ignores` to determine which files should apply the configuration object and which should not. By default, ESLint matches `**/*.js`, `**/*.cjs`, and `**/*.mjs`. Because config objects that don't specify `files` or `ignores` apply to all files that have been matched by any other configuration object, by default config objects will apply to any JavaScript files passed to ESLint. For example: +You can use a combination of `files` and `ignores` to determine which files should apply the configuration object and which should not. By default, ESLint matches `**/*.js`, `**/*.cjs`, and `**/*.mjs`. Because config objects that don't specify `files` or `ignores` apply to all files that have been matched by any other configuration object, those config objects apply to any JavaScript files passed to ESLint by default. For example: ```js export default [ @@ -68,15 +69,15 @@ export default [ ]; ``` -With this configuration, the `semi` rule is enabled for all files that match the default files in ESLint. So if you pass `example.js` to ESLint, the `semi` rule will be applied. If you pass a non-JavaScript file, like `example.txt`, the `semi` rule will not be applied because there are no other configuration objects that match that filename. (ESLint will output an error message letting you know that the file was ignored due to missing configuration.) +With this configuration, the `semi` rule is enabled for all files that match the default files in ESLint. So if you pass `example.js` to ESLint, the `semi` rule is applied. If you pass a non-JavaScript file, like `example.txt`, the `semi` rule is not applied because there are no other configuration objects that match that filename. (ESLint outputs an error message letting you know that the file was ignored due to missing configuration.) #### Excluding files with `ignores` -You can limit which files a configuration object applies to by specifying a combination of `files` and `ignores` patterns. For example, you may want certain rules to apply only to files in your `src` directory, like this: +You can limit which files a configuration object applies to by specifying a combination of `files` and `ignores` patterns. For example, you may want certain rules to apply only to files in your `src` directory: ```js export default [ - { + { files: ["src/**/*.js"], rules: { semi: "error" @@ -85,11 +86,11 @@ export default [ ]; ``` -Here, only the JavaScript files in the `src` directory will have the `semi` rule applied. If you run ESLint on files in another directory, this configuration object will be skipped. By adding `ignores`, you can also remove some of the files in `src` from this configuration object: +Here, only the JavaScript files in the `src` directory have the `semi` rule applied. If you run ESLint on files in another directory, this configuration object is skipped. By adding `ignores`, you can also remove some of the files in `src` from this configuration object: ```js export default [ - { + { files: ["src/**/*.js"], ignores: ["**/*.config.js"], rules: { @@ -103,7 +104,7 @@ This configuration object matches all JavaScript files in the `src` directory ex ```js export default [ - { + { files: ["src/**/*.js"], ignores: ["**/*.config.js", "!**/eslint.config.js"], rules: { @@ -113,13 +114,13 @@ export default [ ]; ``` -Here, the configuration object excludes files ending with `.config.js` except for `eslint.config.js`. That file will still have `semi` applied. +Here, the configuration object excludes files ending with `.config.js` except for `eslint.config.js`. That file still has `semi` applied. If `ignores` is used without `files` and any other setting, then the configuration object applies to all files except the ones specified in `ignores`, for example: ```js export default [ - { + { ignores: ["**/*.config.js"], rules: { semi: "error" @@ -132,17 +133,31 @@ This configuration object applies to all files except those ending with `.config #### Globally ignoring files with `ignores` -If `ignores` is used without any other keys in the configuration object, then the patterns act as additional global ignores, similar to those found in `.eslintignore`. Here's an example: +If `ignores` is used without any other keys in the configuration object, then the patterns act as global ignores. Here's an example: ```js export default [ - { + { ignores: [".config/*"] } ]; ``` -This configuration specifies that all of the files in the `.config` directory should be ignored. This pattern is added after the patterns found in `.eslintignore`. +This configuration specifies that all of the files in the `.config` directory should be ignored. This pattern is added after the default patterns, which are `["**/node_modules/", ".git/"]`. + +You can also unignore files and directories that are ignored by the default patterns. For example, this config unignores `node_modules/mylibrary`: + +```js +export default [ + { + ignores: [ + "!node_modules/", // unignore `node_modules/` directory + "node_modules/*", // ignore its content + "!node_modules/mylibrary/" // unignore `node_modules/mylibrary` directory + ] + } +]; +``` #### Cascading configuration objects @@ -156,16 +171,16 @@ export default [ globals: { MY_CUSTOM_GLOBAL: "readonly" } - } + } }, - { + { files: ["tests/**/*.js"], languageOptions: { globals: { it: "readonly", describe: "readonly" } - } + } } ]; ``` @@ -193,7 +208,7 @@ export default [ #### Reporting unused disable directives -Disable directives such as `/*eslint-disable*/` and `/*eslint-disable-next-line*/` are used to disable ESLint rules around certain portions of code. As code changes, it's possible for these directives to no longer be needed because the code has changed in such a way that the rule will no longer be triggered. You can enable reporting of these unused disable directives by setting the `reportUnusedDisableDirectives` option to `true`, as in this example: +Disable directives such as `/*eslint-disable*/` and `/*eslint-disable-next-line*/` are used to disable ESLint rules around certain portions of code. As code changes, it's possible for these directives to no longer be needed because the code has changed in such a way that the rule is no longer triggered. You can enable reporting of these unused disable directives by setting the `reportUnusedDisableDirectives` option to `true`, as in this example: ```js export default [ @@ -232,8 +247,8 @@ export default [ ESLint can evaluate your code in one of three ways: 1. ECMAScript module (ESM) - Your code has a module scope and is run in strict mode. -1. CommonJS - Your code has a top-level function scope and runs in nonstrict mode. -1. Script - Your code has a shared global scope and runs in nonstrict mode. +1. CommonJS - Your code has a top-level function scope and runs in non-strict mode. +1. Script - Your code has a shared global scope and runs in non-strict mode. You can specify which of these modes your code is intended to run in by specifying the `sourceType` property. This property can be set to `"module"`, `"commonjs"`, or `"script"`. By default, `sourceType` is set to `"module"` for `.js` and `.mjs` files and is set to `"commonjs"` for `.cjs` files. Here's an example: @@ -250,7 +265,7 @@ export default [ #### Configuring a custom parser and its options -In many cases, you can use the default parser that ESLint ships with for parsing your JavaScript code. You can optionally override the default parser by using the `parser` property. The `parser` property can be either a string in the format `"pluginName/parserName"` (indicating to retrieve the parser from a plugin) or an object containing either a `parse()` method or a `parseForESLint()` method. For example, you can use the [`@babel/eslint-parser`](https://www.npmjs.com/package/@babel/eslint-parser) package to allow ESLint to parse experimental syntax: +In many cases, you can use the default parser that ESLint ships with for parsing your JavaScript code. You can optionally override the default parser by using the `parser` property. The `parser` property must be an object containing either a `parse()` method or a `parseForESLint()` method. For example, you can use the [`@babel/eslint-parser`](https://www.npmjs.com/package/@babel/eslint-parser) package to allow ESLint to parse experimental syntax: ```js import babelParser from "@babel/eslint-parser"; @@ -265,7 +280,7 @@ export default [ ]; ``` -This configuration ensures that the Babel parser, rather than the default, will be used to parse all files ending with `.js` and `.mjs`. +This configuration ensures that the Babel parser, rather than the default Espree parser, is used to parse all files ending with `.js` and `.mjs`. You can also pass options directly to the custom parser by using the `parserOptions` property. This property is an object whose name-value pairs are specific to the parser that you are using. For the Babel parser, you might pass in options like this: @@ -327,9 +342,33 @@ export default [ For historical reasons, the boolean value `false` and the string value `"readable"` are equivalent to `"readonly"`. Similarly, the boolean value `true` and the string value `"writeable"` are equivalent to `"writable"`. However, the use of older values is deprecated. +##### Predefined global variables + +Apart from the ECMAScript standard built-in globals, which are automatically enabled based on the configured `languageOptions.ecmaVersion`, ESLint doesn't provide predefined sets of global variables. You can use the [`globals`](https://www.npmjs.com/package/globals) package to additionally enable all globals for a specific environment. For example, here is how you can add `console`, amongst other browser globals, into your configuration. + +```js +import globals from "globals"; + +export default [ + { + languageOptions: { + globals: { + ...globals.browser + } + } + } +]; +``` + ### Using plugins in your configuration -Plugins are used to share rules, processors, configurations, parsers, and more across ESLint projects. Plugins are specified in a configuration object using the `plugins` key, which is an object where the name of the plugin is the property name and the value is the plugin object itself. Here's an example: +Plugins are used to share rules, processors, configurations, parsers, and more across ESLint projects. + +#### Using plugin rules + +You can use specific rules included in a plugin. To do this, specify the plugin +in a configuration object using the `plugins` key. The value for the `plugin` key +is an object where the name of the plugin is the property name and the value is the plugin object itself. Here's an example: ```js import jsdoc from "eslint-plugin-jsdoc"; @@ -339,11 +378,11 @@ export default [ files: ["**/*.js"], plugins: { jsdoc: jsdoc - } + }, rules: { "jsdoc/require-description": "error", "jsdoc/check-values": "error" - } + } } ]; ``` @@ -360,11 +399,11 @@ export default [ files: ["**/*.js"], plugins: { jsdoc - } + }, rules: { "jsdoc/require-description": "error", "jsdoc/check-values": "error" - } + } } ]; ``` @@ -379,17 +418,42 @@ export default [ files: ["**/*.js"], plugins: { jsd: jsdoc - } + }, rules: { "jsd/require-description": "error", "jsd/check-values": "error" - } + } } ]; ``` This configuration object uses `jsd` as the prefix plugin instead of `jsdoc`. +#### Using configurations included in plugins + +You can use a configuration included in a plugin by adding that configuration +directly to the `eslint.config.js` configurations array. +Often, you do this for a plugin's recommended configuration. Here's an example: + +```js +import jsdoc from "eslint-plugin-jsdoc"; + +export default [ + // configuration included in plugin + jsdoc.configs.recommended, + // other configuration objects... + { + files: ["**/*.js"], + plugins: { + jsdoc: jsdoc + }, + rules: { + "jsdoc/require-description": "warn", + } + } +]; +``` + ### Using processors Processors allow ESLint to transform text into pieces of code that ESLint can lint. You can specify the processor to use for a given file type by defining a `processor` property that contains either the processor name in the format `"pluginName/processorName"` to reference a processor in a plugin or an object containing both a `preprocess()` and a `postprocess()` method. For example, to extract JavaScript code blocks from a Markdown file, you might add this to your configuration: @@ -403,7 +467,7 @@ export default [ plugins: { markdown }, - processor: "markdown/markdown" + processor: "markdown/markdown", settings: { sharedData: "Hello" } @@ -411,7 +475,7 @@ export default [ ]; ``` -This configuration object specifies that there is a processor called `"markdown"` contained in the plugin named `"markdown"` and will apply the processor to all files ending with `.md`. +This configuration object specifies that there is a processor called `"markdown"` contained in the plugin named `"markdown"`. The configuration applies the processor to all files ending with `.md`. Processors may make named code blocks that function as filenames in configuration objects, such as `0.js` and `1.js`. ESLint handles such a named code block as a child of the original file. You can specify additional configuration objects for named code blocks. For example, the following disables the `strict` rule for the named code blocks which end with `.js` in markdown files. @@ -424,7 +488,7 @@ export default [ plugins: { markdown }, - processor: "markdown/markdown" + processor: "markdown/markdown", settings: { sharedData: "Hello" } @@ -454,7 +518,7 @@ export default [ ]; ``` -This configuration object specifies that the [`semi`](/docs/latest/rules/semi) rule should be enabled with a severity of `"error"`. You can also provide options to a rule by specifying an array where the first item is the severity and each item after that is an option for the rule. For example, you can switch the `semi` rule to disallow semicolons by passing `"never"` as an option: +This configuration object specifies that the [`semi`](../../rules/semi) rule should be enabled with a severity of `"error"`. You can also provide options to a rule by specifying an array where the first item is the severity and each item after that is an option for the rule. For example, you can switch the `semi` rule to disallow semicolons by passing `"never"` as an option: ```js export default [ @@ -473,7 +537,7 @@ Each rule specifies its own options and can be any valid JSON data type. Please There are three possible severities you can specify for a rule * `"error"` (or `2`) - the reported problem should be treated as an error. When using the ESLint CLI, errors cause the CLI to exit with a nonzero code. -* `"warn"` (or `1`) - the reported problem should be treated as a warning. When using the ESLint CLI, warnings are reported but do not change the exit code. If only errors are reported, the exit code will be 0. +* `"warn"` (or `1`) - the reported problem should be treated as a warning. When using the ESLint CLI, warnings are reported but do not change the exit code. If only warnings are reported, the exit code is 0. * `"off"` (or `0`) - the rule should be turned off completely. #### Rule configuration cascade @@ -516,7 +580,7 @@ Here, the second configuration object only overrides the severity, so the final ### Configuring shared settings -ESLint supports adding shared settings into configuration files. Plugins use `settings` to specify information that should be shared across all of its rules. You can add a `settings` object to a configuration object and it will be supplied to every rule being executed. This may be useful if you are adding custom rules and want them to have access to the same information. Here's an example: +ESLint supports adding shared settings into configuration files. When you add a `settings` object to a configuration object, it is supplied to every rule. By convention, plugins namespace the settings they are interested in to avoid collisions with others. Plugins can use `settings` to specify the information that should be shared across all of their rules. This may be useful if you are adding custom rules and want them to have access to the same information. Here's an example: ```js export default [ @@ -530,16 +594,18 @@ export default [ ### Using predefined configurations -ESLint has two predefined configurations: +ESLint has two predefined configurations for JavaScript: -* `eslint:recommended` - enables the rules that ESLint recommends everyone use to avoid potential errors -* `eslint:all` - enables all of the rules shipped with ESLint +* `js.configs.recommended` - enables the rules that ESLint recommends everyone use to avoid potential errors +* `js.configs.all` - enables all of the rules shipped with ESLint -To include these predefined configurations, you can insert the string values into the returned array and then make any modifications to other properties in subsequent configuration objects: +To include these predefined configurations, install the `@eslint/js` package and then make any modifications to other properties in subsequent configuration objects: ```js +import js from "@eslint/js"; + export default [ - "eslint:recommended", + js.configs.recommended, { rules: { semi: ["warn", "always"] @@ -548,18 +614,29 @@ export default [ ]; ``` -Here, the `eslint:recommended` predefined configuration is applied first and then another configuration object adds the desired configuration for `semi`. +Here, the `js.configs.recommended` predefined configuration is applied first and then another configuration object adds the desired configuration for `semi`. + +You can apply these predefined configs to just a subset of files by specifying a config object with a `files` key, like this: + +```js +import js from "@eslint/js"; + +export default [ + { + files: ["**/src/safe/*.js"], + ...js.configs.recommended + } +]; +``` ## Configuration File Resolution -When ESLint is run on the command line, it first checks the current working directory for `eslint.config.js`, and if not found, will look to the next parent directory for the file. This search continues until either the file is found or the root directory is reached. +When ESLint is run on the command line, it first checks the current working directory for `eslint.config.js`. If the file is not found, it looks to the next parent directory for the file. This search continues until either the file is found or the root directory is reached. -You can prevent this search for `eslint.config.js` by using the `-c` or `--config--file` option on the command line to specify an alternate configuration file, such as: +You can prevent this search for `eslint.config.js` by setting the `ESLINT_USE_FLAT_CONFIG` environment variable to `true` and using the `-c` or `--config` option on the command line to specify an alternate configuration file, such as: ```shell -npx eslint -c some-other-file.js **/*.js +ESLINT_USE_FLAT_CONFIG=true npx eslint -c some-other-file.js **/*.js ``` -In this case, ESLint will not search for `eslint.config.js` and will instead use `some-other-file.js`. - -Each configuration file exports one or more configuration object. A configuration object +In this case, ESLint does not search for `eslint.config.js` and instead uses `some-other-file.js`. diff --git a/eslint/docs/src/user-guide/configuring/configuration-files.md b/eslint/docs/src/use/configure/configuration-files.md similarity index 72% rename from eslint/docs/src/user-guide/configuring/configuration-files.md rename to eslint/docs/src/use/configure/configuration-files.md index 75b6d40..b2d51e0 100644 --- a/eslint/docs/src/user-guide/configuring/configuration-files.md +++ b/eslint/docs/src/use/configure/configuration-files.md @@ -1,14 +1,19 @@ --- title: Configuration Files -layout: doc eleventyNavigation: key: configuration files - parent: configuring + parent: configure title: Configuration Files - order: 1 + order: 2 --- +::: warning +We are transitioning to a new config system in ESLint v9.0.0. The config system shared on this page is currently the default but will be deprecated in v9.0.0. You can opt-in to the new config system by following the instructions in the [documentation](configuration-files-new). +::: + +You can put your ESLint project configuration in a configuration file. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. + ## Configuration File Formats ESLint supports configuration files in several formats: @@ -19,7 +24,7 @@ ESLint supports configuration files in several formats: * **JSON** - use `.eslintrc.json` to define the configuration structure. ESLint's JSON files also allow JavaScript-style comments. * **package.json** - create an `eslintConfig` property in your `package.json` file and define your configuration there. -If there are multiple configuration files in the same directory, ESLint will only use one. The priority order is as follows: +If there are multiple configuration files in the same directory, ESLint only uses one. The priority order is as follows: 1. `.eslintrc.js` 1. `.eslintrc.cjs` @@ -32,7 +37,7 @@ If there are multiple configuration files in the same directory, ESLint will onl There are two ways to use configuration files. -The first way to use configuration files is via `.eslintrc.*` and `package.json` files. ESLint will automatically look for them in the directory of the file to be linted, and in successive parent directories all the way up to the root directory of the filesystem (`/`), the home directory of the current user (`~/`), or when `root: true` is specified. See [Cascading and Hierarchy](#cascading-and-hierarchy) below for more details on this. Configuration files can be useful when you want different configurations for different parts of a project or when you want others to be able to use ESLint directly without needing to remember to pass in the configuration file. +The first way to use configuration files is via `.eslintrc.*` and `package.json` files. ESLint automatically looks for them in the directory of the file to be linted, and in successive parent directories all the way up to the root directory of the filesystem (`/`), the home directory of the current user (`~/`), or when `root: true` is specified. See [Cascading and Hierarchy](#cascading-and-hierarchy) below for more details on this. Configuration files can be useful when you want different configurations for different parts of a project or when you want others to be able to use ESLint directly without needing to remember to pass in the configuration file. The second way to use configuration files is to save the file wherever you would like and pass its location to the CLI using the `--config` option, such as: @@ -40,7 +45,7 @@ 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`](../command-line-interface#--no-eslintrc) along with the [`--config`](../../use/command-line-interface#-c---config) flag. Here's an example JSON configuration file that uses the `typescript-eslint` parser to support TypeScript syntax: @@ -71,7 +76,7 @@ Here's an example JSON configuration file that uses the `typescript-eslint` pars ### Comments in configuration files -Both the JSON and YAML configuration file formats support comments (package.json files should not include them). You can use JavaScript-style comments for JSON files and YAML-style comments for YAML files. ESLint safely ignores comments in configuration files. This allows your configuration files to be more human-friendly. +Both the JSON and YAML configuration file formats support comments (`package.json` files should not include them). You can use JavaScript-style comments for JSON files and YAML-style comments for YAML files. ESLint safely ignores comments in configuration files. This allows your configuration files to be more human-friendly. For JavaScript-style comments: @@ -101,7 +106,7 @@ rules: ## Adding Shared Settings -ESLint supports adding shared settings into configuration files. Plugins use `settings` to specify information that should be shared across all of its rules. You can add `settings` object to ESLint configuration file and it will be supplied to every rule being executed. This may be useful if you are adding custom rules and want them to have access to the same information and be easily configurable. +ESLint supports adding shared settings into configuration files. Plugins use `settings` to specify the information that should be shared across all of its rules. You can add a `settings` object to the ESLint configuration file and it is supplied to every executed rule. This may be useful if you are adding custom rules and want them to have access to the same information and be easily configurable. In JSON: @@ -123,7 +128,7 @@ And in YAML: ## Cascading and Hierarchy -When using `.eslintrc.*` and `package.json` files for configuration, you can take advantage of configuration cascading. Suppose you have the following structure: +When using `.eslintrc.*` and `package.json` files for configuration, you can take advantage of configuration cascading. Suppose your project has the following structure: ```text your-project @@ -135,9 +140,9 @@ your-project └── test.js ``` -The configuration cascade works based on the location of the file being linted. If there is a `.eslintrc` file in the same directory as the file being linted, then that configuration takes precedence. ESLint then searches up the directory structure, merging any `.eslintrc` files it finds along the way until reaching either a `.eslintrc` file with `root: true` or the root directory. +The configuration cascade works based on the location of the file being linted. If there is an `.eslintrc` file in the same directory as the file being linted, then that configuration takes precedence. ESLint then searches up the directory structure, merging any `.eslintrc` files it finds along the way until reaching either an `.eslintrc` file with `root: true` or the root directory. -In the same way, if there is a `package.json` file in the root directory with an `eslintConfig` field, the configuration it describes will apply to all subdirectories beneath it, but the configuration described by the `.eslintrc` file in the `tests/` directory will override it where there are conflicting specifications. +In the same way, if there is a `package.json` file in the root directory with an `eslintConfig` field, the configuration it describes is applied to all subdirectories beneath it. However, the configuration described by the `.eslintrc` file in the `tests/` directory overrides conflicting specifications. ```text your-project @@ -149,9 +154,9 @@ your-project └── test.js ``` -If there is a `.eslintrc` and a `package.json` file found in the same directory, `.eslintrc` will take priority and `package.json` file will not be used. +If there is an `.eslintrc` and a `package.json` file found in the same directory, `.eslintrc` takes priority and the `package.json` file is not used. -By default, ESLint will look for configuration files in all parent folders up to the root directory. This can be useful if you want all of your projects to follow a certain convention, but can sometimes lead to unexpected results. To limit ESLint to a specific project, place `"root": true` inside the `.eslintrc.*` file or `eslintConfig` field of the `package.json` file or in the `.eslintrc.*` file at your project's root level. ESLint will stop looking in parent folders once it finds a configuration with `"root": true`. +By default, ESLint looks for configuration files in all parent folders up to the root directory. This can be useful if you want all of your projects to follow a certain convention, but can sometimes lead to unexpected results. To limit ESLint to a specific project, place `"root": true` inside the `.eslintrc.*` file or `eslintConfig` field of the `package.json` file or in the `.eslintrc.*` file at your project's root level. ESLint stops looking in parent folders once it finds a configuration with `"root": true`. ```js { @@ -166,7 +171,7 @@ And in YAML: root: true ``` -For example, consider `projectA` which has `"root": true` set in the `.eslintrc` file in the `lib/` directory. In this case, while linting `main.js`, the configurations within `lib/` will be used, but the `.eslintrc` file in `projectA/` will not. +For example, consider `projectA` which has `"root": true` set in the `.eslintrc` file in the `lib/` directory. In this case, while linting `main.js`, the configurations within `lib/` are used, but the `.eslintrc` file in `projectA/` is not. ```text home @@ -194,7 +199,7 @@ The complete configuration hierarchy, from highest to lowest precedence, is as f 1. `.eslintrc.*` or `package.json` file in the same directory as the linted file 1. Continue searching for `.eslintrc.*` and `package.json` files in ancestor directories up to and including the root directory or until a config with `"root": true` is found. -Please note that the [home directory of the current user on your preferred operating system](https://nodejs.org/api/os.html#os_os_homedir) (`~/`) is also considered a root directory in this context and searching for configuration files will stop there as well. And with the [removal of support for Personal Configuration Files](https://eslint.org/docs/user-guide/configuring/configuration-files#personal-configuration-files-deprecated) from the 8.0.0 release forward, configuration files present in that directory will be ignored. +Please note that the [home directory of the current user on your preferred operating system](https://nodejs.org/api/os.html#os_os_homedir) (`~/`) is also considered a root directory in this context and searching for configuration files stops there as well. And with the [removal of support for Personal Configuration Files](configuration-files#personal-configuration-files-deprecated) from the 8.0.0 release forward, configuration files present in that directory are ignored. ## Extending Configuration Files @@ -231,7 +236,7 @@ The `rules` property can do any of the following to extend (or override) the set ### Using a shareable configuration package -A [sharable configuration](https://eslint.org/docs/developer-guide/shareable-configs) is an npm package that exports a configuration object. Make sure that you have installed the package in your project root directory, so that ESLint can require it. +A [sharable configuration](../../extend/shareable-configs) is an npm package that exports a configuration object. Make sure that you have installed the package in your project root directory, so that ESLint can require it. The `extends` property value can omit the `eslint-config-` prefix of the package name. @@ -250,7 +255,7 @@ rules: ### Using `eslint:recommended` -Using `"eslint:recommended"` in the `extends` property enables a subset of core rules that report common problems (these rules are identified with a checkmark (recommended) on the [rules page](https://eslint.org/docs/rules/)). +Using `"eslint:recommended"` in the `extends` property enables a subset of core rules that report common problems (these rules are identified with a checkmark (recommended) on the [rules page](../../rules/)). Here's an example of extending `eslint:recommended` and overriding some of the set configuration options: @@ -278,9 +283,9 @@ module.exports = { ### Using a configuration from a plugin -A [plugin](https://eslint.org/docs/developer-guide/working-with-plugins) is an npm package that can add various extensions to ESLint. A plugin can perform numerous functions, including but not limited to adding new rules and exporting [shareable configurations](https://eslint.org/docs/developer-guide/working-with-plugins#configs-in-plugins). Make sure the package has been installed in a directory where ESLint can require it. +A [plugin](../../extend/plugins) is an npm package that can add various extensions to ESLint. A plugin can perform numerous functions, including but not limited to adding new rules and exporting [shareable configurations](../../extend/plugins#configs-in-plugins). Make sure the package has been installed in a directory where ESLint can require it. -The `plugins` [property value](./plugins#configuring-plugins) can omit the `eslint-plugin-` prefix of the package name. +The `plugins` [property value](./plugins#configure-plugins) can omit the `eslint-plugin-` prefix of the package name. The `extends` property value can consist of: @@ -331,9 +336,9 @@ The `extends` property value can be `"eslint:all"` to enable all core rules in t **Important:** This configuration is **not recommended for production use** because it changes with every minor and major version of ESLint. Use it at your own risk. -You might enable all core rules as a shortcut to explore rules and options while you decide on the configuration for a project, especially if you rarely override options or disable rules. The default options for rules are not endorsements by ESLint (for example, the default option for the [`quotes`](https://eslint.org/docs/rules/quotes) rule does not mean double quotes are better than single quotes). +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`](../../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](../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: @@ -359,7 +364,7 @@ module.exports = { ## Configuration Based on Glob Patterns -v4.1.0+. Sometimes a more fine-controlled configuration is necessary, for example, if the configuration for files within the same directory has to be different. Therefore you can provide configurations under the `overrides` key that will only apply to files that match specific glob patterns, using the same format you would pass on the command line (e.g., `app/**/*.test.js`). +**v4.1.0+.** Sometimes a more fine-controlled configuration is necessary, like if the configuration for files within the same directory has to be different. In this case, you can provide configurations under the `overrides` key that only apply to files that match specific glob patterns, using the same format you would pass on the command line (e.g., `app/**/*.test.js`). Glob patterns in overrides use [minimatch syntax](https://github.com/isaacs/minimatch). @@ -389,11 +394,11 @@ In your `.eslintrc.json`: Here is how overrides work in a configuration file: -* The patterns are applied against the file path relative to the directory of the config file. For example, if your config file has the path `/Users/john/workspace/any-project/.eslintrc.js` and the file you want to lint has the path `/Users/john/workspace/any-project/lib/util.js`, then the pattern provided in `.eslintrc.js` will be executed against the relative path `lib/util.js`. +* The patterns are applied against the file path relative to the directory of the config file. For example, if your config file has the path `/Users/john/workspace/any-project/.eslintrc.js` and the file you want to lint has the path `/Users/john/workspace/any-project/lib/util.js`, then the pattern provided in `.eslintrc.js` is executed against the relative path `lib/util.js`. * Glob pattern overrides have higher precedence than the regular configuration in the same config file. Multiple overrides within the same config are applied in order. That is, the last override block in a config file always has the highest precedence. * A glob specific configuration works almost the same as any other ESLint config. Override blocks can contain any configuration options that are valid in a regular config, with the exception of `root` and `ignorePatterns`. * A glob specific configuration can have an `extends` setting, but the `root` property in the extended configs is ignored. The `ignorePatterns` property in the extended configs is used only for the files the glob specific configuration matched. - * Nested `overrides` setting will be applied only if the glob patterns of both of the parent config and the child config matched. This is the same when the extended configs have an `overrides` setting. + * Nested `overrides` settings are applied only if the glob patterns of both the parent config and the child config are matched. This is the same when the extended configs have an `overrides` setting. * Multiple glob patterns can be provided within a single override block. A file must match at least one of the supplied patterns for the configuration to apply. * Override blocks can also specify patterns to exclude from matches. If a file matches any of the excluded patterns, the configuration won't apply. @@ -423,11 +428,11 @@ If a config is provided via the `--config` CLI option, the glob patterns in the If you specified directories with CLI (e.g., `eslint lib`), ESLint searches target files in the directory to lint. The target files are `*.js` or the files that match any of `overrides` entries (but exclude entries that are any of `files` end with `*`). -If you specified the [`--ext`](https://eslint.org/docs/user-guide/command-line-interface#ext) command line option along with directories, the target files are only the files that have specified file extensions regardless of `overrides` entries. +If you specified the [`--ext`](../command-line-interface#--ext) command line option along with directories, the target files are only the files that have specified file extensions regardless of `overrides` entries. ## Personal Configuration Files (deprecated) -⚠️ **This feature has been deprecated**. This feature will be removed in the 8.0.0 release. If you want to continue to use personal configuration files, please use the [`--config` CLI option](https://eslint.org/docs/user-guide/command-line-interface#-c---config). For more information regarding this decision, please see [RFC 28](https://github.com/eslint/rfcs/pull/28) and [RFC 32](https://github.com/eslint/rfcs/pull/32). +⚠️ **This feature has been deprecated**. This feature was removed in the 8.0.0 release. If you want to continue to use personal configuration files, please use the [`--config` CLI option](../command-line-interface#-c---config). For more information regarding this decision, please see [RFC 28](https://github.com/eslint/rfcs/pull/28) and [RFC 32](https://github.com/eslint/rfcs/pull/32). `~/` refers to [the home directory of the current user on your preferred operating system](https://nodejs.org/api/os.html#os_os_homedir). The personal configuration file being referred to here is `~/.eslintrc.*` file, which is currently handled differently than other configuration files. @@ -443,4 +448,4 @@ If `eslint` could find configuration files in the project, `eslint` ignores `~/. `~/.eslintrc.*` files load shareable configs and custom parsers from `~/node_modules/` – similarly to `require()` – in the user's home directory. Please note that it doesn't load global-installed packages. -`~/.eslintrc.*` files load plugins from `$CWD/node_modules` by default in order to identify plugins uniquely. If you want to use plugins with `~/.eslintrc.*` files, plugins must be installed locally per project. Alternatively, you can use the [`--resolve-plugins-relative-to` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--resolve-plugins-relative-to) to change the location from which ESLint loads plugins. +`~/.eslintrc.*` files load plugins from `$CWD/node_modules` by default in order to identify plugins uniquely. If you want to use plugins with `~/.eslintrc.*` files, plugins must be installed locally per project. Alternatively, you can use the [`--resolve-plugins-relative-to` CLI option](../command-line-interface#--resolve-plugins-relative-to) to change the location from which ESLint loads plugins. diff --git a/eslint/docs/src/user-guide/configuring/ignoring-code.md b/eslint/docs/src/use/configure/ignore.md similarity index 73% rename from eslint/docs/src/user-guide/configuring/ignoring-code.md rename to eslint/docs/src/use/configure/ignore.md index c41422c..ffc2342 100644 --- a/eslint/docs/src/user-guide/configuring/ignoring-code.md +++ b/eslint/docs/src/use/configure/ignore.md @@ -1,17 +1,22 @@ --- -title: Ignoring Code -layout: doc +title: Ignore Files eleventyNavigation: - key: ignoring code - parent: configuring - title: Ignoring Code - order: 5 + key: ignore files + parent: configure + title: Ignore Files + order: 7 --- +You can configure ESLint to ignore certain files and directories while linting by specifying one or more glob patterns. +You can ignore files in the following ways: + +* Add `ignorePatterns` to a configuration file. +* Create a dedicated file that contains the ignore patterns (`.eslintignore` by default). + ## `ignorePatterns` in Config Files -You can tell ESLint to ignore specific files and directories using `ignorePatterns` in your config files. `ignorePatterns` patterns follow the same rules as `.eslintignore`. Please see the [the `.eslintignore` file documentation](./ignoring-code#the-eslintignore-file) to learn more. +You can tell ESLint to ignore specific files and directories using `ignorePatterns` in your config files. `ignorePatterns` patterns follow the same rules as `.eslintignore`. Please see the [`.eslintignore` file documentation](#the-eslintignore-file) to learn more. ```json { @@ -32,13 +37,13 @@ If a config is provided via the `--config` CLI option, the ignore patterns that ## The `.eslintignore` File -You can tell ESLint to ignore specific files and directories by creating an `.eslintignore` file in your project's root directory. The `.eslintignore` file is a plain text file where each line is a glob pattern indicating which paths should be omitted from linting. For example, the following will omit all JavaScript files: +You can tell ESLint to ignore specific files and directories by creating an `.eslintignore` file in your project's root directory. The `.eslintignore` file is a plain text file where each line is a glob pattern indicating which paths should be omitted from linting. For example, the following omits all JavaScript files: ```text **/*.js ``` -When ESLint is run, it looks in the current working directory to find an `.eslintignore` file before determining which files to lint. If this file is found, then those preferences are applied when traversing directories. Only one `.eslintignore` file can be used at a time, so `.eslintignore` files other than the one in the current working directory will not be used. +When ESLint is run, it looks in the current working directory to find an `.eslintignore` file before determining which files to lint. If this file is found, then those preferences are applied when traversing directories. Only one `.eslintignore` file can be used at a time, so `.eslintignore` files other than the one in the current working directory are not used. Globs are matched using [node-ignore](https://github.com/kaelzhang/node-ignore), so a number of features are available: @@ -62,17 +67,17 @@ Please see [`.gitignore`](https://git-scm.com/docs/gitignore)'s specification fo In addition to any patterns in the `.eslintignore` file, ESLint always follows a couple of implicit ignore rules even if the `--no-ignore` flag is passed. The implicit rules are as follows: * `node_modules/` is ignored. -* dot-files (except for `.eslintrc.*`), as well as dot-folders and their contents, are ignored. +* dot-files (except for `.eslintrc.*`) as well as dot-folders and their contents are ignored. There are also some exceptions to these rules: -* If the path to lint is a glob pattern or directory path and contains a dot-folder, all dot-files and dot-folders will be linted. This includes dot-files and dot-folders that are buried deeper in the directory structure. +* If the path to lint is a glob pattern or directory path and contains a dot-folder, all dot-files and dot-folders are linted. This includes dot-files and dot-folders that are buried deeper in the directory structure. - For example, `eslint .config/` will lint all dot-folders and dot-files in the `.config` directory, including immediate children as well as children that are deeper in the directory structure. + For example, `eslint .config/` would lint all dot-folders and dot-files in the `.config` directory, including immediate children as well as children that are deeper in the directory structure. -* If the path to lint is a specific file path and the `--no-ignore` flag has been passed, ESLint will lint the file regardless of the implicit ignore rules. +* If the path to lint is a specific file path and the `--no-ignore` flag has been passed, ESLint would lint the file regardless of the implicit ignore rules. - For example, `eslint .config/my-config-file.js --no-ignore` will cause `my-config-file.js` to be linted. It should be noted that the same command without the `--no-ignore` line will not lint the `my-config-file.js` file. + For example, `eslint .config/my-config-file.js --no-ignore` would cause `my-config-file.js` to be linted. It should be noted that the same command without the `--no-ignore` line would not lint the `my-config-file.js` file. * Allowlist and denylist rules specified via `--ignore-pattern` or `.eslintignore` are prioritized above implicit ignore rules. @@ -106,11 +111,11 @@ You can also use your `.gitignore` file: eslint --ignore-path .gitignore file.js ``` -Any file that follows the standard ignore file format can be used. Keep in mind that specifying `--ignore-path` means that any existing `.eslintignore` file will not be used. Note that globbing rules in `.eslintignore` follow those of `.gitignore`. +Any file that follows the standard ignore file format can be used. Keep in mind that specifying `--ignore-path` means that the existing `.eslintignore` file is not used. Note that globbing rules in `.eslintignore` follow those of `.gitignore`. ## Using eslintIgnore in package.json -If an `.eslintignore` file is not found and an alternate file is not specified, ESLint will look in package.json for an `eslintIgnore` key to check for files to ignore. +If an `.eslintignore` file is not found and an alternate file is not specified, ESLint looks in `package.json` for the `eslintIgnore` key to check for files to ignore. ```json { @@ -128,7 +133,7 @@ If an `.eslintignore` file is not found and an alternate file is not specified, ## Ignored File Warnings -When you pass directories to ESLint, files and directories are silently ignored. If you pass a specific file to ESLint, then you will see a warning indicating that the file was skipped. For example, suppose you have an `.eslintignore` file that looks like this: +When you pass directories to ESLint, files and directories are silently ignored. If you pass a specific file to ESLint, then ESLint creates a warning that the file was skipped. For example, suppose you have an `.eslintignore` file that looks like this: ```text foo.js @@ -151,7 +156,7 @@ foo.js This message occurs because ESLint is unsure if you wanted to actually lint the file or not. As the message indicates, you can use `--no-ignore` to omit using the ignore rules. -Consider another scenario where you may want to run ESLint on a specific dot-file or dot-folder, but have forgotten to specifically allow those files in your `.eslintignore` file. You would run something like this: +Consider another scenario where you want to run ESLint on a specific dot-file or dot-folder, but have forgotten to specifically allow those files in your `.eslintignore` file. You would run something like this: ```shell eslint .config/foo.js @@ -166,4 +171,4 @@ You would see this warning: ✖ 1 problem (0 errors, 1 warning) ``` -This message occurs because, normally, this file would be ignored by ESLint's implicit ignore rules (as mentioned above). A negated ignore rule in your `.eslintignore` file would override the implicit rule and reinclude this file for linting. Additionally, in this specific case, `--no-ignore` could be used to lint the file as well. +This message occurs because, normally, this file would be ignored by ESLint's implicit ignore rules (as mentioned above). A negated ignore rule in your `.eslintignore` file would override the implicit rule and reinclude this file for linting. Additionally, in this case, `--no-ignore` could be used to lint the file as well. diff --git a/eslint/docs/src/user-guide/configuring/index.md b/eslint/docs/src/use/configure/index.md similarity index 75% rename from eslint/docs/src/user-guide/configuring/index.md rename to eslint/docs/src/use/configure/index.md index 2eaad9c..e228383 100644 --- a/eslint/docs/src/user-guide/configuring/index.md +++ b/eslint/docs/src/use/configure/index.md @@ -1,11 +1,10 @@ --- -title: Configuring ESLint -layout: doc +title: Configure ESLint eleventyNavigation: - key: configuring - parent: user guide - title: Configuring - order: 2 + key: configure + parent: use eslint + title: Configure ESLint + order: 3 --- @@ -35,27 +34,30 @@ All of these options give you fine-grained control over how ESLint treats your c * [Configuration Based on Glob Patterns](./configuration-files#configuration-based-on-glob-patterns) * [Personal Configuration Files](./configuration-files#personal-configuration-files-deprecated) -[**Language Options**](language-options) +[**Configure Language Options**](language-options) * [Specifying Environments](./language-options#specifying-environments) * [Specifying Globals](./language-options#specifying-globals) * [Specifying Parser Options](./language-options#specifying-parser-options) -[**Rules**](rules) +[**Configure Rules**](rules) -* [Configuring Rules](./rules#configuring-rules) +* [Configuring Rules](./rules) * [Disabling Rules](./rules#disabling-rules) -[**Plugins**](plugins) +[**Configure Plugins**](plugins) -* [Specifying Parser](./plugins#specifying-parser) -* [Specifying Processor](./plugins#specifying-processor) -* [Configuring Plugins](./plugins#configuring-plugins) +* [Configure Plugins](./plugins#configure-plugins) +* [Specify a Processor](./plugins#specify-a-processor) -[**Ignoring Code**](ignoring-code) +[**Configure a Parser**](./parser) -* [`ignorePatterns` in Config Files](./ignoring-code#ignorepatterns-in-config-files) -* [The `.eslintignore` File](./ignoring-code#the-eslintignore-file) -* [Using an Alternate File](./ignoring-code#using-an-alternate-file) -* [Using eslintIgnore in package.json](./ignoring-code#using-eslintignore-in-packagejson) -* [Ignored File Warnings](./ignoring-code#ignored-file-warnings) +* [Configure a Custom Parser](./parser#configure-a-custom-parser) + +[**Ignore Files**](ignore) + +* [`ignorePatterns` in Config Files](./ignore#ignorepatterns-in-config-files) +* [The `.eslintignore` File](./ignore#the-eslintignore-file) +* [Using an Alternate File](./ignore#using-an-alternate-file) +* [Using eslintIgnore in package.json](./ignore#using-eslintignore-in-packagejson) +* [Ignored File Warnings](./ignore#ignored-file-warnings) diff --git a/eslint/docs/src/user-guide/configuring/language-options.md b/eslint/docs/src/use/configure/language-options.md similarity index 78% rename from eslint/docs/src/user-guide/configuring/language-options.md rename to eslint/docs/src/use/configure/language-options.md index 1ec3233..4f8fa14 100644 --- a/eslint/docs/src/user-guide/configuring/language-options.md +++ b/eslint/docs/src/use/configure/language-options.md @@ -1,14 +1,15 @@ --- -title: Language Options -layout: doc +title: Configure Language Options eleventyNavigation: - key: configuring language options - parent: configuring - title: Configuring Language Options - order: 2 + key: configure language options + parent: configure + title: Configure Language Options + order: 3 --- +The JavaScript ecosystem has a variety of runtimes, versions, extensions, and frameworks. Each of these can have different supported syntax and global variables. ESLint lets you configure language options specific to the JavaScript used in your project, like custom global variables. You can also use plugins to extend ESLint to support your project's language options. + ## Specifying Environments An environment provides predefined global variables. The available environments are: @@ -26,7 +27,7 @@ An environment provides predefined global variables. The available environments * `es2021` - adds all ECMAScript 2021 globals and automatically sets the `ecmaVersion` parser option to 12. * `es2022` - adds all ECMAScript 2022 globals and automatically sets the `ecmaVersion` parser option to 13. * `worker` - web workers global variables. -* `amd` - defines `require()` and `define()` as global variables as per the [amd](https://github.com/amdjs/amdjs-api/wiki/AMD) spec. +* `amd` - defines `require()` and `define()` as global variables as per the [amd](https://github.com/amdjs/amdjs-api/blob/master/AMD.md) spec. * `mocha` - adds all of the Mocha testing global variables. * `jasmine` - adds all of the Jasmine testing global variables for version 1.3 and 2.0. * `jest` - Jest global variables. @@ -52,7 +53,7 @@ Environments can be specified inside of a file, in configuration files or using ### Using configuration comments -To specify environments using a comment inside of your JavaScript file, use the following format: +To specify environments with a comment inside of a JavaScript file, use the following format: ```js /* eslint-env node, mocha */ @@ -62,7 +63,7 @@ This enables Node.js and Mocha environments. ### Using configuration files -To specify environments in a configuration file, use the `env` key and specify which environments you want to enable by setting each to `true`. For example, the following enables the browser and Node.js environments: +To specify environments in a configuration file, use the `env` key. Specify which environments you want to enable by setting each to `true`. For example, the following enables the browser and Node.js environments: ```json { @@ -167,7 +168,7 @@ And in YAML: These examples allow `var1` to be overwritten in your code, but disallow it for `var2`. -Globals can be disabled with the string `"off"`. For example, in an environment where most ES2015 globals are available but `Promise` is unavailable, you might use this config: +Globals can be disabled by setting their value to `"off"`. For example, in an environment where most ES2015 globals are available but `Promise` is unavailable, you might use this config: ```json { @@ -180,21 +181,19 @@ Globals can be disabled with the string `"off"`. For example, in an environment } ``` -For historical reasons, the boolean value `false` and the string value `"readable"` are equivalent to `"readonly"`. Similarly, the boolean value `true` and the string value `"writeable"` are equivalent to `"writable"`. However, the use of older values is deprecated. +For historical reasons, the boolean value `false` and the string value `"readable"` are equivalent to `"readonly"`. Similarly, the boolean value `true` and the string value `"writeable"` are equivalent to `"writable"`. However, the use of these older values is deprecated. ## Specifying Parser Options -ESLint allows you to specify the JavaScript language options you want to support. By default, ESLint expects ECMAScript 5 syntax. You can override that setting to enable support for other ECMAScript versions as well as JSX by using parser options. +ESLint allows you to specify the JavaScript language options you want to support. By default, ESLint expects ECMAScript 5 syntax. You can override that setting to enable support for other ECMAScript versions and JSX using parser options. -Please note that supporting JSX syntax is not the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) if you are using React and want React semantics. -By the same token, supporting ES6 syntax is not the same as supporting new ES6 globals (e.g., new types such as -`Set`). -For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 global variables, use `{ "env": -{ "es6": true } }`. `{ "env": { "es6": true } }` enables ES6 syntax automatically, but `{ "parserOptions": { "ecmaVersion": 6 } }` does not enable ES6 globals automatically. +Please note that supporting JSX syntax is not the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) if you are using React. -Parser options are set in your `.eslintrc.*` file by using the `parserOptions` property. The available options are: +By the same token, supporting ES6 syntax is not the same as supporting new ES6 globals (e.g., new types such as `Set`). For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 global variables, use `{ "env": { "es6": true } }`. Setting `{ "env": { "es6": true } }` enables ES6 syntax automatically, but `{ "parserOptions": { "ecmaVersion": 6 } }` does not enable ES6 globals automatically. -* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, 12, 13, or 14 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), 2022 (same as 13), or 2023 (same as 14) to use the year-based naming. You can also set "latest" to use the most recently supported version. +Parser options are set in your `.eslintrc.*` file with the `parserOptions` property. The available options are: + +* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, 12, 13, or 14 to specify the version of ECMAScript syntax you want to use. You can also set it to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), 2018 (same as 9), 2019 (same as 10), 2020 (same as 11), 2021 (same as 12), 2022 (same as 13), or 2023 (same as 14) to use the year-based naming. You can also set `"latest"` to use the most recently supported version. * `sourceType` - set to `"script"` (default) or `"module"` if your code is in ECMAScript modules. * `allowReserved` - allow the use of reserved words as identifiers (if `ecmaVersion` is 3). * `ecmaFeatures` - an object indicating which additional language features you'd like to use: diff --git a/eslint/docs/src/use/configure/parser.md b/eslint/docs/src/use/configure/parser.md new file mode 100644 index 0000000..76d94d1 --- /dev/null +++ b/eslint/docs/src/use/configure/parser.md @@ -0,0 +1,38 @@ +--- +title: Configure a Parser +eleventyNavigation: + key: configure parser + parent: configure + title: Configure a Parser + order: 6 +--- + +You can use custom parsers to convert JavaScript code into an abstract syntax tree for ESLint to evaluate. You might want to add a custom parser if your code isn't compatible with ESLint's default parser, Espree. + +## Configure a Custom Parser + +By default, ESLint uses [Espree](https://github.com/eslint/espree) as its parser. You can optionally specify that a different parser should be used in your configuration file if the parser meets the following requirements: + +1. It must be a Node module loadable from the config file where the parser is used. Usually, this means you should install the parser package separately using npm. +1. It must conform to the [parser interface](../../extend/custom-parsers). + +Note that even with these compatibilities, there are no guarantees that an external parser works correctly with ESLint. ESLint does not fix bugs related to incompatibilities with other parsers. + +To indicate the npm module to use as your parser, specify it using the `parser` option in your `.eslintrc` file. For example, the following specifies to use Esprima instead of Espree: + +```json +{ + "parser": "esprima", + "rules": { + "semi": "error" + } +} +``` + +The following parsers are compatible with ESLint: + +* [Esprima](https://www.npmjs.com/package/esprima) +* [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) - A wrapper around the [Babel](https://babeljs.io) parser that makes it compatible with ESLint. +* [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser) - A parser that converts TypeScript into an ESTree-compatible form so it can be used in ESLint. + +Note that when using a custom parser, the `parserOptions` configuration property is still required for ESLint to work properly with features not in ECMAScript 5 by default. Parsers are all passed `parserOptions` and may or may not use them to determine which features to enable. diff --git a/eslint/docs/src/user-guide/configuring/plugins.md b/eslint/docs/src/use/configure/plugins.md similarity index 60% rename from eslint/docs/src/user-guide/configuring/plugins.md rename to eslint/docs/src/use/configure/plugins.md index 12eef7e..3026592 100644 --- a/eslint/docs/src/user-guide/configuring/plugins.md +++ b/eslint/docs/src/use/configure/plugins.md @@ -1,45 +1,119 @@ --- -title: Plugins -layout: doc +title: Configure Plugins eleventyNavigation: - key: configuring plugins - parent: configuring - title: Configuring Plugins - order: 4 + key: configure plugins + parent: configure + title: Configure Plugins + order: 5 --- -## Specifying Parser +You can extend ESLint with plugins in a variety of different ways. Plugins can include: -By default, ESLint uses [Espree](https://github.com/eslint/espree) as its parser. You can optionally specify that a different parser should be used in your configuration file so long as the parser meets the following requirements: +* Custom rules to validate if your code meets a certain expectation, and what to do if it does not meet that expectation. +* Custom configurations. +* Custom environments. +* Custom processors to extract JavaScript code from other kinds of files or preprocess code before linting. -1. It must be a Node module loadable from the config file where the parser is used. Usually, this means you should install the parser package separately using npm. -1. It must conform to the [parser interface](../../developer-guide/working-with-custom-parsers). +## Configure Plugins -Note that even with these compatibilities, there are no guarantees that an external parser will work correctly with ESLint and ESLint will not fix bugs related to incompatibilities with other parsers. +ESLint supports the use of third-party plugins. Before using a plugin, you have to install it using npm. -To indicate the npm module to use as your parser, specify it using the `parser` option in your `.eslintrc` file. For example, the following specifies to use Esprima instead of Espree: +To configure plugins inside of a configuration file, use the `plugins` key, which contains a list of plugin names. The `eslint-plugin-` prefix can be omitted from the plugin name. ```json { - "parser": "esprima", - "rules": { - "semi": "error" - } + "plugins": [ + "plugin1", + "eslint-plugin-plugin2" + ] } ``` -The following parsers are compatible with ESLint: +And in YAML: -* [Esprima](https://www.npmjs.com/package/esprima) -* [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) - A wrapper around the [Babel](https://babeljs.io) parser that makes it compatible with ESLint. -* [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser) - A parser that converts TypeScript into an ESTree-compatible form so it can be used in ESLint. +```yaml +--- + plugins: + - plugin1 + - eslint-plugin-plugin2 +``` -Note when using a custom parser, the `parserOptions` configuration property is still required for ESLint to work properly with features not in ECMAScript 5 by default. Parsers are all passed `parserOptions` and may or may not use them to determine which features to enable. +**Notes:** -## Specifying Processor +1. Plugins are resolved relative to the config file. In other words, ESLint loads the plugin as a user would obtain by running `require('eslint-plugin-pluginname')` in the config file. +2. Plugins in the base configuration (loaded by `extends` setting) are relative to the derived config file. For example, if `./.eslintrc` has `extends: ["foo"]` and the `eslint-config-foo` has `plugins: ["bar"]`, ESLint finds the `eslint-plugin-bar` from `./node_modules/` (rather than `./node_modules/eslint-config-foo/node_modules/`) or ancestor directories. Thus every plugin in the config file and base configurations is resolved uniquely. -Plugins may provide processors. Processors can extract JavaScript code from other kinds of files, then let ESLint lint the JavaScript code or processors can convert JavaScript code in preprocessing for some purpose. +### Naming convention + +#### Include a plugin + +The `eslint-plugin-` prefix can be omitted for both non-scoped and scoped packages. + +A non-scoped package: + +```js +{ + // ... + "plugins": [ + "jquery", // means eslint-plugin-jquery + ] + // ... +} +``` + +A scoped package: + +```js +{ + // ... + "plugins": [ + "@jquery/jquery", // means @jquery/eslint-plugin-jquery + "@foobar" // means @foobar/eslint-plugin + ] + // ... +} +``` + +#### Use a plugin + +Rules, environments, and configurations defined in plugins must be referenced with the following convention: + +* `eslint-plugin-foo` → `foo/a-rule` +* `@foo/eslint-plugin` → `@foo/a-config` +* `@foo/eslint-plugin-bar` → `@foo/bar/a-environment` + +For example: + +```js +{ + // ... + "plugins": [ + "jquery", // eslint-plugin-jquery + "@foo/foo", // @foo/eslint-plugin-foo + "@bar" // @bar/eslint-plugin + ], + "extends": [ + "plugin:@foo/foo/recommended", + "plugin:@bar/recommended" + ], + "rules": { + "jquery/a-rule": "error", + "@foo/foo/some-rule": "error", + "@bar/another-rule": "error" + }, + "env": { + "jquery/jquery": true, + "@foo/foo/env-foo": true, + "@bar/env-bar": true, + } + // ... +} +``` + +### Specify a Processor + +Plugins may provide processors. Processors can extract JavaScript code from other kinds of files, then let ESLint lint the JavaScript code. Alternatively, processors can convert JavaScript code during preprocessing. To specify processors in a configuration file, use the `processor` key with the concatenated string of a plugin name and a processor name by a slash. For example, the following enables the processor `a-processor` that the plugin `a-plugin` provided: @@ -85,97 +159,3 @@ Processors may make named code blocks such as `0.js` and `1.js`. ESLint handles ``` ESLint checks the file path of named code blocks then ignores those if any `overrides` entry didn't match the file path. Be sure to add an `overrides` entry if you want to lint named code blocks other than `*.js`. - -## Configuring Plugins - -ESLint supports the use of third-party plugins. Before using the plugin, you have to install it using npm. - -To configure plugins inside of a configuration file, use the `plugins` key, which contains a list of plugin names. The `eslint-plugin-` prefix can be omitted from the plugin name. - -```json -{ - "plugins": [ - "plugin1", - "eslint-plugin-plugin2" - ] -} -``` - -And in YAML: - -```yaml ---- - plugins: - - plugin1 - - eslint-plugin-plugin2 -``` - -**Notes:** - -1. Plugins are resolved relative to the config file. In other words, ESLint will load the plugin as a user would obtain by running `require('eslint-plugin-pluginname')` in the config file. -2. Plugins in the base configuration (loaded by `extends` setting) are relative to the derived config file. For example, if `./.eslintrc` has `extends: ["foo"]` and the `eslint-config-foo` has `plugins: ["bar"]`, ESLint finds the `eslint-plugin-bar` from `./node_modules/` (rather than `./node_modules/eslint-config-foo/node_modules/`) or ancestor directories. Thus every plugin in the config file and base configurations is resolved uniquely. - -### Naming convention - -#### Include a plugin - -The `eslint-plugin-` prefix can be omitted for non-scoped packages - -```js -{ - // ... - "plugins": [ - "jquery", // means eslint-plugin-jquery - ] - // ... -} -``` - -The same rule does apply to scoped packages: - -```js -{ - // ... - "plugins": [ - "@jquery/jquery", // means @jquery/eslint-plugin-jquery - "@foobar" // means @foobar/eslint-plugin - ] - // ... -} -``` - -#### Use a plugin - -When using rules, environments or configs defined by plugins, they must be referenced following the convention: - -* `eslint-plugin-foo` → `foo/a-rule` -* `@foo/eslint-plugin` → `@foo/a-config` -* `@foo/eslint-plugin-bar` → `@foo/bar/a-environment` - -For example: - -```js -{ - // ... - "plugins": [ - "jquery", // eslint-plugin-jquery - "@foo/foo", // @foo/eslint-plugin-foo - "@bar" // @bar/eslint-plugin - ], - "extends": [ - "plugin:@foo/foo/recommended", - "plugin:@bar/recommended" - ], - "rules": { - "jquery/a-rule": "error", - "@foo/foo/some-rule": "error", - "@bar/another-rule": "error" - }, - "env": { - "jquery/jquery": true, - "@foo/foo/env-foo": true, - "@bar/env-bar": true, - } - // ... -} -``` diff --git a/eslint/docs/src/user-guide/configuring/rules.md b/eslint/docs/src/use/configure/rules.md similarity index 72% rename from eslint/docs/src/user-guide/configuring/rules.md rename to eslint/docs/src/use/configure/rules.md index e504a53..fd93c29 100644 --- a/eslint/docs/src/user-guide/configuring/rules.md +++ b/eslint/docs/src/use/configure/rules.md @@ -1,22 +1,29 @@ --- -title: Rules -layout: doc +title: Configure Rules eleventyNavigation: - key: configuring rules - parent: configuring - title: Configuring Rules - order: 3 + key: configure rules + parent: configure + title: Configure Rules + order: 4 --- -## Configuring Rules +Rules are the core building block of ESLint. A rule validates if your code meets a certain expectation, and what to do if it does not meet that expectation. Rules can also contain additional configuration options specific to that rule. -ESLint comes with a large number of built-in rules and you can add more rules through plugins. You can modify which rules your project uses either using configuration comments or configuration files. To change a rule setting, you must set the rule ID equal to one of these values: +ESLint comes with a large number of [built-in rules](../../rules/) and you can add more rules through plugins. You can modify which rules your project uses with either configuration comments or configuration files. + +## Rule Severities + +To change a rule's severity, set the rule ID equal to one of these values: * `"off"` or `0` - turn the rule off * `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code) * `"error"` or `2` - turn the rule on as an error (exit code is 1 when triggered) +Rules are typically set to `"error"` to enforce compliance with the rule during continuous integration testing, pre-commit checks, and pull request merging because doing so causes ESLint to exit with a non-zero exit code. + +If you don't want to enforce compliance with a rule but would still like ESLint to report the rule's violations, set the severity to `"warn"`. This is typically used when introducing a new rule that will eventually be set to `"error"`, when a rule is flagging something other than a potential buildtime or runtime error (such as an unused variable), or when a rule cannot determine with certainty that a problem has been found (when a rule might have false positives and need manual review). + ### Using configuration comments To configure rules inside of a file using configuration comments, use a comment in the following format: @@ -41,6 +48,8 @@ If a rule has additional options, you can specify them using array literal synta This comment specifies the "double" option for the [`quotes`](../../rules/quotes) rule. The first item in the array is always the rule severity (number or string). +#### Configuration Comment Descriptions + Configuration comments can include descriptions to explain why the comment is necessary. The description must occur after the configuration and is separated from the configuration by two or more consecutive `-` characters. For example: ```js @@ -86,7 +95,11 @@ rules: - double ``` -To configure a rule which is defined within a plugin you have to prefix the rule ID with the plugin name and a `/`. For example: +## Rules from Plugins + +To configure a rule that is defined within a plugin, prefix the rule ID with the plugin name and `/`. + +In a configuration file, for example: ```json { @@ -117,7 +130,9 @@ rules: plugin1/rule1: error ``` -In these configuration files, the rule `plugin1/rule1` comes from the plugin named `plugin1`. You can also use this format with configuration comments, such as: +In these configuration files, the rule `plugin1/rule1` comes from the plugin named `plugin1`, which is contained in an npm package named `eslint-plugin-plugin1`. + +You can also use this format with configuration comments, such as: ```js /* eslint "plugin1/rule1": "error" */ @@ -129,7 +144,7 @@ In these configuration files, the rule `plugin1/rule1` comes from the plugin nam ### Using configuration comments -To temporarily disable rule warnings in your file, use block comments in the following format: +To disable rule warnings in a part of a file, use block comments in the following format: ```js /* eslint-disable */ @@ -150,7 +165,7 @@ console.log('bar'); /* eslint-enable no-alert, no-console */ ``` -**Note:** `/* eslint-enable */` without any specific rules listed will cause all disabled rules to be re-enabled. +**Note:** `/* eslint-enable */` without any specific rules listed causes all disabled rules to be re-enabled. To disable rule warnings in an entire file, put a `/* eslint-disable */` block comment at the top of the file: @@ -232,7 +247,11 @@ foo(); // eslint-disable-line example/rule-name foo(); /* eslint-disable-line example/rule-name */ ``` -Configuration comments can include descriptions to explain why the comment is necessary. The description must come after the configuration and needs to be separated from the configuration by two or more consecutive `-` characters. For example: +**Note:** Comments that disable warnings for a portion of a file tell ESLint not to report rule violations for the disabled code. ESLint still parses the entire file, however, so disabled code still needs to be syntactically valid JavaScript. + +#### Comment descriptions + +Configuration comments can include descriptions to explain why disabling or re-enabling the rule is necessary. The description must come after the configuration and needs to be separated from the configuration by two or more consecutive `-` characters. For example: ```js // eslint-disable-next-line no-console -- Here's a description about why this configuration is necessary. @@ -245,8 +264,6 @@ console.log('hello'); console.log('hello'); ``` -**Note:** Comments that disable warnings for a portion of a file tell ESLint not to report rule violations for the disabled code. ESLint still parses the entire file, however, so disabled code still needs to be syntactically valid JavaScript. - ### Using configuration files To disable rules inside of a configuration file for a group of files, use the `overrides` key along with a `files` key. For example: @@ -267,7 +284,7 @@ To disable rules inside of a configuration file for a group of files, use the `o ### Disabling Inline Comments -To disable all inline config comments, use the `noInlineConfig` setting. For example: +To disable all inline config comments, use the `noInlineConfig` setting in your configuration file. For example: ```json { @@ -276,7 +293,7 @@ To disable all inline config comments, use the `noInlineConfig` setting. For exa } ``` -This setting is similar to [--no-inline-config](../command-line-interface#--no-inline-config) CLI option. +You can also use the [--no-inline-config](../command-line-interface#--no-inline-config) CLI option to disable rule comments, in addition to other in-line configuration. #### Report unused `eslint-disable` comments diff --git a/eslint/docs/src/use/core-concepts.md b/eslint/docs/src/use/core-concepts.md new file mode 100644 index 0000000..b9dfbbb --- /dev/null +++ b/eslint/docs/src/use/core-concepts.md @@ -0,0 +1,82 @@ +--- +title: Core Concepts +eleventyNavigation: + key: core concepts + title: Core Concepts + parent: use eslint + order: 2 +--- + +This page contains a high-level overview of some of the core concepts of ESLint. + +## What is ESLint? + +ESLint is a configurable JavaScript linter. It helps you find and fix problems in your JavaScript code. Problems can be anything from potential runtime bugs, to not following best practices, to styling issues. + +## Rules + +Rules are the core building block of ESLint. A rule validates if your code meets a certain expectation, and what to do if it does not meet that expectation. Rules can also contain additional configuration options specific to that rule. + +For example, the [`semi`](../rules/semi) rule lets you specify whether or not JavaScript statements should end with a semicolon (`;`). You can set the rule to either always require semicolons or require that a statement never ends with a semicolon. + +ESLint contains hundreds of built-in rules that you can use. You can also create custom rules or use rules that others have created with [plugins](#plugins). + +For more information, refer to [Rules](../rules/). + +## Configuration Files + +An ESLint configuration file is a place where you put the configuration for ESLint in your project. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. + +For more information, refer to [Configuration Files](./configure/configuration-files). + +## Shareable Configurations + +Shareable configurations are ESLint configurations that are shared via npm. + +Often shareable configurations are used to enforce style guides using ESLint's built-in rules. For example the sharable configuration [eslint-config-airbnb-base](https://www.npmjs.com/package/eslint-config-airbnb-base) implements the popular Airbnb JavaScript style guide. + +For more information, refer to [Using a shareable configuration package](./configure/configuration-files#using-a-shareable-configuration-package). + +## Plugins + +An ESLint plugin is an npm module that can contain a set of ESLint rules, configurations, processors, and environments. Often plugins include custom rules. Plugins can be used to enforce a style guide and support JavaScript extensions (like TypeScript), libraries (like React), and frameworks (Angular). + +A popular use case for plugins is to enforce best practices for a framework. For example, [@angular-eslint/eslint-plugin](https://www.npmjs.com/package/@angular-eslint/eslint-plugin) contains best practices for using the Angular framework. + +For more information, refer to [Configure Plugins](./configure/plugins). + +## Parsers + +An ESLint parser converts code into an abstract syntax tree that ESLint can evaluate. By default, ESLint uses the built-in [Espree](https://github.com/eslint/espree) parser, which is compatible with standard JavaScript runtimes and versions. + +Custom parsers let ESLint parse non-standard JavaScript syntax. Often custom parsers are included as part of shareable configurations or plugins, so you don't have to use them directly. + +For example, [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser) is a custom parser included in the [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint) project that lets ESLint parse TypeScript code. + +## Custom Processors + +An ESLint processor extracts JavaScript code from other kinds of files, then lets ESLint lint the JavaScript code. Alternatively, you can use a processor to manipulate JavaScript code before parsing it with ESLint. + +For example, [eslint-plugin-markdown](https://github.com/eslint/eslint-plugin-markdown) contains a custom processor that lets you lint JavaScript code inside of Markdown code blocks. + +## Formatters + +An ESLint formatter controls the appearance of the linting results in the CLI. + +For more information, refer to [Formatters](./formatters/). + +## Integrations + +One of the things that makes ESLint such a useful tool is the ecosystem of integrations that surrounds it. For example, many code editors have ESLint extensions that show you the ESLint results of your code in the file as you work so that you don't need to use the ESLint CLI to see linting results. + +For more information, refer to [Integrations](./integrations). + +## CLI & Node.js API + +The ESLint CLI is a command line interface that lets you execute linting from the terminal. The CLI has a variety of options that you can pass to its commands. + +The ESLint Node.js API lets you use ESLint programmatically from Node.js code. The API is useful when developing plugins, integrations, and other tools related to ESLint. + +Unless you are extending ESLint in some way, you should use the CLI. + +For more information, refer to [Command Line Interface](./command-line-interface) and [Node.js API](../integrate/nodejs-api). diff --git a/eslint/docs/src/user-guide/formatters/html-formatter-example.html b/eslint/docs/src/use/formatters/html-formatter-example.html similarity index 67% rename from eslint/docs/src/user-guide/formatters/html-formatter-example.html rename to eslint/docs/src/use/formatters/html-formatter-example.html index 9efd6e1..1bc28e1 100644 --- a/eslint/docs/src/user-guide/formatters/html-formatter-example.html +++ b/eslint/docs/src/use/formatters/html-formatter-example.html @@ -7,85 +7,110 @@ @@ -93,7 +118,7 @@

    ESLint Report

    - 9 problems (5 errors, 4 warnings) - Generated on Mon Sep 12 2022 01:24:39 GMT-0400 (Eastern Daylight Time) + 9 problems (5 errors, 4 warnings) - Generated on Fri May 19 2023 16:52:13 GMT-0400 (Eastern Daylight Time)
    @@ -104,7 +129,7 @@ 9 problems (5 errors, 4 warnings) - + @@ -113,7 +138,7 @@ - + @@ -122,61 +147,61 @@ - + - + - + - + - + - + - + diff --git a/eslint/docs/src/use/formatters/html-formatter-example.json b/eslint/docs/src/use/formatters/html-formatter-example.json new file mode 100644 index 0000000..6070c85 --- /dev/null +++ b/eslint/docs/src/use/formatters/html-formatter-example.json @@ -0,0 +1,3 @@ +{ + "layout": false +} \ No newline at end of file diff --git a/eslint/docs/src/use/formatters/index.md b/eslint/docs/src/use/formatters/index.md new file mode 100644 index 0000000..ec6ce53 --- /dev/null +++ b/eslint/docs/src/use/formatters/index.md @@ -0,0 +1,296 @@ +--- +title: Formatters Reference +eleventyNavigation: + key: formatters + parent: use eslint + title: Formatters Reference + order: 6 +edit_link: https://github.com/eslint/eslint/edit/main/templates/formatter-examples.md.ejs +--- + +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 in the CLI. For example, `--format json` uses the `json` formatter. + +The built-in formatter options are: + +* [checkstyle](#checkstyle) +* [compact](#compact) +* [html](#html) +* [jslint-xml](#jslint-xml) +* [json-with-metadata](#json-with-metadata) +* [json](#json) +* [junit](#junit) +* [stylish](#stylish) +* [tap](#tap) +* [unix](#unix) +* [visualstudio](#visualstudio) + +## Example Source + +Examples of each formatter were created from linting `fullOfProblems.js` using the `.eslintrc.json` configuration shown below. + +`fullOfProblems.js`: + +```js +function addOne(i) { + if (i != NaN) { + return i ++ + } else { + return + } +}; +``` + +`.eslintrc.json`: + +```json +{ + "extends": "eslint:recommended", + "rules": { + "consistent-return": 2, + "indent" : [1, 4], + "no-else-return" : 1, + "semi" : [1, "always"], + "space-unary-ops" : 2 + } +} +``` + +Tests the formatters with the CLI: + +```shell +npx eslint --format fullOfProblems.js +``` + +## Built-In Formatter Options + +### checkstyle + +Outputs results to the [Checkstyle](https://checkstyle.sourceforge.io/) format. + +Example output: + +```text + +``` + +### compact + +Human-readable output format. Mimics the default output of JSHint. + +Example output: + +```text +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 1, col 10, Error - 'addOne' is defined but never used. (no-unused-vars) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 2, col 9, Error - Use the isNaN function to compare with NaN. (use-isnan) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 3, col 16, Error - Unexpected space before unary operator '++'. (space-unary-ops) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 3, col 20, Warning - Missing semicolon. (semi) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 4, col 12, Warning - Unnecessary 'else' after 'return'. (no-else-return) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 5, col 1, Warning - Expected indentation of 8 spaces but found 6. (indent) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 5, col 7, Error - Function 'addOne' expected a return value. (consistent-return) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 5, col 13, Warning - Missing semicolon. (semi) +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 7, col 2, Error - Unnecessary semicolon. (no-extra-semi) + +9 problems +``` + +### html + +Outputs results to HTML. The `html` formatter is useful for visual presentation in the browser. + +Example output: + + + +### jslint-xml + +Outputs results to format compatible with the [JSLint Jenkins plugin](https://plugins.jenkins.io/jslint/). + +Example output: + +```text + +``` + +### json-with-metadata + +Outputs JSON-serialized results. The `json-with-metadata` provides the same linting results as the [`json`](#json) formatter with additional metadata about the rules applied. The linting results are included in the `results` property and the rules metadata is included in the `metadata` property. + +Alternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint. + +Example output: + +```text +{"results":[{"filePath":"/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'addOne' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":16},{"ruleId":"use-isnan","severity":2,"message":"Use the isNaN function to compare with NaN.","line":2,"column":9,"nodeType":"BinaryExpression","messageId":"comparisonWithNaN","endLine":2,"endColumn":17},{"ruleId":"space-unary-ops","severity":2,"message":"Unexpected space before unary operator '++'.","line":3,"column":16,"nodeType":"UpdateExpression","messageId":"unexpectedBefore","endLine":3,"endColumn":20,"fix":{"range":[57,58],"text":""}},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":3,"column":20,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":4,"endColumn":1,"fix":{"range":[60,60],"text":";"}},{"ruleId":"no-else-return","severity":1,"message":"Unnecessary 'else' after 'return'.","line":4,"column":12,"nodeType":"BlockStatement","messageId":"unexpected","endLine":6,"endColumn":6,"fix":{"range":[0,94],"text":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}"}},{"ruleId":"indent","severity":1,"message":"Expected indentation of 8 spaces but found 6.","line":5,"column":1,"nodeType":"Keyword","messageId":"wrongIndentation","endLine":5,"endColumn":7,"fix":{"range":[74,80],"text":" "}},{"ruleId":"consistent-return","severity":2,"message":"Function 'addOne' expected a return value.","line":5,"column":7,"nodeType":"ReturnStatement","messageId":"missingReturnValue","endLine":5,"endColumn":13},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":13,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":6,"endColumn":1,"fix":{"range":[86,86],"text":";"}},{"ruleId":"no-extra-semi","severity":2,"message":"Unnecessary semicolon.","line":7,"column":2,"nodeType":"EmptyStatement","messageId":"unexpected","endLine":7,"endColumn":3,"fix":{"range":[93,95],"text":"}"}}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":2,"fixableWarningCount":4,"source":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};"}],"metadata":{"rulesMeta":{"no-else-return":{"type":"suggestion","docs":{"description":"Disallow `else` blocks after `return` statements in `if` statements","recommended":false,"url":"https://eslint.org/docs/latest/rules/no-else-return"},"schema":[{"type":"object","properties":{"allowElseIf":{"type":"boolean","default":true}},"additionalProperties":false}],"fixable":"code","messages":{"unexpected":"Unnecessary 'else' after 'return'."}},"indent":{"type":"layout","docs":{"description":"Enforce consistent indentation","recommended":false,"url":"https://eslint.org/docs/latest/rules/indent"},"fixable":"whitespace","schema":[{"oneOf":[{"enum":["tab"]},{"type":"integer","minimum":0}]},{"type":"object","properties":{"SwitchCase":{"type":"integer","minimum":0,"default":0},"VariableDeclarator":{"oneOf":[{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},{"type":"object","properties":{"var":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"let":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"const":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false}]},"outerIIFEBody":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"MemberExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"FunctionDeclaration":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"FunctionExpression":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"StaticBlock":{"type":"object","properties":{"body":{"type":"integer","minimum":0}},"additionalProperties":false},"CallExpression":{"type":"object","properties":{"arguments":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false},"ArrayExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ObjectExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ImportDeclaration":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"flatTernaryExpressions":{"type":"boolean","default":false},"offsetTernaryExpressions":{"type":"boolean","default":false},"ignoredNodes":{"type":"array","items":{"type":"string","not":{"pattern":":exit$"}}},"ignoreComments":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"wrongIndentation":"Expected indentation of {{expected}} but found {{actual}}."}},"space-unary-ops":{"type":"layout","docs":{"description":"Enforce consistent spacing before or after unary operators","recommended":false,"url":"https://eslint.org/docs/latest/rules/space-unary-ops"},"fixable":"whitespace","schema":[{"type":"object","properties":{"words":{"type":"boolean","default":true},"nonwords":{"type":"boolean","default":false},"overrides":{"type":"object","additionalProperties":{"type":"boolean"}}},"additionalProperties":false}],"messages":{"unexpectedBefore":"Unexpected space before unary operator '{{operator}}'.","unexpectedAfter":"Unexpected space after unary operator '{{operator}}'.","unexpectedAfterWord":"Unexpected space after unary word operator '{{word}}'.","wordOperator":"Unary word operator '{{word}}' must be followed by whitespace.","operator":"Unary operator '{{operator}}' must be followed by whitespace.","beforeUnaryExpressions":"Space is required before unary expressions '{{token}}'."}},"semi":{"type":"layout","docs":{"description":"Require or disallow semicolons instead of ASI","recommended":false,"url":"https://eslint.org/docs/latest/rules/semi"},"fixable":"code","schema":{"anyOf":[{"type":"array","items":[{"enum":["never"]},{"type":"object","properties":{"beforeStatementContinuationChars":{"enum":["always","any","never"]}},"additionalProperties":false}],"minItems":0,"maxItems":2},{"type":"array","items":[{"enum":["always"]},{"type":"object","properties":{"omitLastInOneLineBlock":{"type":"boolean"},"omitLastInOneLineClassBody":{"type":"boolean"}},"additionalProperties":false}],"minItems":0,"maxItems":2}]},"messages":{"missingSemi":"Missing semicolon.","extraSemi":"Extra semicolon."}},"consistent-return":{"type":"suggestion","docs":{"description":"Require `return` statements to either always or never specify values","recommended":false,"url":"https://eslint.org/docs/latest/rules/consistent-return"},"schema":[{"type":"object","properties":{"treatUndefinedAsUnspecified":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"missingReturn":"Expected to return a value at the end of {{name}}.","missingReturnValue":"{{name}} expected a return value.","unexpectedReturnValue":"{{name}} expected no return value."}}}}} +``` + +### json + +Outputs JSON-serialized results. The `json` formatter is useful when you want to programmatically work with the CLI's linting results. + +Alternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint. + +Example output: + +```text +[{"filePath":"/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'addOne' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":16},{"ruleId":"use-isnan","severity":2,"message":"Use the isNaN function to compare with NaN.","line":2,"column":9,"nodeType":"BinaryExpression","messageId":"comparisonWithNaN","endLine":2,"endColumn":17},{"ruleId":"space-unary-ops","severity":2,"message":"Unexpected space before unary operator '++'.","line":3,"column":16,"nodeType":"UpdateExpression","messageId":"unexpectedBefore","endLine":3,"endColumn":20,"fix":{"range":[57,58],"text":""}},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":3,"column":20,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":4,"endColumn":1,"fix":{"range":[60,60],"text":";"}},{"ruleId":"no-else-return","severity":1,"message":"Unnecessary 'else' after 'return'.","line":4,"column":12,"nodeType":"BlockStatement","messageId":"unexpected","endLine":6,"endColumn":6,"fix":{"range":[0,94],"text":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}"}},{"ruleId":"indent","severity":1,"message":"Expected indentation of 8 spaces but found 6.","line":5,"column":1,"nodeType":"Keyword","messageId":"wrongIndentation","endLine":5,"endColumn":7,"fix":{"range":[74,80],"text":" "}},{"ruleId":"consistent-return","severity":2,"message":"Function 'addOne' expected a return value.","line":5,"column":7,"nodeType":"ReturnStatement","messageId":"missingReturnValue","endLine":5,"endColumn":13},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":13,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":6,"endColumn":1,"fix":{"range":[86,86],"text":";"}},{"ruleId":"no-extra-semi","severity":2,"message":"Unnecessary semicolon.","line":7,"column":2,"nodeType":"EmptyStatement","messageId":"unexpected","endLine":7,"endColumn":3,"fix":{"range":[93,95],"text":"}"}}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":2,"fixableWarningCount":4,"source":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};"}] +``` + +### junit + +Outputs results to format compatible with the [JUnit Jenkins plugin](https://plugins.jenkins.io/junit/). + +Example output: + +```text + + + + + + + + + + + + + + + +``` + +### stylish + +Human-readable output format. This is the default formatter. + +Example output: + +```text + +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js + 1:10 error 'addOne' is defined but never used no-unused-vars + 2:9 error Use the isNaN function to compare with NaN use-isnan + 3:16 error Unexpected space before unary operator '++' space-unary-ops + 3:20 warning Missing semicolon semi + 4:12 warning Unnecessary 'else' after 'return' no-else-return + 5:1 warning Expected indentation of 8 spaces but found 6 indent + 5:7 error Function 'addOne' expected a return value consistent-return + 5:13 warning Missing semicolon semi + 7:2 error Unnecessary semicolon no-extra-semi + +✖ 9 problems (5 errors, 4 warnings) + 2 errors and 4 warnings potentially fixable with the `--fix` option. + +``` + +### tap + +Outputs results to the [Test Anything Protocol (TAP)](https://testanything.org/) specification format. + +Example output: + +```text +TAP version 13 +1..1 +not ok 1 - /var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js + --- + message: '''addOne'' is defined but never used.' + severity: error + data: + line: 1 + column: 10 + ruleId: no-unused-vars + messages: + - message: Use the isNaN function to compare with NaN. + severity: error + data: + line: 2 + column: 9 + ruleId: use-isnan + - message: Unexpected space before unary operator '++'. + severity: error + data: + line: 3 + column: 16 + ruleId: space-unary-ops + - message: Missing semicolon. + severity: warning + data: + line: 3 + column: 20 + ruleId: semi + - message: Unnecessary 'else' after 'return'. + severity: warning + data: + line: 4 + column: 12 + ruleId: no-else-return + - message: Expected indentation of 8 spaces but found 6. + severity: warning + data: + line: 5 + column: 1 + ruleId: indent + - message: Function 'addOne' expected a return value. + severity: error + data: + line: 5 + column: 7 + ruleId: consistent-return + - message: Missing semicolon. + severity: warning + data: + line: 5 + column: 13 + ruleId: semi + - message: Unnecessary semicolon. + severity: error + data: + line: 7 + column: 2 + ruleId: no-extra-semi + ... + +``` + +### unix + +Outputs results to a format similar to many commands in UNIX-like systems. Parsable with tools such as [grep](https://www.gnu.org/software/grep/manual/grep.html), [sed](https://www.gnu.org/software/sed/manual/sed.html), and [awk](https://www.gnu.org/software/gawk/manual/gawk.html). + +Example output: + +```text +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:1:10: 'addOne' is defined but never used. [Error/no-unused-vars] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:2:9: Use the isNaN function to compare with NaN. [Error/use-isnan] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:3:16: Unexpected space before unary operator '++'. [Error/space-unary-ops] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:3:20: Missing semicolon. [Warning/semi] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:4:12: Unnecessary 'else' after 'return'. [Warning/no-else-return] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:5:1: Expected indentation of 8 spaces but found 6. [Warning/indent] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:5:7: Function 'addOne' expected a return value. [Error/consistent-return] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:5:13: Missing semicolon. [Warning/semi] +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:7:2: Unnecessary semicolon. [Error/no-extra-semi] + +9 problems +``` + +### visualstudio + +Outputs results to format compatible with the integrated terminal of the [Visual Studio](https://visualstudio.microsoft.com/) IDE. When using Visual Studio, you can click on the linting results in the integrated terminal to go to the issue in the source code. + +Example output: + +```text +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(1,10): error no-unused-vars : 'addOne' is defined but never used. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(2,9): error use-isnan : Use the isNaN function to compare with NaN. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(3,16): error space-unary-ops : Unexpected space before unary operator '++'. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(3,20): warning semi : Missing semicolon. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(4,12): warning no-else-return : Unnecessary 'else' after 'return'. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(5,1): warning indent : Expected indentation of 8 spaces but found 6. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(5,7): error consistent-return : Function 'addOne' expected a return value. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(5,13): warning semi : Missing semicolon. +/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(7,2): error no-extra-semi : Unnecessary semicolon. + +9 problems +``` diff --git a/eslint/docs/src/use/getting-started.md b/eslint/docs/src/use/getting-started.md new file mode 100644 index 0000000..623011a --- /dev/null +++ b/eslint/docs/src/use/getting-started.md @@ -0,0 +1,152 @@ +--- +title: Getting Started with ESLint +eleventyNavigation: + key: getting started + parent: use eslint + title: Getting Started + order: 1 + +--- + +ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code, with the goal of making code more consistent and avoiding bugs. + +ESLint is completely pluggable. Every single rule is a plugin and you can add more at runtime. You can also add community plugins, configurations, and parsers to extend the functionality of ESLint. + +## Prerequisites + +To use ESLint, you must have [Node.js](https://nodejs.org/en/) (`^12.22.0`, `^14.17.0`, or `>=16.0.0`) installed and built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.) + +## Quick start + +You can install and configure ESLint using this command: + +```shell +npm init @eslint/config +``` + +If you want to use a specific shareable config that is hosted on npm, you can use the `--config` option and specify the package name: + +```shell +# use `eslint-config-semistandard` shared config + +# npm 7+ +npm init @eslint/config -- --config semistandard + +# or (`eslint-config` prefix is optional) +npm init @eslint/config -- --config eslint-config-semistandard + +# ⚠️ npm 6.x no extra double-dash: +npm init @eslint/config --config semistandard + +``` + +The `--config` flag also supports passing in arrays: + +```shell +npm init @eslint/config -- --config semistandard,standard +# or +npm init @eslint/config -- --config semistandard --config standard +``` + +**Note:** `npm init @eslint/config` assumes you have a `package.json` file already. If you don't, make sure to run `npm init` or `yarn init` beforehand. + +After that, you can run ESLint on any file or directory like this: + +```shell +npx eslint yourfile.js + +# or + +yarn run eslint yourfile.js +``` + +## Configuration + +**Note:** If you are coming from a version before 1.0.0 please see the [migration guide](migrating-to-1.0.0). + +After running `npm init @eslint/config`, you'll have an `.eslintrc.{js,yml,json}` file in your directory. In it, you'll see some rules configured like this: + +```json +{ + "rules": { + "semi": ["error", "always"], + "quotes": ["error", "double"] + } +} +``` + +The names `"semi"` and `"quotes"` are the names of [rules](../rules) in ESLint. The first value is the error level of the rule and can be one of these values: + +* `"off"` or `0` - turn the rule off +* `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code) +* `"error"` or `2` - turn the rule on as an error (exit code will be 1) + +The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](configure/)). + +Your `.eslintrc.{js,yml,json}` configuration file will also include the line: + +```json +{ + "extends": "eslint:recommended" +} +``` + +Because of this line, all of the rules marked "(recommended)" on the [rules page](../rules) will be turned on. Alternatively, you can use configurations that others have created by searching for "eslint-config" on [npmjs.com](https://www.npmjs.com/search?q=eslint-config). ESLint will not lint your code unless you extend from a shared configuration or explicitly turn rules on in your configuration. + +## Global Install + +It is also possible to install ESLint globally, rather than locally, using `npm install eslint --global`. However, this is not recommended, and any plugins or shareable configs that you use must still be installed locally if you install ESLint globally. + +## Manual Set Up + +You can also manually set up ESLint in your project. + +Before you begin, you must already have a `package.json` file. If you don't, make sure to run `npm init` or `yarn init` to create the file beforehand. + +1. Install the ESLint package in your project: + + ```shell + npm install --save-dev eslint + ``` + +1. Add an `.eslintrc` file in one of the [supported configuration file formats](./configure/configuration-files#configuration-file-formats). + + ```shell + # Create JavaScript configuration file + touch .eslintrc.js + ``` + +1. Add configuration to the `.eslintrc` file. Refer to the [Configure ESLint documentation](configure/) to learn how to add rules, environments, custom configurations, plugins, and more. + + ```js + // .eslintrc.js example + module.exports = { + "env": { + "browser": true, + "es2021": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + } + ``` + +1. Lint code using the ESLint CLI: + + ```shell + npx eslint project-dir/ file1.js + ``` + + For more information on the available CLI options, refer to [Command Line Interface](./command-line-interface). + +--- + +## Next Steps + +* Learn about [advanced configuration](configure/) of ESLint. +* Get familiar with the [command line options](command-line-interface). +* Explore [ESLint integrations](integrations) into other tools like editors, build systems, and more. +* Can't find just the right rule? Make your own [custom rule](../extend/custom-rules). +* Make ESLint even better by [contributing](../contribute/). diff --git a/eslint/docs/src/user-guide/index.md b/eslint/docs/src/use/index.md similarity index 69% rename from eslint/docs/src/user-guide/index.md rename to eslint/docs/src/use/index.md index 3b26ee5..6e68a08 100644 --- a/eslint/docs/src/user-guide/index.md +++ b/eslint/docs/src/use/index.md @@ -1,33 +1,40 @@ --- -title: User Guide -layout: doc +title: Use ESLint in Your Project eleventyNavigation: - key: user guide - title: User Guide + key: use eslint + title: Use ESLint in Your Project order: 1 --- -This guide is intended for those who wish to use ESLint as an end-user. If you're looking for how to extend ESLint or work with the ESLint source code, please see the [Developer Guide](../developer-guide/). +This guide is intended for those who wish to use ESLint as an end-user. If you're looking for how to extend ESLint or work with the ESLint source code, please see the [Extend ESLint documentation](../extend/). ## [Getting Started](getting-started) Want to skip ahead and just start using ESLint? This section gives a high-level overview of installation, setup, and configuration options. -## [Rules](../rules/) +## [Core Concepts](core-concepts) -ESLint has a lot of rules that you can configure to fine-tune it to your project. This section is an exhaustive list of every rule and link to each rule's documentation. +Understand the main components of ESLint and how to use them in your project. -## [Configuring](configuring/) +## [Configure ESLint](configure/) Once you've got ESLint running, you'll probably want to adjust the configuration to better suit your project. This section explains all the different ways you can configure ESLint. -## [Command Line Interface](command-line-interface) +## [Command Line Interface Reference](command-line-interface) There are a lot of command line flags for ESLint and this section explains what they do. +## [Rules Reference](../rules/) + +ESLint has a lot of rules that you can configure to fine-tune it to your project. This section is an exhaustive list of every rule and link to each rule's documentation. + +## [Formatters Reference](formatters) + +Control the appearance of the linting results with formatters. View all built-in formatters on this page. + ## [Integrations](integrations) -Wondering if ESLint will work with your favorite editor or build system? This section has a list of all known integrations (submitted by their authors). +Wondering if ESLint will work with your favorite editor or build system? This page has a list of integrations (submitted by their authors). ## [Rule Deprecation](rule-deprecation) @@ -44,4 +51,4 @@ If you were using a prior version of ESLint, you can get help with the transitio * [migrating-to-5.0.0](migrating-to-5.0.0) * [migrating-to-6.0.0](migrating-to-6.0.0) * [migrating-to-7.0.0](migrating-to-7.0.0) -* [migrating-to-8.0.0](migrating-to-8.0.0) +* [migrate-to-8.0.0](migrate-to-8.0.0) diff --git a/eslint/docs/src/user-guide/integrations.md b/eslint/docs/src/use/integrations.md similarity index 57% rename from eslint/docs/src/user-guide/integrations.md rename to eslint/docs/src/use/integrations.md index 5d73484..427377e 100644 --- a/eslint/docs/src/user-guide/integrations.md +++ b/eslint/docs/src/use/integrations.md @@ -1,21 +1,24 @@ --- title: Integrations -layout: doc eleventyNavigation: key: integrations - parent: user guide + parent: use eslint title: Integrations - order: 6 + order: 7 --- +This page contains community projects that have integrated ESLint. The projects on this page are not maintained by the ESLint team. + +If you would like to recommend an integration to be added to this page, [submit a pull request](../contribute/pull-requests). + ## Editors * Sublime Text 3: - * [SublimeLinter-eslint](https://github.com/roadhump/SublimeLinter-eslint) + * [SublimeLinter-eslint](https://github.com/SublimeLinter/SublimeLinter-eslint) * [Build Next](https://github.com/albertosantini/sublimetext-buildnext) * Vim: - * [ALE](https://github.com/w0rp/ale) + * [ALE](https://github.com/dense-analysis/ale) * [Syntastic](https://github.com/vim-syntastic/syntastic/tree/master/syntax_checkers/javascript) * Emacs: [Flycheck](http://www.flycheck.org/) supports ESLint with the [javascript-eslint](http://www.flycheck.org/en/latest/languages.html#javascript) checker. * Eclipse Orion: ESLint is the [default linter](https://dev.eclipse.org/mhonarc/lists/orion-dev/msg02718.html) @@ -23,26 +26,16 @@ eleventyNavigation: * TextMate 2: * [eslint.tmbundle](https://github.com/ryanfitzer/eslint.tmbundle) * [javascript-eslint.tmbundle](https://github.com/natesilva/javascript-eslint.tmbundle) -* Atom: - * [linter-eslint](https://atom.io/packages/linter-eslint) - * [fast-eslint-8](https://atom.io/packages/fast-eslint-8) * IntelliJ IDEA, WebStorm, PhpStorm, PyCharm, RubyMine, and other JetBrains IDEs: [How to use ESLint](https://www.jetbrains.com/help/webstorm/eslint.html) +* Visual Studio: [Linting JavaScript in VS](https://learn.microsoft.com/en-us/visualstudio/javascript/linting-javascript?view=vs-2022) * Visual Studio Code: [ESLint Extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) * Brackets: Included and [Brackets ESLint](https://github.com/brackets-userland/brackets-eslint) ## Build tools * Grunt: [grunt-eslint](https://www.npmjs.com/package/grunt-eslint) -* Gulp: [gulp-eslint](https://www.npmjs.com/package/gulp-eslint) -* Mimosa: [mimosa-eslint](https://www.npmjs.com/package/mimosa-eslint) -* 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) -* 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) -* Brunch: [eslint-brunch](https://www.npmjs.com/package/eslint-brunch) ## Command Line Tools @@ -55,20 +48,7 @@ eleventyNavigation: * [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 - -* Mocha.js: [mocha-eslint](https://www.npmjs.com/package/mocha-eslint) - -## External ESLint rules - -* [AngularJS](https://github.com/Gillespie59/eslint-plugin-angular) -* [BackboneJS](https://github.com/ilyavolodin/eslint-plugin-backbone) -* [Jasmine](https://github.com/tlvince/eslint-plugin-jasmine) -* [React](https://github.com/yannickcr/eslint-plugin-react) - -… and many more published on npm with the [eslintplugin](https://www.npmjs.com/browse/keyword/eslintplugin) keyword. +* [Mega-Linter](https://megalinter.io/latest/): Linters aggregator for CI, [embedding eslint](https://megalinter.io/latest/descriptors/javascript_eslint/) ## Other Integration Lists diff --git a/eslint/docs/src/user-guide/migrating-to-8.0.0.md b/eslint/docs/src/use/migrate-to-8.0.0.md similarity index 85% rename from eslint/docs/src/user-guide/migrating-to-8.0.0.md rename to eslint/docs/src/use/migrate-to-8.0.0.md index 2b263e4..9fac8cd 100644 --- a/eslint/docs/src/user-guide/migrating-to-8.0.0.md +++ b/eslint/docs/src/use/migrate-to-8.0.0.md @@ -1,11 +1,10 @@ --- -title: Migrating to v8.0.0 -layout: doc +title: Migrate to v8.0.0 eleventyNavigation: - key: migrating to v8 - parent: user guide - title: Migrating to v8.x - order: 6 + key: migrate to v8 + parent: use eslint + title: Migrate to v8.x + order: 7 --- @@ -56,7 +55,7 @@ Node.js 10, 13, 15 all reached end of life either in 2020 or early 2021. ESLint 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. +**To address:** If you are using the `codeframe` or `table` formatters, you'll need to install the standalone [`eslint-formatter-codeframe`](https://github.com/eslint-community/eslint-formatter-codeframe) or [`eslint-formatter-table`](https://github.com/eslint-community/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) @@ -110,10 +109,10 @@ In ESLint v7.0.0, using both `--report-unused-disable-directives` and `--fix` on Four new rules have been enabled in the `eslint:recommended` preset. -* [`no-loss-of-precision`](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) +* [`no-loss-of-precision`](../rules/no-loss-of-precision) +* [`no-nonoctal-decimal-escape`](../rules/no-nonoctal-decimal-escape) +* [`no-unsafe-optional-chaining`](../rules/no-unsafe-optional-chaining) +* [`no-useless-backreference`](../rules/no-useless-backreference) **To address:** Fix errors or disable these rules. @@ -121,7 +120,7 @@ Four new rules have been enabled in the `eslint:recommended` preset. ## 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. +In ESLint v7.0.0, rules that [provided suggestions](../extend/custom-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: @@ -136,7 +135,7 @@ module.exports = { }; ``` -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`. +The [eslint-plugin/require-meta-has-suggestions](https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/main/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) @@ -165,11 +164,11 @@ module.exports = { }; ``` -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/require-meta-fixable](https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/main/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. +The [eslint-plugin/prefer-object-rule](https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/main/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. +See the [rule documentation](../extend/custom-rules) for more information on writing rules. **Related issue(s):** [#13349](https://github.com/eslint/eslint/issues/13349) @@ -179,7 +178,7 @@ Back in ESLint v4.0.0, we deprecated `SourceCode#getComments()`, but we neglecte 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). +**To address:** If your rule uses `SourceCode#getComments()`, please use [`SourceCode#getCommentsBefore()`, `SourceCode#getCommentsAfter()`, or `SourceCode#getCommentsInside()`](../extend/custom-rules#accessing-comments). **Related issue(s):** [#14744](https://github.com/eslint/eslint/issues/14744) @@ -234,7 +233,7 @@ In ESLint v8.0.0 (via Acorn v8.0.0), the key and value are now separate objects ## 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). +The `CLIEngine` class has been removed and replaced by the [`ESLint` class](../integrate/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: diff --git a/eslint/docs/src/user-guide/migrating-from-jscs.md b/eslint/docs/src/use/migrating-from-jscs.md similarity index 93% rename from eslint/docs/src/user-guide/migrating-from-jscs.md rename to eslint/docs/src/use/migrating-from-jscs.md index bcb0761..54b1968 100644 --- a/eslint/docs/src/user-guide/migrating-from-jscs.md +++ b/eslint/docs/src/use/migrating-from-jscs.md @@ -1,6 +1,5 @@ --- title: Migrating from JSCS -layout: doc --- @@ -11,7 +10,7 @@ In April 2016, we [announced](https://eslint.org/blog/2016/04/welcoming-jscs-to- Before beginning the process of migrating to ESLint, it's helpful to understand some of the terminology that ESLint uses and how it relates to terminology that JSCS uses. * **Configuration File** - In JSCS, the configuration file is `.jscsrc`, `.jscsrc.json`, `.jscsrc.yaml`, or `.jscsrs.js`. In ESLint, the configuration file can be `.eslintrc.json`, `.eslintrc.yml`, `.eslintrc.yaml`, or `.eslintrc.js` (there is also a deprecated `.eslintrc` file format). -* **Presets** - In JSCS, there were numerous predefined configurations shipped directly within JSCS. ESLint ships with just one predefined configuration (`eslint:recommended`) that has no style rules enabled. However, ESLint does support [shareable configs](https://eslint.org/docs/developer-guide/shareable-configs). Shareable configs are configurations that are published on their own to npm and there are shareable configs available for almost all of the JSCS presets (see [the "Converting Presets" section](#converting-presets) below). Additionally, the `preset` option in a configuration file is the equivalent of the ESLint `extends` option. +* **Presets** - In JSCS, there were numerous predefined configurations shipped directly within JSCS. ESLint ships with just one predefined configuration (`eslint:recommended`) that has no style rules enabled. However, ESLint does support [shareable configs](../extend/shareable-configs). Shareable configs are configurations that are published on their own to npm and there are shareable configs available for almost all of the JSCS presets (see [the "Converting Presets" section](#converting-presets) below). Additionally, the `preset` option in a configuration file is the equivalent of the ESLint `extends` option. ## Convert Configuration Files Using Polyjuice @@ -31,7 +30,7 @@ To convert your configuration file, pass in the location of your `.jscs.json` fi polyjuice --jscs .jscsrc.json > .eslintrc.json ``` -This creates a `.eslintrc.json` with the equivalent rules from `.jscsrc.json`. +This creates an `.eslintrc.json` with the equivalent rules from `.jscsrc.json`. If you have multiple `.jscsrc.json` files, you can pass them all and Polyjuice will combine them into one `.eslintrc.json` file: diff --git a/eslint/docs/src/user-guide/migrating-to-1.0.0.md b/eslint/docs/src/use/migrating-to-1.0.0.md similarity index 54% rename from eslint/docs/src/user-guide/migrating-to-1.0.0.md rename to eslint/docs/src/use/migrating-to-1.0.0.md index 70bdb5b..1ed8496 100644 --- a/eslint/docs/src/user-guide/migrating-to-1.0.0.md +++ b/eslint/docs/src/use/migrating-to-1.0.0.md @@ -1,6 +1,5 @@ --- title: Migrating to v1.0.0 -layout: doc --- @@ -24,62 +23,62 @@ This setting mimics some of the default behavior from 0.x, but not all. If you d The `"eslint:recommended"` configuration contains many of the same default rule settings from 0.x, but not all. These rules are no longer on by default, so you should review your settings to ensure they are still as you expect: -* [no-alert](https://eslint.org/docs/rules/no-alert) -* [no-array-constructor](https://eslint.org/docs/rules/no-array-constructor) -* [no-caller](https://eslint.org/docs/rules/no-caller) -* [no-catch-shadow](https://eslint.org/docs/rules/no-catch-shadow) -* [no-empty-label](https://eslint.org/docs/rules/no-empty-label) -* [no-eval](https://eslint.org/docs/rules/no-eval) -* [no-extend-native](https://eslint.org/docs/rules/no-extend-native) -* [no-extra-bind](https://eslint.org/docs/rules/no-extra-bind) -* [no-extra-strict](https://eslint.org/docs/rules/no-extra-strict) -* [no-implied-eval](https://eslint.org/docs/rules/no-implied-eval) -* [no-iterator](https://eslint.org/docs/rules/no-iterator) -* [no-label-var](https://eslint.org/docs/rules/no-label-var) -* [no-labels](https://eslint.org/docs/rules/no-labels) -* [no-lone-blocks](https://eslint.org/docs/rules/no-lone-blocks) -* [no-loop-func](https://eslint.org/docs/rules/no-loop-func) -* [no-multi-spaces](https://eslint.org/docs/rules/no-multi-spaces) -* [no-multi-str](https://eslint.org/docs/rules/no-multi-str) -* [no-native-reassign](https://eslint.org/docs/rules/no-native-reassign) -* [no-new](https://eslint.org/docs/rules/no-new) -* [no-new-func](https://eslint.org/docs/rules/no-new-func) -* [no-new-object](https://eslint.org/docs/rules/no-new-object) -* [no-new-wrappers](https://eslint.org/docs/rules/no-new-wrappers) -* [no-octal-escape](https://eslint.org/docs/rules/no-octal-escape) -* [no-process-exit](https://eslint.org/docs/rules/no-process-exit) -* [no-proto](https://eslint.org/docs/rules/no-proto) -* [no-return-assign](https://eslint.org/docs/rules/no-return-assign) -* [no-script-url](https://eslint.org/docs/rules/no-script-url) -* [no-sequences](https://eslint.org/docs/rules/no-sequences) -* [no-shadow](https://eslint.org/docs/rules/no-shadow) -* [no-shadow-restricted-names](https://eslint.org/docs/rules/no-shadow-restricted-names) -* [no-spaced-func](https://eslint.org/docs/rules/no-spaced-func) -* [no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces) -* [no-undef-init](https://eslint.org/docs/rules/no-undef-init) -* [no-underscore-dangle](https://eslint.org/docs/rules/no-underscore-dangle) -* [no-unused-expressions](https://eslint.org/docs/rules/no-unused-expressions) -* [no-use-before-define](https://eslint.org/docs/rules/no-use-before-define) -* [no-with](https://eslint.org/docs/rules/no-with) -* [no-wrap-func](https://eslint.org/docs/rules/no-wrap-func) -* [camelcase](https://eslint.org/docs/rules/camelcase) -* [comma-spacing](https://eslint.org/docs/rules/comma-spacing) -* [consistent-return](https://eslint.org/docs/rules/consistent-return) -* [curly](https://eslint.org/docs/rules/curly) -* [dot-notation](https://eslint.org/docs/rules/dot-notation) -* [eol-last](https://eslint.org/docs/rules/eol-last) -* [eqeqeq](https://eslint.org/docs/rules/eqeqeq) -* [key-spacing](https://eslint.org/docs/rules/key-spacing) -* [new-cap](https://eslint.org/docs/rules/new-cap) -* [new-parens](https://eslint.org/docs/rules/new-parens) -* [quotes](https://eslint.org/docs/rules/quotes) -* [semi](https://eslint.org/docs/rules/semi) -* [semi-spacing](https://eslint.org/docs/rules/semi-spacing) -* [space-infix-ops](https://eslint.org/docs/rules/space-infix-ops) -* [space-return-throw-case](https://eslint.org/docs/rules/space-return-throw-case) -* [space-unary-ops](https://eslint.org/docs/rules/space-unary-ops) -* [strict](https://eslint.org/docs/rules/strict) -* [yoda](https://eslint.org/docs/rules/yoda) +* [no-alert](../rules/no-alert) +* [no-array-constructor](../rules/no-array-constructor) +* [no-caller](../rules/no-caller) +* [no-catch-shadow](../rules/no-catch-shadow) +* [no-empty-label](../rules/no-empty-label) +* [no-eval](../rules/no-eval) +* [no-extend-native](../rules/no-extend-native) +* [no-extra-bind](../rules/no-extra-bind) +* [no-extra-strict](../rules/no-extra-strict) +* [no-implied-eval](../rules/no-implied-eval) +* [no-iterator](../rules/no-iterator) +* [no-label-var](../rules/no-label-var) +* [no-labels](../rules/no-labels) +* [no-lone-blocks](../rules/no-lone-blocks) +* [no-loop-func](../rules/no-loop-func) +* [no-multi-spaces](../rules/no-multi-spaces) +* [no-multi-str](../rules/no-multi-str) +* [no-native-reassign](../rules/no-native-reassign) +* [no-new](../rules/no-new) +* [no-new-func](../rules/no-new-func) +* [no-new-object](../rules/no-new-object) +* [no-new-wrappers](../rules/no-new-wrappers) +* [no-octal-escape](../rules/no-octal-escape) +* [no-process-exit](../rules/no-process-exit) +* [no-proto](../rules/no-proto) +* [no-return-assign](../rules/no-return-assign) +* [no-script-url](../rules/no-script-url) +* [no-sequences](../rules/no-sequences) +* [no-shadow](../rules/no-shadow) +* [no-shadow-restricted-names](../rules/no-shadow-restricted-names) +* [no-spaced-func](../rules/no-spaced-func) +* [no-trailing-spaces](../rules/no-trailing-spaces) +* [no-undef-init](../rules/no-undef-init) +* [no-underscore-dangle](../rules/no-underscore-dangle) +* [no-unused-expressions](../rules/no-unused-expressions) +* [no-use-before-define](../rules/no-use-before-define) +* [no-with](../rules/no-with) +* [no-wrap-func](../rules/no-wrap-func) +* [camelcase](../rules/camelcase) +* [comma-spacing](../rules/comma-spacing) +* [consistent-return](../rules/consistent-return) +* [curly](../rules/curly) +* [dot-notation](../rules/dot-notation) +* [eol-last](../rules/eol-last) +* [eqeqeq](../rules/eqeqeq) +* [key-spacing](../rules/key-spacing) +* [new-cap](../rules/new-cap) +* [new-parens](../rules/new-parens) +* [quotes](../rules/quotes) +* [semi](../rules/semi) +* [semi-spacing](../rules/semi-spacing) +* [space-infix-ops](../rules/space-infix-ops) +* [space-return-throw-case](../rules/space-return-throw-case) +* [space-unary-ops](../rules/space-unary-ops) +* [strict](../rules/strict) +* [yoda](../rules/yoda) See also: the [full diff](https://github.com/eslint/eslint/commit/e3e9dbd9876daf4bdeb4e15f8a76a9d5e6e03e39#diff-b01a5cfd9361ca9280a460fd6bb8edbbL1) where the defaults were changed. @@ -152,19 +151,19 @@ Here's a configuration file with the closest equivalent of the old defaults: Over the past several releases, we have been deprecating rules and introducing new rules to take their place. The following is a list of the removed rules and their replacements: -* [generator-star](https://eslint.org/docs/rules/generator-star) is replaced by [generator-star-spacing](https://eslint.org/docs/rules/generator-star-spacing) -* [global-strict](https://eslint.org/docs/rules/global-strict) is replaced by [strict](https://eslint.org/docs/rules/strict) -* [no-comma-dangle](https://eslint.org/docs/rules/no-comma-dangle) is replaced by [comma-dangle](https://eslint.org/docs/rules/comma-dangle) -* [no-empty-class](https://eslint.org/docs/rules/no-empty-class) is replaced by [no-empty-character-class](https://eslint.org/docs/rules/no-empty-character-class) -* [no-extra-strict](https://eslint.org/docs/rules/no-extra-strict) is replaced by [strict](https://eslint.org/docs/rules/strict) -* [no-reserved-keys](https://eslint.org/docs/rules/no-reserved-keys) is replaced by [quote-props](https://eslint.org/docs/rules/quote-props) -* [no-space-before-semi](https://eslint.org/docs/rules/no-space-before-semi) is replaced by [semi-spacing](https://eslint.org/docs/rules/semi-spacing) -* [no-wrap-func](https://eslint.org/docs/rules/no-wrap-func) is replaced by [no-extra-parens](https://eslint.org/docs/rules/no-extra-parens) -* [space-after-function-name](https://eslint.org/docs/rules/space-after-function-name) is replaced by [space-before-function-paren](https://eslint.org/docs/rules/space-before-function-paren) -* [space-before-function-parentheses](https://eslint.org/docs/rules/space-before-function-parentheses) is replaced by [space-before-function-paren](https://eslint.org/docs/rules/space-before-function-paren) -* [space-in-brackets](https://eslint.org/docs/rules/space-in-brackets) is replaced by[object-curly-spacing](https://eslint.org/docs/rules/object-curly-spacing) and [array-bracket-spacing](https://eslint.org/docs/rules/array-bracket-spacing) -* [space-unary-word-ops](https://eslint.org/docs/rules/space-unary-word-ops) is replaced by [space-unary-ops](https://eslint.org/docs/rules/space-unary-ops) -* [spaced-line-comment](https://eslint.org/docs/rules/spaced-line-comment) is replaced by [spaced-comment](https://eslint.org/docs/rules/spaced-comment) +* [generator-star](../rules/generator-star) is replaced by [generator-star-spacing](../rules/generator-star-spacing) +* [global-strict](../rules/global-strict) is replaced by [strict](../rules/strict) +* [no-comma-dangle](../rules/no-comma-dangle) is replaced by [comma-dangle](../rules/comma-dangle) +* [no-empty-class](../rules/no-empty-class) is replaced by [no-empty-character-class](../rules/no-empty-character-class) +* [no-extra-strict](../rules/no-extra-strict) is replaced by [strict](../rules/strict) +* [no-reserved-keys](../rules/no-reserved-keys) is replaced by [quote-props](../rules/quote-props) +* [no-space-before-semi](../rules/no-space-before-semi) is replaced by [semi-spacing](../rules/semi-spacing) +* [no-wrap-func](../rules/no-wrap-func) is replaced by [no-extra-parens](../rules/no-extra-parens) +* [space-after-function-name](../rules/space-after-function-name) is replaced by [space-before-function-paren](../rules/space-before-function-paren) +* [space-before-function-parentheses](../rules/space-before-function-parentheses) is replaced by [space-before-function-paren](../rules/space-before-function-paren) +* [space-in-brackets](../rules/space-in-brackets) is replaced by[object-curly-spacing](../rules/object-curly-spacing) and [array-bracket-spacing](../rules/array-bracket-spacing) +* [space-unary-word-ops](../rules/space-unary-word-ops) is replaced by [space-unary-ops](../rules/space-unary-ops) +* [spaced-line-comment](../rules/spaced-line-comment) is replaced by [spaced-comment](../rules/spaced-comment) **To address:** You'll need to update your rule configurations to use the new rules. ESLint v1.0.0 will also warn you when you're using a rule that has been removed and will suggest the replacement rules. Hopefully, this will result in few surprises during the upgrade process. diff --git a/eslint/docs/src/user-guide/migrating-to-2.0.0.md b/eslint/docs/src/use/migrating-to-2.0.0.md similarity index 88% rename from eslint/docs/src/user-guide/migrating-to-2.0.0.md rename to eslint/docs/src/use/migrating-to-2.0.0.md index 974e89f..753fed7 100644 --- a/eslint/docs/src/user-guide/migrating-to-2.0.0.md +++ b/eslint/docs/src/use/migrating-to-2.0.0.md @@ -1,6 +1,5 @@ --- title: Migrating to v2.0.0 -layout: doc --- @@ -55,11 +54,11 @@ module.exports = { The following rules have been deprecated with new rules created to take their place. The following is a list of the removed rules and their replacements: -* [no-arrow-condition](https://eslint.org/docs/rules/no-arrow-condition) is replaced by a combination of [no-confusing-arrow](https://eslint.org/docs/rules/no-confusing-arrow) and [no-constant-condition](https://eslint.org/docs/rules/no-constant-condition). Turn on both of these rules to get the same functionality as `no-arrow-condition`. -* [no-empty-label](https://eslint.org/docs/rules/no-empty-label) is replaced by [no-labels](https://eslint.org/docs/rules/no-labels) with `{"allowLoop": true, "allowSwitch": true}` option. -* [space-after-keywords](https://eslint.org/docs/rules/space-after-keywords) is replaced by [keyword-spacing](https://eslint.org/docs/rules/keyword-spacing). -* [space-before-keywords](https://eslint.org/docs/rules/space-before-keywords) is replaced by [keyword-spacing](https://eslint.org/docs/rules/keyword-spacing). -* [space-return-throw-case](https://eslint.org/docs/rules/space-return-throw-case) is replaced by [keyword-spacing](https://eslint.org/docs/rules/keyword-spacing). +* [no-arrow-condition](../rules/no-arrow-condition) is replaced by a combination of [no-confusing-arrow](../rules/no-confusing-arrow) and [no-constant-condition](../rules/no-constant-condition). Turn on both of these rules to get the same functionality as `no-arrow-condition`. +* [no-empty-label](../rules/no-empty-label) is replaced by [no-labels](../rules/no-labels) with `{"allowLoop": true, "allowSwitch": true}` option. +* [space-after-keywords](../rules/space-after-keywords) is replaced by [keyword-spacing](../rules/keyword-spacing). +* [space-before-keywords](../rules/space-before-keywords) is replaced by [keyword-spacing](../rules/keyword-spacing). +* [space-return-throw-case](../rules/space-return-throw-case) is replaced by [keyword-spacing](../rules/keyword-spacing). **To address:** You'll need to update your rule configurations to use the new rules. ESLint v2.0.0 will also warn you when you're using a rule that has been removed and will suggest the replacement rules. Hopefully, this will result in few surprises during the upgrade process. @@ -211,17 +210,17 @@ If you're not using `ecmaFeatures` in your configuration or your custom/plugin r In 2.0.0, the following 11 rules were added to `"eslint:recommended"`. -* [constructor-super](https://eslint.org/docs/rules/constructor-super) -* [no-case-declarations](https://eslint.org/docs/rules/no-case-declarations) -* [no-class-assign](https://eslint.org/docs/rules/no-class-assign) -* [no-const-assign](https://eslint.org/docs/rules/no-const-assign) -* [no-dupe-class-members](https://eslint.org/docs/rules/no-dupe-class-members) -* [no-empty-pattern](https://eslint.org/docs/rules/no-empty-pattern) -* [no-new-symbol](https://eslint.org/docs/rules/no-new-symbol) -* [no-self-assign](https://eslint.org/docs/rules/no-self-assign) -* [no-this-before-super](https://eslint.org/docs/rules/no-this-before-super) -* [no-unexpected-multiline](https://eslint.org/docs/rules/no-unexpected-multiline) -* [no-unused-labels](https://eslint.org/docs/rules/no-unused-labels) +* [constructor-super](../rules/constructor-super) +* [no-case-declarations](../rules/no-case-declarations) +* [no-class-assign](../rules/no-class-assign) +* [no-const-assign](../rules/no-const-assign) +* [no-dupe-class-members](../rules/no-dupe-class-members) +* [no-empty-pattern](../rules/no-empty-pattern) +* [no-new-symbol](../rules/no-new-symbol) +* [no-self-assign](../rules/no-self-assign) +* [no-this-before-super](../rules/no-this-before-super) +* [no-unexpected-multiline](../rules/no-unexpected-multiline) +* [no-unused-labels](../rules/no-unused-labels) **To address:** If you don't want to be notified by those rules, you can simply disable those rules. diff --git a/eslint/docs/src/user-guide/migrating-to-3.0.0.md b/eslint/docs/src/use/migrating-to-3.0.0.md similarity index 70% rename from eslint/docs/src/user-guide/migrating-to-3.0.0.md rename to eslint/docs/src/use/migrating-to-3.0.0.md index ab31a4c..289aa14 100644 --- a/eslint/docs/src/user-guide/migrating-to-3.0.0.md +++ b/eslint/docs/src/use/migrating-to-3.0.0.md @@ -1,6 +1,5 @@ --- title: Migrating to v3.0.0 -layout: doc --- @@ -16,7 +15,7 @@ With ESLint v3.0.0, we are dropping support for Node.js versions prior to 4. Nod ESLint v3.0.0 now requires that you use a configuration to run. A configuration can be any of the following: -1. A `.eslintrc.js`, `.eslintrc.json`, `.eslintrc.yml`, `.eslintrc.yaml`, or `.eslintrc` file either in your project or home directory. +1. An `.eslintrc.js`, `.eslintrc.json`, `.eslintrc.yml`, `.eslintrc.yaml`, or `.eslintrc` file either in your project or home directory. 2. Configuration options passed on the command line using `--rule` (or to CLIEngine using `rules`). 3. A configuration file passed on the command line using `-c` (or to CLIEngine using `configFile`). 4. A base configuration is provided to CLIEngine using the `baseConfig` option. @@ -39,17 +38,17 @@ To create a new configuration, use `eslint --init`. In 3.0.0, the following rules were added to `"eslint:recommended"`: -* [`no-unsafe-finally`](https://eslint.org/docs/rules/no-unsafe-finally) helps catch `finally` clauses that may not behave as you think. -* [`no-native-reassign`](https://eslint.org/docs/rules/no-native-reassign) was previously part of `no-undef`, but was split out because it didn't make sense as part of another rule. The `no-native-reassign` rule warns whenever you try to overwrite a read-only global variable. -* [`require-yield`](https://eslint.org/docs/rules/require-yield) helps to identify generator functions that do not have the `yield` keyword. +* [`no-unsafe-finally`](../rules/no-unsafe-finally) helps catch `finally` clauses that may not behave as you think. +* [`no-native-reassign`](../rules/no-native-reassign) was previously part of `no-undef`, but was split out because it didn't make sense as part of another rule. The `no-native-reassign` rule warns whenever you try to overwrite a read-only global variable. +* [`require-yield`](../rules/require-yield) helps to identify generator functions that do not have the `yield` keyword. The following rules were removed from `"eslint:recommended"`: -* [`comma-dangle`](https://eslint.org/docs/rules/comma-dangle) used to be recommended because Internet Explorer 8 and earlier threw a syntax error when it found a dangling comma on object literal properties. However, [Internet Explorer 8 was end-of-lifed](https://www.microsoft.com/en-us/WindowsForBusiness/End-of-IE-support) in January 2016 and all other active browsers allow dangling commas. As such, we consider dangling commas to now be a stylistic issue instead of a possible error. +* [`comma-dangle`](../rules/comma-dangle) used to be recommended because Internet Explorer 8 and earlier threw a syntax error when it found a dangling comma on object literal properties. However, [Internet Explorer 8 was end-of-lifed](https://www.microsoft.com/en-us/WindowsForBusiness/End-of-IE-support) in January 2016 and all other active browsers allow dangling commas. As such, we consider dangling commas to now be a stylistic issue instead of a possible error. The following rules were modified: -* [`complexity`](https://eslint.org/docs/rules/complexity) used to have a hardcoded default of 11 in `eslint:recommended` that would be used if you turned the rule on without specifying a maximum. The default is now 20. The rule actually always had a default of 20, but `eslint:recommended` was overriding it by mistake. +* [`complexity`](../rules/complexity) used to have a hardcoded default of 11 in `eslint:recommended` that would be used if you turned the rule on without specifying a maximum. The default is now 20. The rule actually always had a default of 20, but `eslint:recommended` was overriding it by mistake. **To address:** If you want to mimic how `eslint:recommended` worked in v2.x, you can use the following: diff --git a/eslint/docs/src/user-guide/migrating-to-4.0.0.md b/eslint/docs/src/use/migrating-to-4.0.0.md similarity index 88% rename from eslint/docs/src/user-guide/migrating-to-4.0.0.md rename to eslint/docs/src/use/migrating-to-4.0.0.md index e96b8ac..81051a7 100644 --- a/eslint/docs/src/user-guide/migrating-to-4.0.0.md +++ b/eslint/docs/src/use/migrating-to-4.0.0.md @@ -1,6 +1,5 @@ --- title: Migrating to v4.0.0 -layout: doc --- @@ -36,10 +35,10 @@ The lists below are ordered roughly by the number of users each change is expect ## `eslint:recommended` changes -Two new rules have been added to the [`eslint:recommended`](https://eslint.org/docs/user-guide/configuring#using-eslintrecommended) config: +Two new rules have been added to the [`eslint:recommended`](configure#using-eslintrecommended) config: -* [`no-compare-neg-zero`](/docs/rules/no-compare-neg-zero) disallows comparisons to `-0` -* [`no-useless-escape`](/docs/rules/no-useless-escape) disallows uselessly-escaped characters in strings and regular expressions +* [`no-compare-neg-zero`](../rules/no-compare-neg-zero) disallows comparisons to `-0` +* [`no-useless-escape`](../rules/no-useless-escape) disallows uselessly-escaped characters in strings and regular expressions **To address:** To mimic the `eslint:recommended` behavior from 3.x, you can disable these rules in a config file: @@ -56,11 +55,11 @@ Two new rules have been added to the [`eslint:recommended`](https://eslint.org/d ## The `indent` rule is more strict -Previously, the [`indent`](/docs/rules/indent) rule was fairly lenient about checking indentation; there were many code patterns where indentation was not validated by the rule. This caused confusion for users, because they were accidentally writing code with incorrect indentation, and they expected ESLint to catch the issues. +Previously, the [`indent`](../rules/indent) rule was fairly lenient about checking indentation; there were many code patterns where indentation was not validated by the rule. This caused confusion for users, because they were accidentally writing code with incorrect indentation, and they expected ESLint to catch the issues. In 4.0.0, the `indent` rule has been rewritten. The new version of the rule will report some indentation errors that the old version of the rule did not catch. Additionally, the indentation of `MemberExpression` nodes, function parameters, and function arguments will now be checked by default (it was previously ignored by default for backwards compatibility). -To make the upgrade process easier, we've introduced the [`indent-legacy`](/docs/rules/indent-legacy) rule as a snapshot of the `indent` rule from 3.x. If you run into issues from the `indent` rule when you upgrade, you should be able to use the `indent-legacy` rule to replicate the 3.x behavior. However, the `indent-legacy` rule is deprecated and will not receive bugfixes or improvements in the future, so you should eventually switch back to the `indent` rule. +To make the upgrade process easier, we've introduced the [`indent-legacy`](../rules/indent-legacy) rule as a snapshot of the `indent` rule from 3.x. If you run into issues from the `indent` rule when you upgrade, you should be able to use the `indent-legacy` rule to replicate the 3.x behavior. However, the `indent-legacy` rule is deprecated and will not receive bugfixes or improvements in the future, so you should eventually switch back to the `indent` rule. **To address:** We recommend upgrading without changing your `indent` configuration, and fixing any new indentation errors that appear in your codebase. However, if you want to mimic how the `indent` rule worked in 3.x, you can update your configuration: @@ -87,13 +86,13 @@ Due to a bug, glob patterns in an `.eslintignore` file were previously resolved ## The `padded-blocks` rule is more strict by default -By default, the [`padded-blocks`](/docs/rules/padded-blocks) rule will now enforce padding in class bodies and switch statements. Previously, the rule would ignore these cases unless the user opted into enforcing them. +By default, the [`padded-blocks`](../rules/padded-blocks) rule will now enforce padding in class bodies and switch statements. Previously, the rule would ignore these cases unless the user opted into enforcing them. **To address:** If this change results in more linting errors in your codebase, you should fix them or reconfigure the rule. ## The `space-before-function-paren` rule is more strict by default -By default, the [`space-before-function-paren`](/docs/rules/space-before-function-paren) rule will now enforce spacing for async arrow functions. Previously, the rule would ignore these cases unless the user opted into enforcing them. +By default, the [`space-before-function-paren`](../rules/space-before-function-paren) rule will now enforce spacing for async arrow functions. Previously, the rule would ignore these cases unless the user opted into enforcing them. **To address:** To mimic the default config from 3.x, you can use: @@ -111,7 +110,7 @@ By default, the [`space-before-function-paren`](/docs/rules/space-before-functio ## The `no-multi-spaces` rule is more strict by default -By default, the [`no-multi-spaces`](/docs/rules/no-multi-spaces) rule will now disallow multiple spaces before comments at the end of a line. Previously, the rule did not check this case. +By default, the [`no-multi-spaces`](../rules/no-multi-spaces) rule will now disallow multiple spaces before comments at the end of a line. Previously, the rule did not check this case. **To address:** To mimic the default config from 3.x, you can use: diff --git a/eslint/docs/src/user-guide/migrating-to-5.0.0.md b/eslint/docs/src/use/migrating-to-5.0.0.md similarity index 92% rename from eslint/docs/src/user-guide/migrating-to-5.0.0.md rename to eslint/docs/src/use/migrating-to-5.0.0.md index 36db7d4..96c54ec 100644 --- a/eslint/docs/src/user-guide/migrating-to-5.0.0.md +++ b/eslint/docs/src/use/migrating-to-5.0.0.md @@ -1,6 +1,5 @@ --- title: Migrating to v5.0.0 -layout: doc --- @@ -51,10 +50,10 @@ As of April 30th, 2018, Node.js 4 will be at EOL and will no longer be receiving ## `eslint:recommended` changes -Two new rules have been added to the [`eslint:recommended`](https://eslint.org/docs/user-guide/configuring#using-eslintrecommended) config: +Two new rules have been added to the [`eslint:recommended`](configure/configuration-files#using-eslintrecommended) config: -* [`for-direction`](/docs/rules/for-direction) enforces that a `for` loop update clause moves the counter in the right direction. -* [`getter-return`](/docs/rules/getter-return) enforces that a `return` statement is present in property getters. +* [`for-direction`](../rules/for-direction) enforces that a `for` loop update clause moves the counter in the right direction. +* [`getter-return`](../rules/getter-return) enforces that a `return` statement is present in property getters. **To address:** To mimic the `eslint:recommended` behavior from 4.x, you can disable these rules in a config file: @@ -97,7 +96,7 @@ Note that this also enables parsing for other features from ES2018, such as [asy For compatibility, ESLint v5 will treat `ecmaFeatures: { experimentalObjectRestSpread: true }` as an alias for `ecmaVersion: 2018` when the former is found in a config file. As a result, if you use object rest/spread, your code should still parse successfully with ESLint v5. However, note that this alias will be removed in ESLint v6. -**To address:** If you use the `experimentalObjectRestSpread` option, you should be able to upgrade to ESLint v5 without any changes, but you will encounter a deprecation warning. To avoid the warning, use `ecmaVersion: 2018` in your config file rather than `ecmaFeatures: { experimentalObjectRestSpread: true }`. If you would like to disallow the use of other ES2018 features, consider using rules such as [`no-restricted-syntax`](/docs/rules/no-restricted-syntax). +**To address:** If you use the `experimentalObjectRestSpread` option, you should be able to upgrade to ESLint v5 without any changes, but you will encounter a deprecation warning. To avoid the warning, use `ecmaVersion: 2018` in your config file rather than `ecmaFeatures: { experimentalObjectRestSpread: true }`. If you would like to disallow the use of other ES2018 features, consider using rules such as [`no-restricted-syntax`](../rules/no-restricted-syntax). ## Linting nonexistent files from the command line is now a fatal error @@ -114,7 +113,7 @@ ESLint v5 will report a fatal error when either of the following conditions is m * A file provided on the command line does not exist * A glob or folder provided on the command line does not match any lintable files -Note that this also affects the [`CLIEngine.executeOnFiles()`](https://eslint.org/docs/developer-guide/nodejs-api#cliengineexecuteonfiles) API. +Note that this also affects the [`CLIEngine.executeOnFiles()`](../integrate/nodejs-api#cliengineexecuteonfiles) API. **To address:** If you encounter an error about missing files after upgrading to ESLint v5, you may want to double-check that there are no typos in the paths you provide to ESLint. To make the error go away, you can simply remove the given files or globs from the list of arguments provided to ESLint on the command line. @@ -122,8 +121,8 @@ If you use a boilerplate generator that relies on this behavior (e.g. to generat ## The default options for some rules have changed -* The default options for the [`object-curly-newline`](/docs/rules/object-curly-newline) rule have changed from `{ multiline: true }` to `{ consistent: true }`. -* The default options object for the [`no-self-assign`](/docs/rules/no-self-assign) rule has changed from `{ props: false }` to `{ props: true }`. +* The default options for the [`object-curly-newline`](../rules/object-curly-newline) rule have changed from `{ multiline: true }` to `{ consistent: true }`. +* The default options object for the [`no-self-assign`](../rules/no-self-assign) rule has changed from `{ props: false }` to `{ props: true }`. **To address:** To restore the rule behavior from ESLint v4, you can update your config file to include the previous options: @@ -138,7 +137,7 @@ If you use a boilerplate generator that relies on this behavior (e.g. to generat ## Deprecated globals have been removed from the `node`, `browser`, and `jest` environments -Some global variables have been deprecated or removed for code running in Node.js, browsers, and Jest. (For example, browsers used to expose an `SVGAltGlyphElement` global variable to JavaScript code, but this global has been removed from web standards and is no longer present in browsers.) As a result, we have removed these globals from the corresponding `eslint` environments, so use of these globals will trigger an error when using rules such as [`no-undef`](/docs/rules/no-undef). +Some global variables have been deprecated or removed for code running in Node.js, browsers, and Jest. (For example, browsers used to expose an `SVGAltGlyphElement` global variable to JavaScript code, but this global has been removed from web standards and is no longer present in browsers.) As a result, we have removed these globals from the corresponding `eslint` environments, so use of these globals will trigger an error when using rules such as [`no-undef`](../rules/no-undef). **To address:** If you use deprecated globals in the `node`, `browser`, or `jest` environments, you can add a `globals` section to your configuration to re-enable any globals you need. For example: @@ -159,7 +158,7 @@ ESLint v4 had a special behavior when linting files that only contain whitespace ESLint v5 treats whitespace-only files the same way as all other files: it parses them and runs enabled rules on them as appropriate. This could lead to additional linting problems if you use a custom rule that reports errors on empty files. -**To address:** If you have an empty file in your project and you don't want it to be linted, consider adding it to an [`.eslintignore` file](/docs/user-guide/configuring#ignoring-files-and-directories). +**To address:** If you have an empty file in your project and you don't want it to be linted, consider adding it to an [`.eslintignore` file](configure/ignore). If you have a custom rule, you should make sure it handles empty files appropriately. (In most cases, no changes should be necessary.) @@ -222,7 +221,7 @@ Previously, the `context.getScope()` method changed its behavior based on the `p Additionally, `context.getScope()` incorrectly returned the parent scope of the proper scope on `CatchClause` (in ES5), `ForStatement` (in ≧ES2015), `ForInStatement` (in ≧ES2015), `ForOfStatement`, and `WithStatement` nodes. -In ESLint v5, the `context.getScope()` method has the same behavior regardless of `parserOptions.ecmaVersion` and returns the proper scope. See [the documentation](../developer-guide/working-with-rules#contextgetscope) for more details on which scopes are returned. +In ESLint v5, the `context.getScope()` method has the same behavior regardless of `parserOptions.ecmaVersion` and returns the proper scope. See [the documentation](../extend/scope-manager-interface) for more details on which scopes are returned. **To address:** If you have written a custom rule that uses the `context.getScope()` method in node handlers, you may need to update it to account for the modified scope information. @@ -230,7 +229,7 @@ In ESLint v5, the `context.getScope()` method has the same behavior regardless o Previously, rule context objects had an undocumented `_linter` property, which was used internally within ESLint to process reports from rules. Some rules used this property to achieve functionality that was not intended to be possible for rules. For example, several plugins used the `_linter` property in a rule to monitor reports from other rules, for the purpose of checking for unused `/* eslint-disable */` directive comments. Although this functionality was useful for users, it could also cause stability problems for projects using ESLint. For example, an upgrade to a rule in one plugin could unexpectedly cause a rule in another plugin to start reporting errors. -The `_linter` property has been removed in ESLint v5.0, so it is no longer possible to implement rules with this functionality. However, the [`--report-unused-disable-directives`](/docs/user-guide/command-line-interface#--report-unused-disable-directives) CLI flag can be used to flag unused directive comments. +The `_linter` property has been removed in ESLint v5.0, so it is no longer possible to implement rules with this functionality. However, the [`--report-unused-disable-directives`](command-line-interface#--report-unused-disable-directives) CLI flag can be used to flag unused directive comments. ## `RuleTester` now uses strict equality checks in its assertions @@ -269,6 +268,6 @@ In ESLint v5, an unsuccessful linting run due to a fatal error will result in an ## The `eslint.linter` property is now non-enumerable -When using ESLint's Node.js API, the [`linter`](/docs/developer-guide/nodejs-api#linter-1) property is now non-enumerable. Note that the `linter` property was deprecated in ESLint v4 in favor of the [`Linter`](/docs/developer-guide/nodejs-api#linter) property. +When using ESLint's Node.js API, the [`linter`](../integrate/nodejs-api#linter-1) property is now non-enumerable. Note that the `linter` property was deprecated in ESLint v4 in favor of the [`Linter`](../integrate/nodejs-api#linter) property. **To address:** If you rely on enumerating all the properties of the `eslint` object, use something like `Object.getOwnPropertyNames` to ensure that non-enumerable keys are captured. diff --git a/eslint/docs/src/user-guide/migrating-to-6.0.0.md b/eslint/docs/src/use/migrating-to-6.0.0.md similarity index 83% rename from eslint/docs/src/user-guide/migrating-to-6.0.0.md rename to eslint/docs/src/use/migrating-to-6.0.0.md index 84c74e4..0d2595e 100644 --- a/eslint/docs/src/user-guide/migrating-to-6.0.0.md +++ b/eslint/docs/src/use/migrating-to-6.0.0.md @@ -1,6 +1,5 @@ --- title: Migrating to v6.0.0 -layout: doc --- @@ -52,19 +51,19 @@ As of April 2019, Node.js 6 will be at EOL and will no longer be receiving secur ## `eslint:recommended` has been updated -The following rules have been added to the [`eslint:recommended`](https://eslint.org/docs/user-guide/configuring#using-eslintrecommended) config: +The following rules have been added to the [`eslint:recommended`](../use/configure#using-eslintrecommended) config: -* [`no-async-promise-executor`](https://eslint.org/docs/rules/no-async-promise-executor) disallows using an `async` function as the argument to the `Promise` constructor, which is usually a bug. -* [`no-misleading-character-class`](https://eslint.org/docs/rules/no-misleading-character-class) reports character classes in regular expressions that might not behave as expected. -* [`no-prototype-builtins`](https://eslint.org/docs/rules/no-prototype-builtins) reports method calls like `foo.hasOwnProperty("bar")` (which are a frequent source of bugs), and suggests that they be replaced with `Object.prototype.hasOwnProperty.call(foo, "bar")` instead. -* [`no-shadow-restricted-names`](https://eslint.org/docs/rules/no-shadow-restricted-names) disallows shadowing variables like `undefined` (e.g. with code like `let undefined = 5;`), since is likely to confuse readers. -* [`no-useless-catch`](https://eslint.org/docs/rules/no-useless-catch) reports `catch` clauses that are redundant and can be removed from the code without changing its behavior. -* [`no-with`](https://eslint.org/docs/rules/no-with) disallows use of the [`with` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with), which can make code difficult to understand and cause compatibility problems. -* [`require-atomic-updates`](https://eslint.org/docs/rules/require-atomic-updates) reports race condition bugs that can occur when reassigning variables in async functions. +* [`no-async-promise-executor`](../rules/no-async-promise-executor) disallows using an `async` function as the argument to the `Promise` constructor, which is usually a bug. +* [`no-misleading-character-class`](../rules/no-misleading-character-class) reports character classes in regular expressions that might not behave as expected. +* [`no-prototype-builtins`](../rules/no-prototype-builtins) reports method calls like `foo.hasOwnProperty("bar")` (which are a frequent source of bugs), and suggests that they be replaced with `Object.prototype.hasOwnProperty.call(foo, "bar")` instead. +* [`no-shadow-restricted-names`](../rules/no-shadow-restricted-names) disallows shadowing variables like `undefined` (e.g. with code like `let undefined = 5;`), since is likely to confuse readers. +* [`no-useless-catch`](../rules/no-useless-catch) reports `catch` clauses that are redundant and can be removed from the code without changing its behavior. +* [`no-with`](../rules/no-with) disallows use of the [`with` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with), which can make code difficult to understand and cause compatibility problems. +* [`require-atomic-updates`](../rules/require-atomic-updates) reports race condition bugs that can occur when reassigning variables in async functions. Additionally, the following rule has been *removed* from `eslint:recommended`: -* [`no-console`](https://eslint.org/docs/rules/no-console) disallows calling functions like `console.log`. While this rule is useful in many cases (e.g. to avoid inadvertently leaving debugging statements in production code), it is not as broadly applicable as the other rules in `eslint:recommended`, and it was a source of false positives in cases where `console.log` is acceptable (e.g. in CLI applications). +* [`no-console`](../rules/no-console) disallows calling functions like `console.log`. While this rule is useful in many cases (e.g. to avoid inadvertently leaving debugging statements in production code), it is not as broadly applicable as the other rules in `eslint:recommended`, and it was a source of false positives in cases where `console.log` is acceptable (e.g. in CLI applications). Finally, in ESLint v5 `eslint:recommended` would explicitly disable all core rules that were not considered "recommended". This could cause confusing behavior if `eslint:recommended` was loaded after another config, since `eslint:recommended` would have the effect of turning off some rules. In ESLint v6, `eslint:recommended` has no effect on non-recommended rules. @@ -134,7 +133,7 @@ config | ESLint v5 | ESLint v6 ## The `no-redeclare` rule is now more strict by default -The default options for the [`no-redeclare`](https://eslint.org/docs/rules/no-redeclare) rule have changed from `{ builtinGlobals: false }` to `{ builtinGlobals: true }`. Additionally, the `no-redeclare` rule will now report an error for globals enabled by comments like `/* global foo */` if those globals were already enabled through configuration anyway. +The default options for the [`no-redeclare`](../rules/no-redeclare) rule have changed from `{ builtinGlobals: false }` to `{ builtinGlobals: true }`. Additionally, the `no-redeclare` rule will now report an error for globals enabled by comments like `/* global foo */` if those globals were already enabled through configuration anyway. **To address:** @@ -154,7 +153,7 @@ Additionally, if you see new errors for `global` comments in your code, you shou ## The `comma-dangle` rule is now more strict by default -Previously, the [`comma-dangle`](https://eslint.org/docs/rules/comma-dangle) rule would ignore trailing function arguments and parameters, unless explicitly configured to check for function commas. In ESLint v6, function commas are treated the same way as other types of trailing commas. +Previously, the [`comma-dangle`](../rules/comma-dangle) rule would ignore trailing function arguments and parameters, unless explicitly configured to check for function commas. In ESLint v6, function commas are treated the same way as other types of trailing commas. **To address:** You can restore the previous default behavior of the rule with: @@ -178,7 +177,7 @@ To restore the previous behavior of a string option like `"always-multiline"`, r ## The `no-confusing-arrow` rule is now more lenient by default -The default options for the [`no-confusing-arrow`](https://eslint.org/docs/rules/no-confusing-arrow) rule have changed from `{ allowParens: false }` to `{ allowParens: true }`. +The default options for the [`no-confusing-arrow`](../rules/no-confusing-arrow) rule have changed from `{ allowParens: false }` to `{ allowParens: true }`. **To address:** You can restore the previous default behavior of the rule with: @@ -196,7 +195,7 @@ The default options for the [`no-confusing-arrow`](https://eslint.org/docs/rules Due to a bug, the glob patterns in a `files` list in an `overrides` section of a config file would never match dotfiles, making it impossible to have overrides apply to files starting with a dot. This bug has been fixed in ESLint v6. -**To address:** If you don't want dotfiles to be matched by an override, consider adding something like `excludedFiles: [".*"]` to that `overrides` section. See the [documentation](https://eslint.org/docs/user-guide/configuring#configuration-based-on-glob-patterns) for more details. +**To address:** If you don't want dotfiles to be matched by an override, consider adding something like `excludedFiles: [".*"]` to that `overrides` section. See the [documentation](../use/configure#configuration-based-on-glob-patterns) for more details. **Related issue(s):** [eslint/eslint#11201](https://github.com/eslint/eslint/issues/11201) @@ -283,7 +282,7 @@ If you're not sure which config file needs to be updated, it may be useful to ru ## User-provided regular expressions in rule options are parsed with the unicode flag -Rules like [`max-len`](/docs/rules/max-len) accept a string option which is interpreted as a regular expression. In ESLint v6.0.0, these regular expressions are interpreted with the [unicode flag](https://mathiasbynens.be/notes/es6-unicode-regex), which should exhibit more reasonable behavior when matching characters like astral symbols. Unicode regexes also validate escape sequences more strictly than non-unicode regexes. +Rules like [`max-len`](../rules/max-len) accept a string option which is interpreted as a regular expression. In ESLint v6.0.0, these regular expressions are interpreted with the [unicode flag](https://mathiasbynens.be/notes/es6-unicode-regex), which should exhibit more reasonable behavior when matching characters like astral symbols. Unicode regexes also validate escape sequences more strictly than non-unicode regexes. **To address:** If you get rule option validation errors after upgrading, ensure that any regular expressions in your rule options have no invalid escape sequences. @@ -329,6 +328,6 @@ Previously, when linting code with a parser that had not been previously defined In ESLint v6, `Linter` will no longer perform any filesystem operations, including loading parsers. -**To address:** If you're using `Linter` with a custom parser, use [`Linter#defineParser`](https://eslint.org/docs/developer-guide/nodejs-api#linterdefineparser) to explicitly define the parser before linting any code. +**To address:** If you're using `Linter` with a custom parser, use [`Linter#defineParser`](../integrate/nodejs-api#linterdefineparser) to explicitly define the parser before linting any code. **Related issue(s):** [eslint/rfcs#7](https://github.com/eslint/rfcs/pull/7) diff --git a/eslint/docs/src/user-guide/migrating-to-7.0.0.md b/eslint/docs/src/use/migrating-to-7.0.0.md similarity index 77% rename from eslint/docs/src/user-guide/migrating-to-7.0.0.md rename to eslint/docs/src/use/migrating-to-7.0.0.md index 9e2a702..86b81cf 100644 --- a/eslint/docs/src/user-guide/migrating-to-7.0.0.md +++ b/eslint/docs/src/use/migrating-to-7.0.0.md @@ -1,6 +1,5 @@ --- title: Migrating to v7.0.0 -layout: doc --- @@ -109,7 +108,7 @@ Personal config files have been deprecated since [v6.7.0](https://eslint.org/blo 1. When a project does not have a configuration file present and ESLint loads configuration from `~/.eslintrc.*`. 1. When a project has a configuration file and ESLint ignored a `~/.eslintrc.*` configuration file. This occurs when the `$HOME` directory is an ancestor directory of the project and the project's configuration files doesn't contain `root:true`. -**To address:** Remove `~/.eslintrc.*` configuration files and add a `.eslintrc.*` configuration file to your project. Alternatively, use the `--config` option to use shared config files. +**To address:** Remove `~/.eslintrc.*` configuration files and add an `.eslintrc.*` configuration file to your project. Alternatively, use the `--config` option to use shared config files. **Related issue(s):** [RFC32](https://github.com/eslint/rfcs/tree/master/designs/2019-deprecating-personal-config/README.md), [#12678](https://github.com/eslint/eslint/pull/12678) @@ -146,22 +145,22 @@ To allow for the colocation of comments that provide context with the directive, ## Node.js/CommonJS rules have been deprecated -The ten Node.js/CommonJS rules in core have been deprecated and moved to the [eslint-plugin-node](https://github.com/mysticatea/eslint-plugin-node) plugin. +The ten Node.js/CommonJS rules in core have been deprecated and moved to the [eslint-plugin-node](https://github.com/mysticatea/eslint-plugin-node) plugin (for ESLint v8.0.0 and later, use the maintained [eslint-plugin-n](https://github.com/eslint-community/eslint-plugin-n) fork instead) . -**To address:** As per [our deprecation policy](https://eslint.org/docs/user-guide/rule-deprecation), the deprecated rules will remain in core for the foreseeable future and are still available for use. However, we will no longer be updating or fixing any bugs in those rules. To use a supported version of the rules, we recommend using the corresponding rules in the plugin instead. +**To address:** As per [our deprecation policy](../use/rule-deprecation), the deprecated rules will remain in core for the foreseeable future and are still available for use. However, we will no longer be updating or fixing any bugs in those rules. To use a supported version of the rules, we recommend using the corresponding rules in the plugin instead. | Deprecated Rules | Replacement | | :--------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ | -| [callback-return](https://eslint.org/docs/rules/callback-return) | [node/callback-return](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/callback-return.md) | -| [global-require](https://eslint.org/docs/rules/global-require) | [node/global-require](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/global-require.md) | -| [handle-callback-err](https://eslint.org/docs/rules/handle-callback-err) | [node/handle-callback-err](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/handle-callback-err.md) | -| [no-mixed-requires](https://eslint.org/docs/rules/no-mixed-requires) | [node/no-mixed-requires](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-mixed-requires.md) | -| [no-new-require](https://eslint.org/docs/rules/no-new-require) | [node/no-new-require](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-new-require.md) | -| [no-path-concat](https://eslint.org/docs/rules/no-path-concat) | [node/no-path-concat](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-path-concat.md) | -| [no-process-env](https://eslint.org/docs/rules/no-process-env) | [node/no-process-env](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-process-env.md) | -| [no-process-exit](https://eslint.org/docs/rules/no-process-exit) | [node/no-process-exit](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-process-exit.md) | -| [no-restricted-modules](https://eslint.org/docs/rules/no-restricted-modules) | [node/no-restricted-require](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-restricted-require.md) | -| [no-sync](https://eslint.org/docs/rules/no-sync) | [node/no-sync](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-sync.md) | +| [callback-return](../rules/callback-return) | [node/callback-return](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/callback-return.md) | +| [global-require](../rules/global-require) | [node/global-require](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/global-require.md) | +| [handle-callback-err](../rules/handle-callback-err) | [node/handle-callback-err](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/handle-callback-err.md) | +| [no-mixed-requires](../rules/no-mixed-requires) | [node/no-mixed-requires](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-mixed-requires.md) | +| [no-new-require](../rules/no-new-require) | [node/no-new-require](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-new-require.md) | +| [no-path-concat](../rules/no-path-concat) | [node/no-path-concat](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-path-concat.md) | +| [no-process-env](../rules/no-process-env) | [node/no-process-env](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-process-env.md) | +| [no-process-exit](../rules/no-process-exit) | [node/no-process-exit](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-process-exit.md) | +| [no-restricted-modules](../rules/no-restricted-modules) | [node/no-restricted-require](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-restricted-require.md) | +| [no-sync](../rules/no-sync) | [node/no-sync](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-sync.md) | **Related issue(s):** [#12898](https://github.com/eslint/eslint/pull/12898) @@ -169,16 +168,16 @@ The ten Node.js/CommonJS rules in core have been deprecated and moved to the [es Several rules have been enhanced and now report additional errors: -* [accessor-pairs](https://eslint.org/docs/rules/accessor-pairs) rule now recognizes class members by default. -* [array-callback-return](https://eslint.org/docs/rules/array-callback-return) rule now recognizes `flatMap` method. -* [computed-property-spacing](https://eslint.org/docs/rules/computed-property-spacing) rule now recognizes class members by default. -* [func-names](https://eslint.org/docs/rules/func-names) rule now recognizes function declarations in default exports. -* [no-extra-parens](https://eslint.org/docs/rules/no-extra-parens) rule now recognizes parentheses in assignment targets. -* [no-dupe-class-members](https://eslint.org/docs/rules/no-dupe-class-members) rule now recognizes computed keys for static class members. -* [no-magic-numbers](https://eslint.org/docs/rules/no-magic-numbers) rule now recognizes bigint literals. -* [radix](https://eslint.org/docs/rules/radix) rule now recognizes invalid numbers for the second parameter of `parseInt()`. -* [use-isnan](https://eslint.org/docs/rules/use-isnan) rule now recognizes class members by default. -* [yoda](https://eslint.org/docs/rules/yoda) rule now recognizes bigint literals. +* [accessor-pairs](../rules/accessor-pairs) rule now recognizes class members by default. +* [array-callback-return](../rules/array-callback-return) rule now recognizes `flatMap` method. +* [computed-property-spacing](../rules/computed-property-spacing) rule now recognizes class members by default. +* [func-names](../rules/func-names) rule now recognizes function declarations in default exports. +* [no-extra-parens](../rules/no-extra-parens) rule now recognizes parentheses in assignment targets. +* [no-dupe-class-members](../rules/no-dupe-class-members) rule now recognizes computed keys for static class members. +* [no-magic-numbers](../rules/no-magic-numbers) rule now recognizes bigint literals. +* [radix](../rules/radix) rule now recognizes invalid numbers for the second parameter of `parseInt()`. +* [use-isnan](../rules/use-isnan) rule now recognizes class members by default. +* [yoda](../rules/yoda) rule now recognizes bigint literals. **To address:** Fix errors or disable these rules. @@ -188,9 +187,9 @@ Several rules have been enhanced and now report additional errors: Three new rules have been enabled in the `eslint:recommended` preset. -* [no-dupe-else-if](https://eslint.org/docs/rules/no-dupe-else-if) -* [no-import-assign](https://eslint.org/docs/rules/no-import-assign) -* [no-setter-return](https://eslint.org/docs/rules/no-setter-return) +* [no-dupe-else-if](../rules/no-dupe-else-if) +* [no-import-assign](../rules/no-import-assign) +* [no-setter-return](../rules/no-setter-return) **To address:** Fix errors or disable these rules. @@ -210,7 +209,7 @@ The `RuleTester` now validates the following: ## The `CLIEngine` class has been deprecated -The [`CLIEngine` class](https://eslint.org/docs/developer-guide/nodejs-api#cliengine) has been deprecated and replaced by the new [`ESLint` class](https://eslint.org/docs/developer-guide/nodejs-api#eslint-class). +The [`CLIEngine` class](../integrate/nodejs-api#cliengine) has been deprecated and replaced by the new [`ESLint` class](../integrate/nodejs-api#eslint-class). The `CLIEngine` class provides a synchronous API that is blocking the implementation of features such as parallel linting, supporting ES modules in shareable configs/parsers/plugins/formatters, and adding the ability to visually display the progress of linting runs. The new `ESLint` class provides an asynchronous API that ESLint core will now using going forward. `CLIEngine` will remain in core for the foreseeable future but may be removed in a future major version. diff --git a/eslint/docs/src/user-guide/rule-deprecation.md b/eslint/docs/src/use/rule-deprecation.md similarity index 99% rename from eslint/docs/src/user-guide/rule-deprecation.md rename to eslint/docs/src/use/rule-deprecation.md index 7766b55..bd15405 100644 --- a/eslint/docs/src/user-guide/rule-deprecation.md +++ b/eslint/docs/src/use/rule-deprecation.md @@ -1,6 +1,5 @@ --- title: Rule Deprecation -layout: doc --- diff --git a/eslint/docs/src/user-guide/command-line-interface.md b/eslint/docs/src/user-guide/command-line-interface.md deleted file mode 100644 index c8ddefe..0000000 --- a/eslint/docs/src/user-guide/command-line-interface.md +++ /dev/null @@ -1,585 +0,0 @@ ---- -title: Command Line Interface -layout: doc -eleventyNavigation: - key: command line interface - parent: user guide - title: Command Line Interface - order: 3 - ---- - -ESLint requires Node.js for installation. Follow the instructions in the [Getting Started Guide](getting-started) to install ESLint. - -Most users use [`npx`](https://docs.npmjs.com/cli/v8/commands/npx) to run ESLint on the command line like this: - -```shell -npx eslint [options] [file|dir|glob]* -``` - -Such as: - -```shell -# Run on two files -npx eslint file1.js file2.js - -# Run on multiple files -npx eslint lib/** -``` - -Please note that when passing a glob as a parameter, it will be expanded by your shell. The results of the expansion can vary depending on your shell, and its configuration. If you want to use node `glob` syntax, you have to quote your parameter (using double quotes if you need it to run in Windows), as follows: - -```shell -npx eslint "lib/**" -``` - -**Note:** You can also use alternative package managers such as [Yarn](https://yarnpkg.com/) or [pnpm](https://pnpm.io/) to run ESLint. Please refer to your package manager's documentation for the correct syntax. - -## Options - -The command line utility has several options. You can view the options by running `npx eslint -h`. - -```text -eslint [options] file.js [file.js] [dir] - -Basic configuration: - --no-eslintrc Disable use of configuration from .eslintrc.* - -c, --config path::String Use this configuration, overriding .eslintrc.* config options if present - --env [String] Specify environments - --ext [String] Specify JavaScript file extensions - --global [String] Define global variables - --parser String Specify the parser to be used - --parser-options Object Specify parser options - --resolve-plugins-relative-to path::String A folder where plugins should be resolved from, CWD by default - -Specifying rules and plugins: - --plugin [String] Specify plugins - --rule Object Specify rules - --rulesdir [path::String] Load additional rules from this directory. Deprecated: Use rules from 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 (directive, problem, suggestion, layout) - -Ignoring files: - --ignore-path path::String Specify path of ignore file - --no-ignore Disable use of ignore files and patterns - --ignore-pattern [String] Pattern of files to ignore (in addition to those in .eslintignore) - -Using stdin: - --stdin Lint code provided on - default: false - --stdin-filename String Specify filename to process STDIN as - -Handling warnings: - --quiet Report errors only - default: false - --max-warnings Int Number of warnings to trigger nonzero exit code - default: -1 - -Output: - -o, --output-file path::String Specify file to write report to - -f, --format String Use a specific output format - default: stylish - --color, --no-color Force enabling/disabling of color - -Inline configuration comments: - --no-inline-config Prevent comments from changing config or rules - --report-unused-disable-directives Adds reported errors for unused eslint-disable directives - -Caching: - --cache Only check changed files - default: false - --cache-file path::String Path to the cache file. Deprecated: use --cache-location - default: .eslintcache - --cache-location path::String Path to the cache file or directory - --cache-strategy String Strategy to use for detecting changed files in the cache - either: metadata or content - default: metadata - -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 - --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 - --print-config path::String Print the configuration for the given file -``` - -Options that accept array values can be specified by repeating the option or with a comma-delimited list (other than `--ignore-pattern` which does not allow the second style). - -Example: - -```shell -npx eslint --ext .jsx --ext .js lib/ - -npx eslint --ext .jsx,.js lib/ -``` - -### Basic configuration - -#### `--no-eslintrc` - -Disables use of configuration from `.eslintrc.*` and `package.json` files. - -Example: - -```shell -npx eslint --no-eslintrc file.js -``` - -#### `-c`, `--config` - -This option allows you to specify an additional configuration file for ESLint (see [Configuring ESLint](configuring/) for more). - -Example: - -```shell -npx eslint -c ~/my-eslint.json file.js -``` - -This example uses the configuration file at `~/my-eslint.json`. - -If `.eslintrc.*` and/or `package.json` files are also used for configuration (i.e., `--no-eslintrc` was not specified), the configurations will be merged. Options from this configuration file have precedence over the options from `.eslintrc.*` and `package.json` files. - -#### `--env` - -This option enables specific environments. Details about the global variables defined by each environment are available on the [Specifying Environments](configuring/language-options#specifying-environments) documentation. This option only enables environments; it does not disable environments set in other configuration files. To specify multiple environments, separate them using commas, or use the option multiple times. - -Examples: - -```shell -npx eslint --env browser,node file.js -npx eslint --env browser --env node file.js -``` - -#### `--ext` - -This option allows you to specify which file extensions ESLint will use when searching for target files in the directories you specify. -By default, ESLint lints `*.js` files and the files that match the `overrides` entries of your configuration. - -Examples: - -```shell -# Use only .ts extension -npx eslint . --ext .ts - -# Use both .js and .ts -npx eslint . --ext .js --ext .ts - -# Also use both .js and .ts -npx eslint . --ext .js,.ts -``` - -**Note:** `--ext` is only used when the arguments are directories. If you use glob patterns or file names, then `--ext` is ignored. - -For example, `npx eslint lib/* --ext .js` will match all files within the `lib/` directory, regardless of extension. - -#### `--global` - -This option defines global variables so that they will not be flagged as undefined by the `no-undef` rule. Any specified global variables are assumed to be read-only by default, but appending `:true` to a variable's name ensures that `no-undef` will also allow writes. To specify multiple global variables, separate them using commas, or use the option multiple times. - -Examples: - -```shell -npx eslint --global require,exports:true file.js -npx eslint --global require --global exports:true -``` - -#### `--parser` - -This option allows you to specify a parser to be used by ESLint. By default, `espree` will be used. - -#### `--parser-options` - -This option allows you to specify parser options to be used by ESLint. Note that the available parser options are determined by the parser being used. - -Examples: - -```shell -echo '3 ** 4' | npx eslint --stdin --parser-options=ecmaVersion:6 # will fail with a parsing error -echo '3 ** 4' | npx eslint --stdin --parser-options=ecmaVersion:7 # succeeds, yay! -``` - -#### `--resolve-plugins-relative-to` - -Changes the folder where plugins are resolved from. By default, plugins are resolved from the current working directory. This option should be used when plugins were installed by someone other than the end user. It should be set to the project directory of the project that has a dependency on the necessary plugins. For example: - -* When using a config file that is located outside of the current project (with the `--config` flag), if the config uses plugins which are installed locally to itself, `--resolve-plugins-relative-to` should be set to the directory containing the config file. -* If an integration has dependencies on ESLint and a set of plugins, and the tool invokes ESLint on behalf of the user with a preset configuration, the tool should set `--resolve-plugins-relative-to` to the top-level directory of the tool. - -### Specifying rules and plugins - -#### `--plugin` - -This option specifies a plugin to load. You can omit the prefix `eslint-plugin-` from the plugin name. - -Before using the plugin, you have to install it using npm. - -Examples: - -```shell -npx eslint --plugin jquery file.js -npx eslint --plugin eslint-plugin-mocha file.js -``` - -#### `--rule` - -This option specifies rules to be used. These rules will be merged with any rules specified with configuration files. (You can use `--no-eslintrc` to change that behavior.) To define multiple rules, separate them using commas, or use the option multiple times. The [levn](https://github.com/gkz/levn#levn--) format is used for specifying the rules. - -If the rule is defined within a plugin, you have to prefix the rule ID with the plugin name and a `/`. - -Examples: - -```shell -npx eslint --rule 'quotes: [error, double]' -npx eslint --rule 'guard-for-in: error' --rule 'brace-style: [error, 1tbs]' -npx eslint --rule 'jquery/dollar-sign: error' -``` - -#### `--rulesdir` - -**Deprecated**: Use rules from plugins instead. - -This option allows you to specify another directory from which to load rules files. This allows you to dynamically load new rules at run time. This is useful when you have custom rules that aren't suitable for being bundled with ESLint. - -Example: - -```shell -npx eslint --rulesdir my-rules/ file.js -``` - -The rules in your custom rules directory must follow the same format as bundled rules to work properly. You can also specify multiple locations for custom rules by including multiple `--rulesdir` options: - -```shell -npx eslint --rulesdir my-rules/ --rulesdir my-other-rules/ file.js -``` - -Note that, as with core rules and plugin rules, you still need to enable the rules in configuration or via the `--rule` CLI option in order to actually run those rules during linting. Specifying a rules directory with `--rulesdir` does not automatically enable the rules within that directory. - -### Fixing problems - -#### `--fix` - -This option instructs ESLint to try to fix as many issues as possible. The fixes are made to the actual files themselves and only the remaining unfixed issues are output. Not all problems are fixable using this option, and the option does not work in these situations: - -1. This option throws an error when code is piped to ESLint. -1. This option has no effect on code that uses a processor, unless the processor opts into allowing autofixes. - -If you want to fix code from `stdin` or otherwise want to get the fixes without actually writing them to the file, use the [`--fix-dry-run`](#--fix-dry-run) option. - -#### `--fix-dry-run` - -This option has the same effect as `--fix` with one difference: the fixes are not saved to the file system. This makes it possible to fix code from `stdin` (when used with the `--stdin` flag). - -Because the default formatter does not output the fixed code, you'll have to use another one (e.g. `json`) to get the fixes. Here's an example of this pattern: - -```shell -getSomeText | npx eslint --stdin --fix-dry-run --format=json -``` - -This flag can be useful for integrations (e.g. editor plugins) which need to autofix text from the command line without saving it to the filesystem. - -#### `--fix-type` - -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: - -```shell -npx eslint --fix --fix-type suggestion . -npx eslint --fix --fix-type suggestion --fix-type problem . -npx eslint --fix --fix-type suggestion,layout . -``` - -This option is helpful if you are using another program to format your code but you would still like ESLint to apply other types of fixes. - -### Ignoring files - -#### `--ignore-path` - -This option allows you to specify the file to use as your `.eslintignore`. By default, ESLint looks in the current working directory for `.eslintignore`. You can override this behavior by providing a path to a different file. - -Example: - -```shell -npx eslint --ignore-path tmp/.eslintignore file.js -npx eslint --ignore-path .gitignore file.js -``` - -#### `--no-ignore` - -Disables excluding of files from `.eslintignore`, `--ignore-path`, `--ignore-pattern`, and `ignorePatterns` property in config files. - -Example: - -```shell -npx eslint --no-ignore file.js -``` - -#### `--ignore-pattern` - -This option allows you to specify patterns of files to ignore (in addition to those in `.eslintignore`). You can repeat the option to provide multiple patterns. The supported syntax is the same as for `.eslintignore` [files](configuring/ignoring-code#the-eslintignore-file), which use the same patterns as the `.gitignore` [specification](https://git-scm.com/docs/gitignore). You should quote your patterns in order to avoid shell interpretation of glob patterns. - -Example: - -```shell -npx eslint --ignore-pattern '/lib/' --ignore-pattern '/src/vendor/*' . -``` - -### Using stdin - -#### `--stdin` - -This option tells ESLint to read and lint source code from STDIN instead of from files. You can use this to pipe code to ESLint. - -Example: - -```shell -cat myfile.js | npx eslint --stdin -``` - -#### `--stdin-filename` - -This option allows you to specify a filename to process STDIN as. This is useful when processing files from STDIN and you have rules which depend on the filename. - -Example - -```shell -cat myfile.js | npx eslint --stdin --stdin-filename=myfile.js -``` - -### Handling warnings - -#### `--quiet` - -This option allows you to disable reporting on warnings. If you enable this option, only errors are reported by ESLint. - -Example: - -```shell -npx eslint --quiet file.js -``` - -#### `--max-warnings` - -This option allows you to specify a warning threshold, which can be used to force ESLint to exit with an error status if there are too many warning-level rule violations in your project. - -Normally, if ESLint runs and finds no errors (only warnings), it will exit with a success exit status. However, if `--max-warnings` is specified and the total warning count is greater than the specified threshold, ESLint will exit with an error status. Specifying a threshold of `-1` or omitting this option will prevent this behavior. - -Example: - -```shell -npx eslint --max-warnings 10 file.js -``` - -### Output - -#### `-o`, `--output-file` - -Enable report to be written to a file. - -Example: - -```shell -npx eslint -o ./test/test.html -``` - -When specified, the given format is output into the provided file name. - -#### `-f`, `--format` - -This option specifies the output format for the console. Possible formats are: - -* [checkstyle](formatters/#checkstyle) -* [compact](formatters/#compact) -* [html](formatters/#html) -* [jslint-xml](formatters/#jslint-xml) -* [json](formatters/#json) -* [junit](formatters/#junit) -* [stylish](formatters/#stylish) (the default) -* [tap](formatters/#tap) -* [unix](formatters/#unix) -* [visualstudio](formatters/#visualstudio) - -Example: - -```shell -npx eslint -f compact file.js -``` - -You can also use a custom formatter from the command line by specifying a path to the custom formatter file. - -Example: - -```shell -npx eslint -f ./customformat.js file.js -``` - -An npm-installed formatter is resolved with or without `eslint-formatter-` prefix. - -Example: - -```shell -npm install eslint-formatter-pretty - -npx eslint -f pretty file.js - -// equivalent: -npx eslint -f eslint-formatter-pretty file.js -``` - -When specified, the given format is output to the console. If you'd like to save that output into a file, you can do so on the command line like so: - -```shell -npx eslint -f compact file.js > results.txt -``` - -This saves the output into the `results.txt` file. - -#### `--color`, `--no-color` - -This option forces the enabling/disabling of colorized output. You can use this to override the default behavior, which is to enable colorized output unless no TTY is detected, such as when piping `eslint` through `cat` or `less`. - -Examples: - -```shell -npx eslint --color file.js | cat -npx eslint --no-color file.js -``` - -### Inline configuration comments - -#### `--no-inline-config` - -This option prevents inline comments like `/*eslint-disable*/` or -`/*global foo*/` from having any effect. This allows you to set an ESLint -config without files modifying it. All inline config comments are ignored, e.g.: - -* `/*eslint-disable*/` -* `/*eslint-enable*/` -* `/*global*/` -* `/*eslint*/` -* `/*eslint-env*/` -* `// eslint-disable-line` -* `// eslint-disable-next-line` - -Example: - -```shell -npx eslint --no-inline-config file.js -``` - -#### `--report-unused-disable-directives` - -This option causes ESLint to report directive comments like `// eslint-disable-line` when no errors would have been reported on that line anyway. This can be useful to prevent future errors from unexpectedly being suppressed, by cleaning up old `eslint-disable` comments which are no longer applicable. - -**Warning**: When using this option, it is possible that new errors will start being reported whenever ESLint or custom rules are upgraded. For example, suppose a rule has a bug that causes it to report a false positive, and an `eslint-disable` comment is added to suppress the incorrect report. If the bug is then fixed in a patch release of ESLint, the `eslint-disable` comment will become unused since ESLint is no longer generating an incorrect report. This will result in a new reported error for the unused directive if the `report-unused-disable-directives` option is used. - -Example: - -```shell -npx eslint --report-unused-disable-directives file.js -``` - -### Caching - -#### `--cache` - -Store the info about processed files in order to only operate on the changed ones. The cache is stored in `.eslintcache` by default. Enabling this option can dramatically improve ESLint's running time by ensuring that only changed files are linted. - -**Note:** If you run ESLint with `--cache` and then run ESLint without `--cache`, the `.eslintcache` file will be deleted. This is necessary because the results of the lint might change and make `.eslintcache` invalid. If you want to control when the cache file is deleted, then use `--cache-location` to specify an alternate location for the cache file. - -**Note:** Autofixed files are not placed in the cache. Subsequent linting that does not trigger an autofix will place it in the cache. - -#### `--cache-file` - -Path to the cache file. If none specified `.eslintcache` will be used. The file will be created in the directory where the `eslint` command is executed. **Deprecated**: Use `--cache-location` instead. - -#### `--cache-location` - -Path to the cache location. Can be a file or a directory. If no location is specified, `.eslintcache` will be used. In that case, the file will be created in the directory where the `eslint` command is executed. - -If a directory is specified, a cache file will be created inside the specified folder. The name of the file will be based on the hash of the current working directory (CWD). e.g.: `.cache_hashOfCWD` - -**Important note:** If the directory for the cache does not exist make sure you add a trailing `/` on \*nix systems or `\` in windows. Otherwise the path will be assumed to be a file. - -Example: - -```shell -npx eslint "src/**/*.js" --cache --cache-location "/Users/user/.eslintcache/" -``` - -#### `--cache-strategy` - -Strategy for the cache to use for detecting changed files. Can be either `metadata` or `content`. If no strategy is specified, `metadata` will be used. - -The `content` strategy can be useful in cases where the modification time of your files change even if their contents have not. For example, this can happen during git operations like git clone because git does not track file modification time. - -Example: - -```shell -npx eslint "src/**/*.js" --cache --cache-strategy content -``` - -### Miscellaneous - -#### `--init` - -This option will run `npm init @eslint/config` to start config initialization wizard. It's designed to help new users quickly create .eslintrc file by answering a few questions, choosing a popular style guide. - -The resulting configuration file will be created in the current directory. - -#### `--env-info` - -This option outputs information about the execution environment, including the version of Node, npm, and local and global installations of ESLint. The ESLint team may ask for this information to help solve bugs. - -#### `--no-error-on-unmatched-pattern` - -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. -Add this flag to an ESLint command line invocation in order to get extra debug information as the command is run (e.g. `npx eslint --debug test.js` and `npx eslint test.js --debug` are equivalent) - -#### `-h`, `--help` - -This option outputs the help menu, displaying all of the available options. All other options are ignored when this is present. - -#### `-v`, `--version` - -This option outputs the current ESLint version onto the console. All other options are ignored when this is present. - -#### `--print-config` - -This option outputs the configuration to be used for the file passed. When present, no linting is performed and only config-related options are valid. - -Example: - -```shell -npx eslint --print-config file.js -``` - -## Ignoring files from linting - -ESLint supports `.eslintignore` files to exclude files from the linting process when ESLint operates on a directory. Files given as individual CLI arguments will be exempt from exclusion. The `.eslintignore` file is a plain text file containing one pattern per line. It can be located in any of the target directory's ancestors; it will affect files in its containing directory as well as all sub-directories. Here's a simple example of a `.eslintignore` file: - -```text -temp.js -**/vendor/*.js -``` - -A more detailed breakdown of supported patterns and directories ESLint ignores by default can be found in [Ignoring Code](configuring/ignoring-code). - -## Exit codes - -When linting files, ESLint will exit with one of the following exit codes: - -* `0`: Linting was successful and there are no linting errors. If the `--max-warnings` flag is set to `n`, the number of linting warnings is at most `n`. -* `1`: Linting was successful and there is at least one linting error, or there are more linting warnings than allowed by the `--max-warnings` option. -* `2`: Linting was unsuccessful due to a configuration problem or an internal error. diff --git a/eslint/docs/src/user-guide/formatters/index.md b/eslint/docs/src/user-guide/formatters/index.md deleted file mode 100644 index 074b04a..0000000 --- a/eslint/docs/src/user-guide/formatters/index.md +++ /dev/null @@ -1,243 +0,0 @@ ---- -title: Formatters -layout: doc -eleventyNavigation: - key: formatters - parent: user guide - title: Formatters - order: 5 -edit_link: https://github.com/eslint/eslint/edit/main/templates/formatter-examples.md.ejs ---- - -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 json` uses the `json` formatter. - -The built-in formatter options are: - -* [checkstyle](#checkstyle) -* [compact](#compact) -* [html](#html) -* [jslint-xml](#jslint-xml) -* [json-with-metadata](#json-with-metadata) -* [json](#json) -* [junit](#junit) -* [stylish](#stylish) -* [tap](#tap) -* [unix](#unix) -* [visualstudio](#visualstudio) - -## Example Source - -Examples of each formatter were created from linting `fullOfProblems.js` using the `.eslintrc` configuration shown below. - -### `fullOfProblems.js` - -```js -function addOne(i) { - if (i != NaN) { - return i ++ - } else { - return - } -}; -``` - -### `.eslintrc`: - -```json -{ - "extends": "eslint:recommended", - "rules": { - "consistent-return": 2, - "indent" : [1, 4], - "no-else-return" : 1, - "semi" : [1, "always"], - "space-unary-ops" : 2 - } -} -``` - -## Output Examples - -### checkstyle - -```text -<?xml version="1.0" encoding="utf-8"?><checkstyle version="4.3"><file name="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js"><error line="1" column="10" severity="error" message="&apos;addOne&apos; is defined but never used. (no-unused-vars)" source="eslint.rules.no-unused-vars" /><error line="2" column="9" severity="error" message="Use the isNaN function to compare with NaN. (use-isnan)" source="eslint.rules.use-isnan" /><error line="3" column="16" severity="error" message="Unexpected space before unary operator &apos;++&apos;. (space-unary-ops)" source="eslint.rules.space-unary-ops" /><error line="3" column="20" severity="warning" message="Missing semicolon. (semi)" source="eslint.rules.semi" /><error line="4" column="12" severity="warning" message="Unnecessary &apos;else&apos; after &apos;return&apos;. (no-else-return)" source="eslint.rules.no-else-return" /><error line="5" column="1" severity="warning" message="Expected indentation of 8 spaces but found 6. (indent)" source="eslint.rules.indent" /><error line="5" column="7" severity="error" message="Function &apos;addOne&apos; expected a return value. (consistent-return)" source="eslint.rules.consistent-return" /><error line="5" column="13" severity="warning" message="Missing semicolon. (semi)" source="eslint.rules.semi" /><error line="7" column="2" severity="error" message="Unnecessary semicolon. (no-extra-semi)" source="eslint.rules.no-extra-semi" /></file></checkstyle> -``` - -### compact - -```text -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 1, col 10, Error - 'addOne' is defined but never used. (no-unused-vars) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 2, col 9, Error - Use the isNaN function to compare with NaN. (use-isnan) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 3, col 16, Error - Unexpected space before unary operator '++'. (space-unary-ops) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 3, col 20, Warning - Missing semicolon. (semi) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 4, col 12, Warning - Unnecessary 'else' after 'return'. (no-else-return) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 5, col 1, Warning - Expected indentation of 8 spaces but found 6. (indent) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 5, col 7, Error - Function 'addOne' expected a return value. (consistent-return) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 5, col 13, Warning - Missing semicolon. (semi) -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js: line 7, col 2, Error - Unnecessary semicolon. (no-extra-semi) - -9 problems -``` - -### html - - - -### jslint-xml - -```text -<?xml version="1.0" encoding="utf-8"?><jslint><file name="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js"><issue line="1" char="10" evidence="" reason="&apos;addOne&apos; is defined but never used. (no-unused-vars)" /><issue line="2" char="9" evidence="" reason="Use the isNaN function to compare with NaN. (use-isnan)" /><issue line="3" char="16" evidence="" reason="Unexpected space before unary operator &apos;++&apos;. (space-unary-ops)" /><issue line="3" char="20" evidence="" reason="Missing semicolon. (semi)" /><issue line="4" char="12" evidence="" reason="Unnecessary &apos;else&apos; after &apos;return&apos;. (no-else-return)" /><issue line="5" char="1" evidence="" reason="Expected indentation of 8 spaces but found 6. (indent)" /><issue line="5" char="7" evidence="" reason="Function &apos;addOne&apos; expected a return value. (consistent-return)" /><issue line="5" char="13" evidence="" reason="Missing semicolon. (semi)" /><issue line="7" char="2" evidence="" reason="Unnecessary semicolon. (no-extra-semi)" /></file></jslint> -``` - -### json-with-metadata - -```text -{"results":[{"filePath":"/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'addOne' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":16},{"ruleId":"use-isnan","severity":2,"message":"Use the isNaN function to compare with NaN.","line":2,"column":9,"nodeType":"BinaryExpression","messageId":"comparisonWithNaN","endLine":2,"endColumn":17},{"ruleId":"space-unary-ops","severity":2,"message":"Unexpected space before unary operator '++'.","line":3,"column":16,"nodeType":"UpdateExpression","messageId":"unexpectedBefore","endLine":3,"endColumn":20,"fix":{"range":[57,58],"text":""}},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":3,"column":20,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":4,"endColumn":1,"fix":{"range":[60,60],"text":";"}},{"ruleId":"no-else-return","severity":1,"message":"Unnecessary 'else' after 'return'.","line":4,"column":12,"nodeType":"BlockStatement","messageId":"unexpected","endLine":6,"endColumn":6,"fix":{"range":[0,94],"text":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}"}},{"ruleId":"indent","severity":1,"message":"Expected indentation of 8 spaces but found 6.","line":5,"column":1,"nodeType":"Keyword","messageId":"wrongIndentation","endLine":5,"endColumn":7,"fix":{"range":[74,80],"text":" "}},{"ruleId":"consistent-return","severity":2,"message":"Function 'addOne' expected a return value.","line":5,"column":7,"nodeType":"ReturnStatement","messageId":"missingReturnValue","endLine":5,"endColumn":13},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":13,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":6,"endColumn":1,"fix":{"range":[86,86],"text":";"}},{"ruleId":"no-extra-semi","severity":2,"message":"Unnecessary semicolon.","line":7,"column":2,"nodeType":"EmptyStatement","messageId":"unexpected","endLine":7,"endColumn":3,"fix":{"range":[93,95],"text":"}"}}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":2,"fixableWarningCount":4,"source":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};"}],"metadata":{"rulesMeta":{"no-else-return":{"type":"suggestion","docs":{"description":"Disallow `else` blocks after `return` statements in `if` statements","recommended":false,"url":"https://eslint.org/docs/rules/no-else-return"},"schema":[{"type":"object","properties":{"allowElseIf":{"type":"boolean","default":true}},"additionalProperties":false}],"fixable":"code","messages":{"unexpected":"Unnecessary 'else' after 'return'."}},"indent":{"type":"layout","docs":{"description":"Enforce consistent indentation","recommended":false,"url":"https://eslint.org/docs/rules/indent"},"fixable":"whitespace","schema":[{"oneOf":[{"enum":["tab"]},{"type":"integer","minimum":0}]},{"type":"object","properties":{"SwitchCase":{"type":"integer","minimum":0,"default":0},"VariableDeclarator":{"oneOf":[{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},{"type":"object","properties":{"var":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"let":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"const":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false}]},"outerIIFEBody":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"MemberExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"FunctionDeclaration":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"FunctionExpression":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"StaticBlock":{"type":"object","properties":{"body":{"type":"integer","minimum":0}},"additionalProperties":false},"CallExpression":{"type":"object","properties":{"arguments":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false},"ArrayExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ObjectExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ImportDeclaration":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"flatTernaryExpressions":{"type":"boolean","default":false},"offsetTernaryExpressions":{"type":"boolean","default":false},"ignoredNodes":{"type":"array","items":{"type":"string","not":{"pattern":":exit$"}}},"ignoreComments":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"wrongIndentation":"Expected indentation of {{expected}} but found {{actual}}."}},"space-unary-ops":{"type":"layout","docs":{"description":"Enforce consistent spacing before or after unary operators","recommended":false,"url":"https://eslint.org/docs/rules/space-unary-ops"},"fixable":"whitespace","schema":[{"type":"object","properties":{"words":{"type":"boolean","default":true},"nonwords":{"type":"boolean","default":false},"overrides":{"type":"object","additionalProperties":{"type":"boolean"}}},"additionalProperties":false}],"messages":{"unexpectedBefore":"Unexpected space before unary operator '{{operator}}'.","unexpectedAfter":"Unexpected space after unary operator '{{operator}}'.","unexpectedAfterWord":"Unexpected space after unary word operator '{{word}}'.","wordOperator":"Unary word operator '{{word}}' must be followed by whitespace.","operator":"Unary operator '{{operator}}' must be followed by whitespace.","beforeUnaryExpressions":"Space is required before unary expressions '{{token}}'."}},"semi":{"type":"layout","docs":{"description":"Require or disallow semicolons instead of ASI","recommended":false,"url":"https://eslint.org/docs/rules/semi"},"fixable":"code","schema":{"anyOf":[{"type":"array","items":[{"enum":["never"]},{"type":"object","properties":{"beforeStatementContinuationChars":{"enum":["always","any","never"]}},"additionalProperties":false}],"minItems":0,"maxItems":2},{"type":"array","items":[{"enum":["always"]},{"type":"object","properties":{"omitLastInOneLineBlock":{"type":"boolean"}},"additionalProperties":false}],"minItems":0,"maxItems":2}]},"messages":{"missingSemi":"Missing semicolon.","extraSemi":"Extra semicolon."}},"consistent-return":{"type":"suggestion","docs":{"description":"Require `return` statements to either always or never specify values","recommended":false,"url":"https://eslint.org/docs/rules/consistent-return"},"schema":[{"type":"object","properties":{"treatUndefinedAsUnspecified":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"missingReturn":"Expected to return a value at the end of {{name}}.","missingReturnValue":"{{name}} expected a return value.","unexpectedReturnValue":"{{name}} expected no return value."}}}}} -``` - -### json - -```text -[{"filePath":"/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'addOne' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":16},{"ruleId":"use-isnan","severity":2,"message":"Use the isNaN function to compare with NaN.","line":2,"column":9,"nodeType":"BinaryExpression","messageId":"comparisonWithNaN","endLine":2,"endColumn":17},{"ruleId":"space-unary-ops","severity":2,"message":"Unexpected space before unary operator '++'.","line":3,"column":16,"nodeType":"UpdateExpression","messageId":"unexpectedBefore","endLine":3,"endColumn":20,"fix":{"range":[57,58],"text":""}},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":3,"column":20,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":4,"endColumn":1,"fix":{"range":[60,60],"text":";"}},{"ruleId":"no-else-return","severity":1,"message":"Unnecessary 'else' after 'return'.","line":4,"column":12,"nodeType":"BlockStatement","messageId":"unexpected","endLine":6,"endColumn":6,"fix":{"range":[0,94],"text":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}"}},{"ruleId":"indent","severity":1,"message":"Expected indentation of 8 spaces but found 6.","line":5,"column":1,"nodeType":"Keyword","messageId":"wrongIndentation","endLine":5,"endColumn":7,"fix":{"range":[74,80],"text":" "}},{"ruleId":"consistent-return","severity":2,"message":"Function 'addOne' expected a return value.","line":5,"column":7,"nodeType":"ReturnStatement","messageId":"missingReturnValue","endLine":5,"endColumn":13},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":13,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":6,"endColumn":1,"fix":{"range":[86,86],"text":";"}},{"ruleId":"no-extra-semi","severity":2,"message":"Unnecessary semicolon.","line":7,"column":2,"nodeType":"EmptyStatement","messageId":"unexpected","endLine":7,"endColumn":3,"fix":{"range":[93,95],"text":"}"}}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":2,"fixableWarningCount":4,"source":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};"}] -``` - -### junit - -```text -<?xml version="1.0" encoding="utf-8"?> -<testsuites> -<testsuite package="org.eslint" time="0" tests="9" errors="9" name="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js"> -<testcase time="0" name="org.eslint.no-unused-vars" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="&apos;addOne&apos; is defined but never used."><![CDATA[line 1, col 10, Error - &apos;addOne&apos; is defined but never used. (no-unused-vars)]]></failure></testcase> -<testcase time="0" name="org.eslint.use-isnan" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Use the isNaN function to compare with NaN."><![CDATA[line 2, col 9, Error - Use the isNaN function to compare with NaN. (use-isnan)]]></failure></testcase> -<testcase time="0" name="org.eslint.space-unary-ops" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Unexpected space before unary operator &apos;++&apos;."><![CDATA[line 3, col 16, Error - Unexpected space before unary operator &apos;++&apos;. (space-unary-ops)]]></failure></testcase> -<testcase time="0" name="org.eslint.semi" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Missing semicolon."><![CDATA[line 3, col 20, Warning - Missing semicolon. (semi)]]></failure></testcase> -<testcase time="0" name="org.eslint.no-else-return" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Unnecessary &apos;else&apos; after &apos;return&apos;."><![CDATA[line 4, col 12, Warning - Unnecessary &apos;else&apos; after &apos;return&apos;. (no-else-return)]]></failure></testcase> -<testcase time="0" name="org.eslint.indent" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Expected indentation of 8 spaces but found 6."><![CDATA[line 5, col 1, Warning - Expected indentation of 8 spaces but found 6. (indent)]]></failure></testcase> -<testcase time="0" name="org.eslint.consistent-return" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Function &apos;addOne&apos; expected a return value."><![CDATA[line 5, col 7, Error - Function &apos;addOne&apos; expected a return value. (consistent-return)]]></failure></testcase> -<testcase time="0" name="org.eslint.semi" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Missing semicolon."><![CDATA[line 5, col 13, Warning - Missing semicolon. (semi)]]></failure></testcase> -<testcase time="0" name="org.eslint.no-extra-semi" classname="/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems"><failure message="Unnecessary semicolon."><![CDATA[line 7, col 2, Error - Unnecessary semicolon. (no-extra-semi)]]></failure></testcase> -</testsuite> -</testsuites> - -``` - -### stylish - -```text - -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js - 1:10 error 'addOne' is defined but never used no-unused-vars - 2:9 error Use the isNaN function to compare with NaN use-isnan - 3:16 error Unexpected space before unary operator '++' space-unary-ops - 3:20 warning Missing semicolon semi - 4:12 warning Unnecessary 'else' after 'return' no-else-return - 5:1 warning Expected indentation of 8 spaces but found 6 indent - 5:7 error Function 'addOne' expected a return value consistent-return - 5:13 warning Missing semicolon semi - 7:2 error Unnecessary semicolon no-extra-semi - -✖ 9 problems (5 errors, 4 warnings) - 2 errors and 4 warnings potentially fixable with the `--fix` option. - -``` - -### tap - -```text -TAP version 13 -1..1 -not ok 1 - /var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js - --- - message: '''addOne'' is defined but never used.' - severity: error - data: - line: 1 - column: 10 - ruleId: no-unused-vars - messages: - - message: Use the isNaN function to compare with NaN. - severity: error - data: - line: 2 - column: 9 - ruleId: use-isnan - - message: Unexpected space before unary operator '++'. - severity: error - data: - line: 3 - column: 16 - ruleId: space-unary-ops - - message: Missing semicolon. - severity: warning - data: - line: 3 - column: 20 - ruleId: semi - - message: Unnecessary 'else' after 'return'. - severity: warning - data: - line: 4 - column: 12 - ruleId: no-else-return - - message: Expected indentation of 8 spaces but found 6. - severity: warning - data: - line: 5 - column: 1 - ruleId: indent - - message: Function 'addOne' expected a return value. - severity: error - data: - line: 5 - column: 7 - ruleId: consistent-return - - message: Missing semicolon. - severity: warning - data: - line: 5 - column: 13 - ruleId: semi - - message: Unnecessary semicolon. - severity: error - data: - line: 7 - column: 2 - ruleId: no-extra-semi - ... - -``` - -### unix - -```text -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:1:10: 'addOne' is defined but never used. [Error/no-unused-vars] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:2:9: Use the isNaN function to compare with NaN. [Error/use-isnan] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:3:16: Unexpected space before unary operator '++'. [Error/space-unary-ops] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:3:20: Missing semicolon. [Warning/semi] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:4:12: Unnecessary 'else' after 'return'. [Warning/no-else-return] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:5:1: Expected indentation of 8 spaces but found 6. [Warning/indent] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:5:7: Function 'addOne' expected a return value. [Error/consistent-return] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:5:13: Missing semicolon. [Warning/semi] -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js:7:2: Unnecessary semicolon. [Error/no-extra-semi] - -9 problems -``` - -### visualstudio - -```text -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(1,10): error no-unused-vars : 'addOne' is defined but never used. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(2,9): error use-isnan : Use the isNaN function to compare with NaN. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(3,16): error space-unary-ops : Unexpected space before unary operator '++'. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(3,20): warning semi : Missing semicolon. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(4,12): warning no-else-return : Unnecessary 'else' after 'return'. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(5,1): warning indent : Expected indentation of 8 spaces but found 6. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(5,7): error consistent-return : Function 'addOne' expected a return value. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(5,13): warning semi : Missing semicolon. -/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js(7,2): error no-extra-semi : Unnecessary semicolon. - -9 problems -``` diff --git a/eslint/docs/src/user-guide/getting-started.md b/eslint/docs/src/user-guide/getting-started.md deleted file mode 100644 index 675e94f..0000000 --- a/eslint/docs/src/user-guide/getting-started.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: Getting Started with ESLint -layout: doc -eleventyNavigation: - key: getting started - parent: user guide - title: Getting Started - order: 1 - ---- - -ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code, with the goal of making code more consistent and avoiding bugs. In many ways, it is similar to JSLint and JSHint with a few exceptions: - -* ESLint uses [Espree](https://github.com/eslint/espree) for JavaScript parsing. -* ESLint uses an AST to evaluate patterns in code. -* ESLint is completely pluggable, every single rule is a plugin and you can add more at runtime. - -## Installation and Usage - -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 and configure ESLint using this command: - -```shell -npm init @eslint/config -``` - -**Note:** `npm init @eslint/config` assumes you have a `package.json` file already. If you don't, make sure to run `npm init` or `yarn init` beforehand. - -After that, you can run ESLint on any file or directory like this: - -```shell -npx eslint yourfile.js - -# or - -yarn run eslint yourfile.js -``` - -It is also possible to install ESLint globally rather than locally (using `npm install eslint --global`). However, this is not recommended, and any plugins or shareable configs that you use must be installed locally in either case. - -## Configuration - -**Note:** If you are coming from a version before 1.0.0 please see the [migration guide](migrating-to-1.0.0). - -After running `npm init @eslint/config`, you'll have a `.eslintrc.{js,yml,json}` file in your directory. In it, you'll see some rules configured like this: - -```json -{ - "rules": { - "semi": ["error", "always"], - "quotes": ["error", "double"] - } -} -``` - -The names `"semi"` and `"quotes"` are the names of [rules](/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values: - -* `"off"` or `0` - turn the rule off -* `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code) -* `"error"` or `2` - turn the rule on as an error (exit code will be 1) - -The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](configuring/)). - -Your `.eslintrc.{js,yml,json}` configuration file will also include the line: - -```json -{ - "extends": "eslint:recommended" -} -``` - -Because of this line, all of the rules marked "(recommended)" on the [rules page](/docs/rules) will be turned on. Alternatively, you can use configurations that others have created by searching for "eslint-config" on [npmjs.com](https://www.npmjs.com/search?q=eslint-config). ESLint will not lint your code unless you extend from a shared configuration or explicitly turn rules on in your configuration. - ---- - -## Next Steps - -* Learn about [advanced configuration](configuring/) of ESLint. -* Get familiar with the [command line options](command-line-interface). -* Explore [ESLint integrations](integrations) into other tools like editors, build systems, and more. -* Can't find just the right rule? Make your own [custom rule](/docs/developer-guide/working-with-rules). -* Make ESLint even better by [contributing](/docs/developer-guide/contributing/). diff --git a/eslint/docs/tools/validate-links.js b/eslint/docs/tools/validate-links.js new file mode 100644 index 0000000..4d88ee7 --- /dev/null +++ b/eslint/docs/tools/validate-links.js @@ -0,0 +1,57 @@ +"use strict"; + +const path = require("path"); +const TapRender = require("@munter/tap-render"); +const spot = require("tap-spot"); +const hyperlink = require("hyperlink"); + +const tapRenderInstance = new TapRender(); + +tapRenderInstance.pipe(spot()).pipe(process.stdout); + +const skipPatterns = [ + "https://", + "fragment-redirect", + "migrating-to", + "/blog", + "/play", + "/team", + "/donate", + "/docs/latest", + 'src="null"' +]; + +/** + * Filter function to mark tests as skipped. + * Tests for which this function returns `true' are not considered failed. + * @param {Object} report hyperlink's test report for a link. + * @returns {boolean} `true` if the report contains any of `skipPatterns`. + */ +function skipFilter(report) { + return Object.values(report).some(value => + skipPatterns.some(pattern => String(value).includes(pattern))); +} + +(async () => { + try { + await hyperlink( + { + inputUrls: ["../_site/index.html"], + root: path.resolve(__dirname, "../_site"), + canonicalRoot: "https://eslint.org/docs/head/", + recursive: true, + internalOnly: true, + pretty: true, + concurrency: 25, + skipFilter + }, + tapRenderInstance + ); + } catch (err) { + console.log(err.stack); + process.exit(1); + } + const results = tapRenderInstance.close(); + + process.exit(results.fail ? 1 : 0); +})(); diff --git a/eslint/eslint.config.js b/eslint/eslint.config.js index 2156ebe..5c488a5 100644 --- a/eslint/eslint.config.js +++ b/eslint/eslint.config.js @@ -29,6 +29,7 @@ const path = require("path"); const internalPlugin = require("eslint-plugin-internal-rules"); const eslintPlugin = require("eslint-plugin-eslint-plugin"); const { FlatCompat } = require("@eslint/eslintrc"); +const js = require("./packages/js"); const globals = require("globals"); //----------------------------------------------------------------------------- @@ -36,7 +37,8 @@ const globals = require("globals"); //----------------------------------------------------------------------------- const compat = new FlatCompat({ - baseDirectory: __dirname + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended }); const INTERNAL_FILES = { @@ -78,6 +80,23 @@ function createInternalFilesPatterns(pattern = null) { module.exports = [ ...compat.extends("eslint"), + { + ignores: [ + "build/**", + "coverage/**", + "docs/*", + "!docs/*.js", + "!docs/tools/", + "jsdoc/**", + "templates/**", + "tests/bench/**", + "tests/fixtures/**", + "tests/performance/**", + "tmp/**", + "tools/internal-rules/node_modules/**", + "**/test.js" + ] + }, { plugins: { "internal-rules": internalPlugin, @@ -101,9 +120,10 @@ module.exports = [ } }, { - files: ["tools/*.js"], + files: ["tools/*.js", "docs/tools/*.js"], rules: { - "no-console": "off" + "no-console": "off", + "n/no-process-exit": "off" } }, { @@ -117,7 +137,7 @@ module.exports = [ "eslint-plugin/prefer-placeholders": "error", "eslint-plugin/prefer-replace-text": "error", "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], - "eslint-plugin/require-meta-docs-description": ["error", { pattern: "^(Enforce|Require|Disallow)" }], + "eslint-plugin/require-meta-docs-description": ["error", { pattern: "^(Enforce|Require|Disallow) .+[^. ]$" }], "internal-rules/no-invalid-meta": "error" } }, @@ -125,7 +145,7 @@ module.exports = [ files: ["lib/rules/*"], ignores: ["index.js"], rules: { - "eslint-plugin/require-meta-docs-url": ["error", { pattern: "https://eslint.org/docs/rules/{{name}}" }] + "eslint-plugin/require-meta-docs-url": ["error", { pattern: "https://eslint.org/docs/latest/rules/{{name}}" }] } }, { diff --git a/eslint/lib/cli-engine/cli-engine.js b/eslint/lib/cli-engine/cli-engine.js index fdc6619..093a20b 100644 --- a/eslint/lib/cli-engine/cli-engine.js +++ b/eslint/lib/cli-engine/cli-engine.js @@ -308,9 +308,11 @@ function createIgnoreResult(filePath, baseDir) { filePath: path.resolve(filePath), messages: [ { + ruleId: null, fatal: false, severity: 1, - message + message, + nodeType: null } ], suppressedMessages: [], @@ -615,8 +617,8 @@ class CLIEngine { useEslintrc: options.useEslintrc, builtInRules, loadRules, - getEslintRecommendedConfig: () => require("../../conf/eslint-recommended.js"), - getEslintAllConfig: () => require("../../conf/eslint-all.js") + getEslintRecommendedConfig: () => require("@eslint/js").configs.recommended, + getEslintAllConfig: () => require("@eslint/js").configs.all }); const fileEnumerator = new FileEnumerator({ configArrayFactory, diff --git a/eslint/lib/cli-engine/file-enumerator.js b/eslint/lib/cli-engine/file-enumerator.js index b65d0a2..5dff8d0 100644 --- a/eslint/lib/cli-engine/file-enumerator.js +++ b/eslint/lib/cli-engine/file-enumerator.js @@ -217,8 +217,8 @@ class FileEnumerator { cwd = process.cwd(), configArrayFactory = new CascadingConfigArrayFactory({ cwd, - getEslintRecommendedConfig: () => require("../../conf/eslint-recommended.js"), - getEslintAllConfig: () => require("../../conf/eslint-all.js") + getEslintRecommendedConfig: () => require("@eslint/js").configs.recommended, + getEslintAllConfig: () => require("@eslint/js").configs.all }), extensions = null, globInputPaths = true, @@ -349,7 +349,7 @@ class FileEnumerator { return this._iterateFilesWithFile(absolutePath); } if (globInputPaths && isGlobPattern(pattern)) { - return this._iterateFilesWithGlob(absolutePath, isDot); + return this._iterateFilesWithGlob(pattern, isDot); } return []; @@ -398,15 +398,17 @@ class FileEnumerator { _iterateFilesWithGlob(pattern, dotfiles) { debug(`Glob: ${pattern}`); - const directoryPath = path.resolve(getGlobParent(pattern)); - const globPart = pattern.slice(directoryPath.length + 1); + const { cwd } = internalSlotsMap.get(this); + const directoryPath = path.resolve(cwd, getGlobParent(pattern)); + const absolutePath = path.resolve(cwd, pattern); + const globPart = absolutePath.slice(directoryPath.length + 1); /* * recursive if there are `**` or path separators in the glob part. * Otherwise, patterns such as `src/*.js`, it doesn't need recursive. */ const recursive = /\*\*|\/|\\/u.test(globPart); - const selector = new Minimatch(pattern, minimatchOpts); + const selector = new Minimatch(absolutePath, minimatchOpts); debug(`recursive? ${recursive}`); diff --git a/eslint/lib/cli-engine/formatters/formatters-meta.json b/eslint/lib/cli-engine/formatters/formatters-meta.json new file mode 100644 index 0000000..bcd35e5 --- /dev/null +++ b/eslint/lib/cli-engine/formatters/formatters-meta.json @@ -0,0 +1,46 @@ +[ + { + "name": "checkstyle", + "description": "Outputs results to the [Checkstyle](https://checkstyle.sourceforge.io/) format." + }, + { + "name": "compact", + "description": "Human-readable output format. Mimics the default output of JSHint." + }, + { + "name": "html", + "description": "Outputs results to HTML. The `html` formatter is useful for visual presentation in the browser." + }, + { + "name": "jslint-xml", + "description": "Outputs results to format compatible with the [JSLint Jenkins plugin](https://plugins.jenkins.io/jslint/)." + }, + { + "name": "json-with-metadata", + "description": "Outputs JSON-serialized results. The `json-with-metadata` provides the same linting results as the [`json`](#json) formatter with additional metadata about the rules applied. The linting results are included in the `results` property and the rules metadata is included in the `metadata` property.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint." + }, + { + "name": "json", + "description": "Outputs JSON-serialized results. The `json` formatter is useful when you want to programmatically work with the CLI's linting results.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint." + }, + { + "name": "junit", + "description": "Outputs results to format compatible with the [JUnit Jenkins plugin](https://plugins.jenkins.io/junit/)." + }, + { + "name": "stylish", + "description": "Human-readable output format. This is the default formatter." + }, + { + "name": "tap", + "description": "Outputs results to the [Test Anything Protocol (TAP)](https://testanything.org/) specification format." + }, + { + "name": "unix", + "description": "Outputs results to a format similar to many commands in UNIX-like systems. Parsable with tools such as [grep](https://www.gnu.org/software/grep/manual/grep.html), [sed](https://www.gnu.org/software/sed/manual/sed.html), and [awk](https://www.gnu.org/software/gawk/manual/gawk.html)." + }, + { + "name": "visualstudio", + "description": "Outputs results to format compatible with the integrated terminal of the [Visual Studio](https://visualstudio.microsoft.com/) IDE. When using Visual Studio, you can click on the linting results in the integrated terminal to go to the issue in the source code." + } +] \ No newline at end of file diff --git a/eslint/lib/cli-engine/formatters/html.js b/eslint/lib/cli-engine/formatters/html.js index 6e40bbe..1aa66fc 100644 --- a/eslint/lib/cli-engine/formatters/html.js +++ b/eslint/lib/cli-engine/formatters/html.js @@ -43,85 +43,110 @@ function pageTemplate(it) { @@ -214,7 +239,7 @@ function messageTemplate(it) { } = it; return ` - + diff --git a/eslint/lib/cli.js b/eslint/lib/cli.js index 2fca65c..a14930e 100644 --- a/eslint/lib/cli.js +++ b/eslint/lib/cli.js @@ -19,13 +19,11 @@ const fs = require("fs"), path = require("path"), { promisify } = require("util"), { ESLint } = require("./eslint"), - { FlatESLint } = require("./eslint/flat-eslint"), + { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint"), createCLIOptions = require("./options"), log = require("./shared/logging"), RuntimeInfo = require("./shared/runtime-info"); const { Legacy: { naming } } = require("@eslint/eslintrc"); -const { findFlatConfigFile } = require("./eslint/flat-eslint"); -const { gitignoreToMinimatch } = require("@humanwhocodes/gitignore-to-minimatch"); const { ModuleImporter } = require("@humanwhocodes/module-importer"); const debug = require("debug")("eslint:cli"); @@ -38,6 +36,7 @@ const debug = require("debug")("eslint:cli"); /** @typedef {import("./eslint/eslint").LintMessage} LintMessage */ /** @typedef {import("./eslint/eslint").LintResult} LintResult */ /** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions */ +/** @typedef {import("./shared/types").ResultsMeta} ResultsMeta */ //------------------------------------------------------------------------------ // Helpers @@ -143,12 +142,6 @@ async function translateOptions({ overrideConfig[0].plugins = plugins; } - if (ignorePattern) { - overrideConfig.push({ - ignores: ignorePattern.map(gitignoreToMinimatch) - }); - } - } else { overrideConfigFile = config; @@ -182,17 +175,19 @@ async function translateOptions({ fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true), fixTypes: fixType, ignore, - ignorePath, overrideConfig, overrideConfigFile, reportUnusedDisableDirectives: reportUnusedDisableDirectives ? "error" : void 0 }; - if (configType !== "flat") { + if (configType === "flat") { + options.ignorePatterns = ignorePattern; + } else { options.resolvePluginsRelativeTo = resolvePluginsRelativeTo; options.rulePaths = rulesdir; options.useEslintrc = eslintrc; options.extensions = ext; + options.ignorePath = ignorePath; } return options; @@ -201,7 +196,7 @@ async function translateOptions({ /** * Count error messages. * @param {LintResult[]} results The lint results. - * @returns {{errorCount:number;warningCount:number}} The number of error messages. + * @returns {{errorCount:number;fatalErrorCount:number,warningCount:number}} The number of error messages. */ function countErrors(results) { let errorCount = 0; @@ -239,10 +234,11 @@ async function isDirectory(filePath) { * @param {LintResult[]} results The results to print. * @param {string} format The name of the formatter to use or the path to the formatter. * @param {string} outputFile The path for the output file. + * @param {ResultsMeta} resultsMeta Warning count and max threshold. * @returns {Promise} True if the printing succeeds, false if not. * @private */ -async function printResults(engine, results, format, outputFile) { +async function printResults(engine, results, format, outputFile, resultsMeta) { let formatter; try { @@ -252,7 +248,7 @@ async function printResults(engine, results, format, outputFile) { return false; } - const output = await formatter.format(results); + const output = await formatter.format(results, resultsMeta); if (output) { if (outputFile) { @@ -307,7 +303,7 @@ const cli = { * switch to flat config we can remove this logic. */ - const usingFlatConfig = allowFlatConfig && !!(await findFlatConfigFile(process.cwd())); + const usingFlatConfig = allowFlatConfig && await shouldUseFlatConfig(); debug("Using flat config?", usingFlatConfig); @@ -407,17 +403,24 @@ const cli = { resultsToPrint = ActiveESLint.getErrorResults(resultsToPrint); } - if (await printResults(engine, resultsToPrint, options.format, options.outputFile)) { + const resultCounts = countErrors(results); + const tooManyWarnings = options.maxWarnings >= 0 && resultCounts.warningCount > options.maxWarnings; + const resultsMeta = tooManyWarnings + ? { + maxWarningsExceeded: { + maxWarnings: options.maxWarnings, + foundWarnings: resultCounts.warningCount + } + } + : {}; + + if (await printResults(engine, resultsToPrint, options.format, options.outputFile, resultsMeta)) { // Errors and warnings from the original unfiltered results should determine the exit code - const { errorCount, fatalErrorCount, warningCount } = countErrors(results); - - const tooManyWarnings = - options.maxWarnings >= 0 && warningCount > options.maxWarnings; const shouldExitForFatalErrors = - options.exitOnFatalError && fatalErrorCount > 0; + options.exitOnFatalError && resultCounts.fatalErrorCount > 0; - if (!errorCount && tooManyWarnings) { + if (!resultCounts.errorCount && tooManyWarnings) { log.error( "ESLint found too many warnings (maximum: %s).", options.maxWarnings @@ -428,7 +431,7 @@ const cli = { return 2; } - return (errorCount || tooManyWarnings) ? 1 : 0; + return (resultCounts.errorCount || tooManyWarnings) ? 1 : 0; } return 2; diff --git a/eslint/lib/config/default-config.js b/eslint/lib/config/default-config.js index c48551a..8a6ff82 100644 --- a/eslint/lib/config/default-config.js +++ b/eslint/lib/config/default-config.js @@ -19,9 +19,6 @@ exports.defaultConfig = [ { plugins: { "@": { - parsers: { - espree: require("espree") - }, /* * Because we try to delay loading rules until absolutely @@ -43,7 +40,7 @@ exports.defaultConfig = [ languageOptions: { sourceType: "module", ecmaVersion: "latest", - parser: "@/espree", + parser: require("espree"), parserOptions: {} } }, @@ -51,8 +48,8 @@ exports.defaultConfig = [ // default ignores are listed here { ignores: [ - "**/node_modules/**", - ".git/**" + "**/node_modules/", + ".git/" ] }, diff --git a/eslint/lib/config/flat-config-array.js b/eslint/lib/config/flat-config-array.js index ad8986f..689dc42 100644 --- a/eslint/lib/config/flat-config-array.js +++ b/eslint/lib/config/flat-config-array.js @@ -13,7 +13,7 @@ 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 jsPlugin = require("@eslint/js"); //----------------------------------------------------------------------------- // Helpers @@ -36,6 +36,45 @@ function splitPluginIdentifier(identifier) { }; } +/** + * Returns the name of an object in the config by reading its `meta` key. + * @param {Object} object The object to check. + * @returns {string?} The name of the object if found or `null` if there + * is no name. + */ +function getObjectId(object) { + + // first check old-style name + let name = object.name; + + if (!name) { + + if (!object.meta) { + return null; + } + + name = object.meta.name; + + if (!name) { + return null; + } + } + + // now check for old-style version + let version = object.version; + + if (!version) { + version = object.meta && object.meta.version; + } + + // if there's a version then append that + if (version) { + return `${name}@${version}`; + } + + return name; +} + const originalBaseConfig = Symbol("originalBaseConfig"); //----------------------------------------------------------------------------- @@ -70,7 +109,7 @@ class FlatConfigArray extends ConfigArray { } /** - * The baes config used to build the config array. + * The base config used to build the config array. * @type {Array} */ this[originalBaseConfig] = baseConfig; @@ -96,17 +135,23 @@ class FlatConfigArray extends ConfigArray { */ [ConfigArraySymbol.preprocessConfig](config) { if (config === "eslint:recommended") { - return recommendedConfig; + + // if we are in a Node.js environment warn the user + if (typeof process !== "undefined" && process.emitWarning) { + process.emitWarning("The 'eslint:recommended' string configuration is deprecated and will be replaced by the @eslint/js package's 'recommended' config."); + } + + return jsPlugin.configs.recommended; } if (config === "eslint:all") { - /* - * Load `eslint-all.js` here instead of at the top level to avoid loading all rule modules - * when it isn't necessary. `eslint-all.js` reads `meta` of rule objects to filter out deprecated ones, - * so requiring `eslint-all.js` module loads all rule modules as a consequence. - */ - return require("../../conf/eslint-all"); + // if we are in a Node.js environment warn the user + if (typeof process !== "undefined" && process.emitWarning) { + process.emitWarning("The 'eslint:all' string configuration is deprecated and will be replaced by the @eslint/js package's 'all' config."); + } + + return jsPlugin.configs.all; } /* @@ -145,16 +190,15 @@ class FlatConfigArray extends ConfigArray { // Check parser value if (languageOptions && languageOptions.parser) { - if (typeof languageOptions.parser === "string") { - const { pluginName, objectName: localParserName } = splitPluginIdentifier(languageOptions.parser); + const { parser } = languageOptions; - parserName = languageOptions.parser; + if (typeof parser === "object") { + parserName = getObjectId(parser); - if (!plugins || !plugins[pluginName] || !plugins[pluginName].parsers || !plugins[pluginName].parsers[localParserName]) { - throw new TypeError(`Key "parser": Could not find "${localParserName}" in plugin "${pluginName}".`); + if (!parserName) { + invalidParser = true; } - languageOptions.parser = plugins[pluginName].parsers[localParserName]; } else { invalidParser = true; } @@ -172,6 +216,13 @@ class FlatConfigArray extends ConfigArray { } config.processor = plugins[pluginName].processors[localProcessorName]; + } else if (typeof processor === "object") { + processorName = getObjectId(processor); + + if (!processorName) { + invalidProcessor = true; + } + } else { invalidProcessor = true; } @@ -185,16 +236,25 @@ class FlatConfigArray extends ConfigArray { value: function() { if (invalidParser) { - throw new Error("Caching is not supported when parser is an object."); + throw new Error("Could not serialize parser object (missing 'meta' object)."); } if (invalidProcessor) { - throw new Error("Caching is not supported when processor is an object."); + throw new Error("Could not serialize processor object (missing 'meta' object)."); } return { ...this, - plugins: Object.keys(plugins), + plugins: Object.entries(plugins).map(([namespace, plugin]) => { + + const pluginId = getObjectId(plugin); + + if (!pluginId) { + return namespace; + } + + return `${namespace}:${pluginId}`; + }), languageOptions: { ...languageOptions, parser: parserName diff --git a/eslint/lib/config/flat-config-schema.js b/eslint/lib/config/flat-config-schema.js index cb8e796..10d6b50 100644 --- a/eslint/lib/config/flat-config-schema.js +++ b/eslint/lib/config/flat-config-schema.js @@ -126,32 +126,65 @@ function normalizeRuleOptions(ruleOptions) { // Assertions //----------------------------------------------------------------------------- +/** + * The error type when a rule's options are configured with an invalid type. + */ +class InvalidRuleOptionsError extends Error { + + /** + * @param {string} ruleId Rule name being configured. + * @param {any} value The invalid value. + */ + constructor(ruleId, value) { + super(`Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`); + this.messageTemplate = "invalid-rule-options"; + this.messageData = { ruleId, value }; + } +} + /** * Validates that a value is a valid rule options entry. + * @param {string} ruleId Rule name being configured. * @param {any} value The value to check. * @returns {void} - * @throws {TypeError} If the value isn't a valid rule options. + * @throws {InvalidRuleOptionsError} If the value isn't a valid rule options. */ -function assertIsRuleOptions(value) { - +function assertIsRuleOptions(ruleId, value) { if (typeof value !== "string" && typeof value !== "number" && !Array.isArray(value)) { - throw new TypeError("Expected a string, number, or array."); + throw new InvalidRuleOptionsError(ruleId, value); + } +} + +/** + * The error type when a rule's severity is invalid. + */ +class InvalidRuleSeverityError extends Error { + + /** + * @param {string} ruleId Rule name being configured. + * @param {any} value The invalid value. + */ + constructor(ruleId, value) { + super(`Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`); + this.messageTemplate = "invalid-rule-severity"; + this.messageData = { ruleId, value }; } } /** * Validates that a value is valid rule severity. + * @param {string} ruleId Rule name being configured. * @param {any} value The value to check. * @returns {void} - * @throws {TypeError} If the value isn't a valid rule severity. + * @throws {InvalidRuleSeverityError} If the value isn't a valid rule severity. */ -function assertIsRuleSeverity(value) { +function assertIsRuleSeverity(ruleId, 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."); + throw new InvalidRuleSeverityError(ruleId, value); } } @@ -179,18 +212,6 @@ function assertIsObject(value) { } } -/** - * 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 //----------------------------------------------------------------------------- @@ -242,15 +263,13 @@ const globalsSchema = { 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 (!value || typeof value !== "object" || + (typeof value.parse !== "function" && typeof value.parseForESLint !== "function") + ) { + throw new TypeError("Expected object with parse() or parseForESLint() method."); } - if (typeof value === "string") { - assertIsPluginMemberName(value); - } } }; @@ -371,39 +390,28 @@ const rulesSchema = { validate(value) { assertIsObject(value); - let lastRuleId; + /* + * 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)) { - // 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); - } + // avoid hairy edge case + if (ruleId === "__proto__") { + continue; + } + + const ruleOptions = value[ruleId]; + + assertIsRuleOptions(ruleId, ruleOptions); + + if (Array.isArray(ruleOptions)) { + assertIsRuleSeverity(ruleId, ruleOptions[0]); + } else { + assertIsRuleSeverity(ruleId, ruleOptions); } - } catch (error) { - error.message = `Key "${lastRuleId}": ${error.message}`; - throw error; } } }; diff --git a/eslint/lib/eslint/eslint.js b/eslint/lib/eslint/eslint.js index 9a3bd66..e1d2116 100644 --- a/eslint/lib/eslint/eslint.js +++ b/eslint/lib/eslint/eslint.js @@ -36,11 +36,12 @@ const { version } = require("../../package.json"); /** @typedef {import("../shared/types").Plugin} Plugin */ /** @typedef {import("../shared/types").Rule} Rule */ /** @typedef {import("../shared/types").LintResult} LintResult */ +/** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */ /** * The main formatter object. * @typedef LoadedFormatter - * @property {function(LintResult[]): string | Promise} format format function. + * @property {(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise} format format function. */ /** @@ -625,14 +626,16 @@ class ESLint { /** * The main formatter method. * @param {LintResult[]} results The lint results to format. + * @param {ResultsMeta} resultsMeta Warning count and max threshold. * @returns {string | Promise} The formatted lint results. */ - format(results) { + format(results, resultsMeta) { let rulesMeta = null; results.sort(compareResultsByFilePath); return formatter(results, { + ...resultsMeta, get cwd() { return options.cwd; }, diff --git a/eslint/lib/eslint/flat-eslint.js b/eslint/lib/eslint/flat-eslint.js index e436c46..f615ae1 100644 --- a/eslint/lib/eslint/flat-eslint.js +++ b/eslint/lib/eslint/flat-eslint.js @@ -16,7 +16,6 @@ const findUp = require("find-up"); const { version } = require("../../package.json"); const { Linter } = require("../linter"); const { getRuleFromConfig } = require("../config/flat-config-helpers"); -const { gitignoreToMinimatch } = require("@humanwhocodes/gitignore-to-minimatch"); const { Legacy: { ConfigOps: { @@ -28,7 +27,6 @@ const { } = require("@eslint/eslintrc"); const { - fileExists, findFiles, getCacheFile, @@ -57,8 +55,10 @@ const LintResultCache = require("../cli-engine/lint-result-cache"); /** @typedef {import("../shared/types").ConfigData} ConfigData */ /** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */ /** @typedef {import("../shared/types").LintMessage} LintMessage */ +/** @typedef {import("../shared/types").LintResult} LintResult */ /** @typedef {import("../shared/types").ParserOptions} ParserOptions */ /** @typedef {import("../shared/types").Plugin} Plugin */ +/** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */ /** @typedef {import("../shared/types").RuleConf} RuleConf */ /** @typedef {import("../shared/types").Rule} Rule */ /** @typedef {ReturnType} ExtractedConfig */ @@ -76,9 +76,8 @@ const LintResultCache = require("../cli-engine/lint-result-cache"); * @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean. * @property {string[]} [fixTypes] Array of rule types to apply fixes for. * @property {boolean} [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. - * @property {boolean} [ignore] False disables use of .eslintignore. - * @property {string} [ignorePath] The ignore file to use instead of .eslintignore. - * @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to .eslintignore. + * @property {boolean} [ignore] False disables all ignore patterns except for the default ones. + * @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to config ignores. These patterns are relative to `cwd`. * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance * @property {boolean|string} [overrideConfigFile] Searches for default config file when falsy; * doesn't do any config file lookup when `true`; considered to be a config filename @@ -95,6 +94,7 @@ const FLAT_CONFIG_FILENAME = "eslint.config.js"; const debug = require("debug")("eslint:flat-eslint"); const removedFormatters = new Set(["table", "codeframe"]); const privateMembers = new WeakMap(); +const importedConfigFileModificationTime = new Map(); /** * It will calculate the error and warning count for collection of messages per file @@ -151,30 +151,6 @@ function calculateStatsPerRun(results) { }); } -/** - * Loads global ignore patterns from an ignore file (usually .eslintignore). - * @param {string} filePath The filename to load. - * @returns {ignore} A function encapsulating the ignore patterns. - * @throws {Error} If the file cannot be read. - * @private - */ -async function loadIgnoreFilePatterns(filePath) { - debug(`Loading ignore file: ${filePath}`); - - try { - const ignoreFileText = await fs.readFile(filePath, { encoding: "utf8" }); - - return ignoreFileText - .split(/\r?\n/gu) - .filter(line => line.trim() !== "" && !line.startsWith("#")); - - } catch (e) { - debug(`Error reading ignore file: ${filePath}`); - e.message = `Cannot read ignore file: ${filePath}\nError: ${e.message}`; - throw e; - } -} - /** * Create rulesMeta object. * @param {Map} rules a map of rules from which to generate the object. @@ -187,6 +163,16 @@ function createRulesMeta(rules) { }, {}); } +/** + * Return the absolute path of a file named `"__placeholder__.js"` in a given directory. + * This is used as a replacement for a missing file path. + * @param {string} cwd An absolute directory path. + * @returns {string} The absolute path of a file named `"__placeholder__.js"` in the given directory. + */ +function getPlaceholderPath(cwd) { + return path.join(cwd, "__placeholder__.js"); +} + /** @type {WeakMap} */ const usedDeprecatedRulesCache = new WeakMap(); @@ -203,7 +189,7 @@ function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) { } = privateMembers.get(eslint); const filePath = path.isAbsolute(maybeFilePath) ? maybeFilePath - : path.join(cwd, "__placeholder__.js"); + : getPlaceholderPath(cwd); const config = configs.getConfig(filePath); // Most files use the same config, so cache it. @@ -276,7 +262,7 @@ function compareResultsByFilePath(a, b) { * Searches from the current working directory up until finding the * given flat config filename. * @param {string} cwd The current working directory to search from. - * @returns {Promise} The filename if found or `null` if not. + * @returns {Promise} The filename if found or `undefined` if not. */ function findFlatConfigFile(cwd) { return findUp( @@ -288,24 +274,90 @@ function findFlatConfigFile(cwd) { /** * Load the config array from the given filename. * @param {string} filePath The filename to load from. - * @param {Object} options Options to help load the config file. - * @param {string} options.basePath The base path for the config array. - * @param {boolean} options.shouldIgnore Whether to honor ignore patterns. - * @returns {Promise} The config array loaded from the config file. + * @returns {Promise} The config loaded from the config file. */ -async function loadFlatConfigFile(filePath, { basePath, shouldIgnore }) { +async function loadFlatConfigFile(filePath) { debug(`Loading config from ${filePath}`); const fileURL = pathToFileURL(filePath); debug(`Config file URL is ${fileURL}`); - const module = await import(fileURL); + const mtime = (await fs.stat(filePath)).mtime.getTime(); - return new FlatConfigArray(module.default, { + /* + * Append a query with the config file's modification time (`mtime`) in order + * to import the current version of the config file. Without the query, `import()` would + * cache the config file module by the pathname only, and then always return + * the same version (the one that was actual when the module was imported for the first time). + * + * This ensures that the config file module is loaded and executed again + * if it has been changed since the last time it was imported. + * If it hasn't been changed, `import()` will just return the cached version. + * + * Note that we should not overuse queries (e.g., by appending the current time + * to always reload the config file module) as that could cause memory leaks + * because entries are never removed from the import cache. + */ + fileURL.searchParams.append("mtime", mtime); + + /* + * With queries, we can bypass the import cache. However, when import-ing a CJS module, + * Node.js uses the require infrastructure under the hood. That includes the require cache, + * which caches the config file module by its file path (queries have no effect). + * Therefore, we also need to clear the require cache before importing the config file module. + * In order to get the same behavior with ESM and CJS config files, in particular - to reload + * the config file only if it has been changed, we track file modification times and clear + * the require cache only if the file has been changed. + */ + if (importedConfigFileModificationTime.get(filePath) !== mtime) { + delete require.cache[filePath]; + } + + const config = (await import(fileURL)).default; + + importedConfigFileModificationTime.set(filePath, mtime); + + return config; +} + +/** + * Determines which config file to use. This is determined by seeing if an + * override config file was passed, and if so, using it; otherwise, as long + * as override config file is not explicitly set to `false`, it will search + * upwards from the cwd for a file named `eslint.config.js`. + * @param {import("./eslint").ESLintOptions} options The ESLint instance options. + * @returns {{configFilePath:string|undefined,basePath:string,error:Error|null}} Location information for + * the config file. + */ +async function locateConfigFileToUse({ configFile, cwd }) { + + // determine where to load config file from + let configFilePath; + let basePath = cwd; + let error = null; + + if (typeof configFile === "string") { + debug(`Override config file path is ${configFile}`); + configFilePath = path.resolve(cwd, configFile); + } else if (configFile !== false) { + debug("Searching for eslint.config.js"); + configFilePath = await findFlatConfigFile(cwd); + + if (configFilePath) { + basePath = path.resolve(path.dirname(configFilePath)); + } else { + error = new Error("Could not find config file."); + } + + } + + return { + configFilePath, basePath, - shouldIgnore - }); + error + }; + } /** @@ -316,10 +368,10 @@ async function loadFlatConfigFile(filePath, { basePath, shouldIgnore }) { */ async function calculateConfigArray(eslint, { cwd, + baseConfig, overrideConfig, configFile, ignore: shouldIgnore, - ignorePath, ignorePatterns }) { @@ -330,105 +382,62 @@ async function calculateConfigArray(eslint, { return slots.configs; } - // determine where to load config file from - let configFilePath; - let basePath = cwd; + const { configFilePath, basePath, error } = await locateConfigFileToUse({ configFile, cwd }); - if (typeof configFile === "string") { - debug(`Override config file path is ${configFile}`); - configFilePath = path.resolve(cwd, configFile); - } else if (configFile !== false) { - debug("Searching for eslint.config.js"); - configFilePath = await findFlatConfigFile(cwd); - - if (!configFilePath) { - throw new Error("Could not find config file."); - } - - basePath = path.resolve(path.dirname(configFilePath)); + // config file is required to calculate config + if (error) { + throw error; } - // load config array - let configs; + const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore }); + // load config file if (configFilePath) { - configs = await loadFlatConfigFile(configFilePath, { - basePath, - shouldIgnore - }); - } else { - configs = new FlatConfigArray([], { basePath, shouldIgnore }); + const fileConfig = await loadFlatConfigFile(configFilePath); + + if (Array.isArray(fileConfig)) { + configs.push(...fileConfig); + } else { + configs.push(fileConfig); + } } // add in any configured defaults configs.push(...slots.defaultConfigs); - let allIgnorePatterns = []; - let ignoreFilePath; - - // load ignore file if necessary - if (shouldIgnore) { - if (ignorePath) { - ignoreFilePath = path.resolve(cwd, ignorePath); - allIgnorePatterns = await loadIgnoreFilePatterns(ignoreFilePath); - } else { - ignoreFilePath = path.resolve(cwd, ".eslintignore"); - - // no error if .eslintignore doesn't exist` - if (fileExists(ignoreFilePath)) { - allIgnorePatterns = await loadIgnoreFilePatterns(ignoreFilePath); - } - } - } - // append command line ignore patterns - if (ignorePatterns) { - if (typeof ignorePatterns === "string") { - allIgnorePatterns.push(ignorePatterns); + if (ignorePatterns && ignorePatterns.length > 0) { + + let relativeIgnorePatterns; + + /* + * If the config file basePath is different than the cwd, then + * the ignore patterns won't work correctly. Here, we adjust the + * ignore pattern to include the correct relative path. Patterns + * passed as `ignorePatterns` are relative to the cwd, whereas + * the config file basePath can be an ancestor of the cwd. + */ + if (basePath === cwd) { + relativeIgnorePatterns = ignorePatterns; } else { - allIgnorePatterns.push(...ignorePatterns); - } - } - /* - * If the config file basePath is different than the cwd, then - * the ignore patterns won't work correctly. Here, we adjust the - * ignore pattern to include the correct relative path. Patterns - * loaded from ignore files are always relative to the cwd, whereas - * the config file basePath can be an ancestor of the cwd. - */ - if (basePath !== cwd && allIgnorePatterns.length) { + const relativeIgnorePath = path.relative(basePath, cwd); - const relativeIgnorePath = path.relative(basePath, cwd); + relativeIgnorePatterns = ignorePatterns.map(pattern => { + const negated = pattern.startsWith("!"); + const basePattern = negated ? pattern.slice(1) : pattern; - allIgnorePatterns = allIgnorePatterns.map(pattern => { - const negated = pattern.startsWith("!"); - const basePattern = negated ? pattern.slice(1) : pattern; - - /* - * Ignore patterns are considered relative to a directory - * when the pattern contains a slash in a position other - * than the last character. If that's the case, we need to - * add the relative ignore path to the current pattern to - * get the correct behavior. Otherwise, no change is needed. - */ - if (!basePattern.includes("/") || basePattern.endsWith("/")) { - return pattern; - } - - return (negated ? "!" : "") + + return (negated ? "!" : "") + path.posix.join(relativeIgnorePath, basePattern); - }); - } - - if (allIgnorePatterns.length) { + }); + } /* * Ignore patterns are added to the end of the config array * so they can override default ignores. */ configs.push({ - ignores: allIgnorePatterns.map(gitignoreToMinimatch) + ignores: relativeIgnorePatterns }); } @@ -481,7 +490,7 @@ function verifyText({ * `config.extractConfig(filePath)` requires an absolute path, but `linter` * doesn't know CWD, so it gives `linter` an absolute path always. */ - const filePathToVerify = filePath === "" ? path.join(cwd, "__placeholder__.js") : filePath; + const filePathToVerify = filePath === "" ? getPlaceholderPath(cwd) : filePath; const { fixed, messages, output } = linter.verifyAndFix( text, configs, @@ -578,6 +587,14 @@ function *iterateRuleDeprecationWarnings(configs) { } } +/** + * Creates an error to be thrown when an array of results passed to `getRulesMetaForResults` was not created by the current engine. + * @returns {TypeError} An error object. + */ +function createExtraneousResultsError() { + return new TypeError("Results object was not created from this ESLint instance."); +} + //----------------------------------------------------------------------------- // Main API //----------------------------------------------------------------------------- @@ -708,14 +725,16 @@ class FlatESLint { */ getRulesMetaForResults(results) { - const resultRules = new Map(); - // short-circuit simple case if (results.length === 0) { - return resultRules; + return {}; } - const { configs } = privateMembers.get(this); + const resultRules = new Map(); + const { + configs, + options: { cwd } + } = privateMembers.get(this); /* * We can only accurately return rules meta information for linting results if the @@ -724,7 +743,7 @@ class FlatESLint { * to let the user know we can't do anything here. */ if (!configs) { - throw new TypeError("Results object was not created from this ESLint instance."); + throw createExtraneousResultsError(); } for (const result of results) { @@ -733,16 +752,23 @@ class FlatESLint { * Normalize filename for . */ const filePath = result.filePath === "" - ? "__placeholder__.js" : result.filePath; - - /* - * All of the plugin and rule information is contained within the - * calculated config for the given file. - */ - const config = configs.getConfig(filePath); + ? getPlaceholderPath(cwd) : result.filePath; const allMessages = result.messages.concat(result.suppressedMessages); for (const { ruleId } of allMessages) { + if (!ruleId) { + continue; + } + + /* + * All of the plugin and rule information is contained within the + * calculated config for the given file. + */ + const config = configs.getConfig(filePath); + + if (!config) { + throw createExtraneousResultsError(); + } const rule = getRuleFromConfig(ruleId, config); // ensure the rule exists @@ -872,7 +898,7 @@ class FlatESLint { } - // set up fixer for fixtypes if necessary + // set up fixer for fixTypes if necessary let fixer = fix; if (fix && fixTypesSet) { @@ -1048,7 +1074,7 @@ class FlatESLint { * The following values are allowed: * - `undefined` ... Load `stylish` builtin formatter. * - A builtin formatter name ... Load the builtin formatter. - * - A thirdparty formatter name: + * - A third-party formatter name: * - `foo` → `eslint-formatter-foo` * - `@foo` → `@foo/eslint-formatter` * - `@foo/bar` → `@foo/eslint-formatter-bar` @@ -1080,7 +1106,7 @@ class FlatESLint { const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter"); // TODO: This is pretty dirty...would be nice to clean up at some point. - formatterPath = ModuleResolver.resolve(npmFormat, path.join(cwd, "__placeholder__.js")); + formatterPath = ModuleResolver.resolve(npmFormat, getPlaceholderPath(cwd)); } catch { formatterPath = path.resolve(__dirname, "../", "cli-engine", "formatters", `${normalizedFormatName}.js`); } @@ -1114,14 +1140,16 @@ class FlatESLint { /** * The main formatter method. * @param {LintResults[]} results The lint results to format. + * @param {ResultsMeta} resultsMeta Warning count and max threshold. * @returns {string} The formatted lint results. */ - format(results) { + format(results, resultsMeta) { let rulesMeta = null; results.sort(compareResultsByFilePath); return formatter(results, { + ...resultsMeta, cwd, get rulesMeta() { if (!rulesMeta) { @@ -1154,6 +1182,19 @@ class FlatESLint { return configs.getConfig(absolutePath); } + /** + * Finds the config file being used by this instance based on the options + * passed to the constructor. + * @returns {string|undefined} The path to the config file being used or + * `undefined` if no config file is being used. + */ + async findConfigFile() { + const options = privateMembers.get(this).options; + const { configFilePath } = await locateConfigFileToUse(options); + + return configFilePath; + } + /** * Checks if a given path is ignored by ESLint. * @param {string} filePath The path of the file to check. @@ -1166,11 +1207,31 @@ class FlatESLint { } } +/** + * Returns whether flat config should be used. + * @returns {Promise} Whether flat config should be used. + */ +async function shouldUseFlatConfig() { + switch (process.env.ESLINT_USE_FLAT_CONFIG) { + case "true": + return true; + case "false": + return false; + default: + + /* + * If neither explicitly enabled nor disabled, then use the presence + * of a flat config file to determine enablement. + */ + return !!(await findFlatConfigFile(process.cwd())); + } +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ module.exports = { FlatESLint, - findFlatConfigFile + shouldUseFlatConfig }; diff --git a/eslint/lib/linter/apply-disable-directives.js b/eslint/lib/linter/apply-disable-directives.js index 459c859..13ced99 100644 --- a/eslint/lib/linter/apply-disable-directives.js +++ b/eslint/lib/linter/apply-disable-directives.js @@ -5,6 +5,16 @@ "use strict"; +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** @typedef {import("../shared/types").LintMessage} LintMessage */ + +//------------------------------------------------------------------------------ +// Module Definition +//------------------------------------------------------------------------------ + const escapeRegExp = require("escape-string-regexp"); /** @@ -196,7 +206,7 @@ function processUnusedDisableDirectives(allDirectives) { * @param {Object} options options for applying directives. This is the same as the options * for the exported function, except that `reportUnusedDisableDirectives` is not supported * (this function always reports unused disable directives). - * @returns {{problems: Problem[], unusedDisableDirectives: Problem[]}} An object with a list + * @returns {{problems: LintMessage[], unusedDisableDirectives: LintMessage[]}} An object with a list * of problems (including suppressed ones) and unused eslint-disable directives */ function applyDirectives(options) { diff --git a/eslint/lib/linter/config-comment-parser.js b/eslint/lib/linter/config-comment-parser.js index 643de8f..9aab3c4 100644 --- a/eslint/lib/linter/config-comment-parser.js +++ b/eslint/lib/linter/config-comment-parser.js @@ -19,6 +19,12 @@ const levn = require("levn"), const debug = require("debug")("eslint:config-comment-parser"); +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** @typedef {import("../shared/types").LintMessage} LintMessage */ + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -61,7 +67,7 @@ module.exports = class ConfigCommentParser { * Parses a JSON-like config. * @param {string} string The string to parse. * @param {Object} location Start line and column of comments for potential error message. - * @returns {({success: true, config: Object}|{success: false, error: Problem})} Result map object + * @returns {({success: true, config: Object}|{success: false, error: LintMessage})} Result map object */ parseJsonConfig(string, location) { debug("Parsing JSON config"); @@ -109,7 +115,8 @@ module.exports = class ConfigCommentParser { severity: 2, message: `Failed to parse JSON from '${normalizedString}': ${ex.message}`, line: location.start.line, - column: location.start.column + 1 + column: location.start.column + 1, + nodeType: null } }; diff --git a/eslint/lib/linter/linter.js b/eslint/lib/linter/linter.js index a29ce92..233cbed 100644 --- a/eslint/lib/linter/linter.js +++ b/eslint/lib/linter/linter.js @@ -18,6 +18,9 @@ const merge = require("lodash.merge"), pkg = require("../../package.json"), astUtils = require("../shared/ast-utils"), + { + directivesPattern + } = require("../shared/directives"), { Legacy: { ConfigOps, @@ -213,6 +216,7 @@ function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, ena if (variable) { variable.eslintUsed = true; + variable.eslintExported = true; } }); @@ -360,7 +364,7 @@ function extractDirectiveComment(value) { * @param {ASTNode} ast The top node of the AST. * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules * @param {string|null} warnInlineConfig If a string then it should warn directive comments as disabled. The string value is the config name what the setting came from. - * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}} + * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: LintMessage[], disableDirectives: DisableDirective[]}} * A collection of the directive comments that were found, along with any problems that occurred when parsing */ function getDirectiveComments(ast, ruleMapper, warnInlineConfig) { @@ -376,7 +380,7 @@ function getDirectiveComments(ast, ruleMapper, warnInlineConfig) { ast.comments.filter(token => token.type !== "Shebang").forEach(comment => { const { directivePart, justificationPart } = extractDirectiveComment(comment.value); - const match = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(directivePart); + const match = directivesPattern.exec(directivePart); if (!match) { return; @@ -588,7 +592,7 @@ function findEslintEnv(text) { * Convert "/path/to/" to "". * `CLIEngine#executeOnText()` method gives "/path/to/" if the filename * was omitted because `configArray.extractConfig()` requires an absolute path. - * But the linter should pass `` to `RuleContext#getFilename()` in that + * But the linter should pass `` to `RuleContext#filename` in that * case. * Also, code blocks can have their virtual filename. If the parent filename was * ``, the virtual filename is `/0_foo.js` or something like (i.e., @@ -771,7 +775,7 @@ function analyzeScope(ast, languageOptions, visitorKeys) { * @param {string} text The text to parse. * @param {LanguageOptions} languageOptions Options to pass to the parser * @param {string} filePath The path to the file being parsed. - * @returns {{success: false, error: Problem}|{success: true, sourceCode: SourceCode}} + * @returns {{success: false, error: LintMessage}|{success: true, sourceCode: SourceCode}} * An object containing the AST and parser services if parsing was successful, or the error if parsing failed * @private */ @@ -847,69 +851,13 @@ function parse(text, languageOptions, filePath) { severity: 2, message, line: ex.lineNumber, - column: ex.column + column: ex.column, + nodeType: null } }; } } -/** - * Gets the scope for the current node - * @param {ScopeManager} scopeManager The scope manager for this AST - * @param {ASTNode} currentNode The node to get the scope of - * @returns {eslint-scope.Scope} The scope information for this node - */ -function getScope(scopeManager, currentNode) { - - // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope. - const inner = currentNode.type !== "Program"; - - for (let node = currentNode; node; node = node.parent) { - const scope = scopeManager.acquire(node, inner); - - if (scope) { - if (scope.type === "function-expression-name") { - return scope.childScopes[0]; - } - return scope; - } - } - - return scopeManager.scopes[0]; -} - -/** - * Marks a variable as used in the current scope - * @param {ScopeManager} scopeManager The scope manager for this AST. The scope may be mutated by this function. - * @param {ASTNode} currentNode The node currently being traversed - * @param {LanguageOptions} languageOptions The options used to parse this text - * @param {string} name The name of the variable that should be marked as used. - * @returns {boolean} True if the variable was found and marked as used, false if not. - */ -function markVariableAsUsed(scopeManager, currentNode, languageOptions, name) { - const parserOptions = languageOptions.parserOptions; - const sourceType = languageOptions.sourceType; - const hasGlobalReturn = - (parserOptions.ecmaFeatures && parserOptions.ecmaFeatures.globalReturn) || - sourceType === "commonjs"; - const specialScope = hasGlobalReturn || sourceType === "module"; - const currentScope = getScope(scopeManager, currentNode); - - // Special Node.js scope means we need to start one level deeper - const initialScope = currentScope.type === "global" && specialScope ? currentScope.childScopes[0] : currentScope; - - for (let scope = initialScope; scope; scope = scope.upper) { - const variable = scope.variables.find(scopeVar => scopeVar.name === name); - - if (variable) { - variable.eslintUsed = true; - return true; - } - } - - return false; -} - /** * Runs a rule, and gets its listeners * @param {Rule} rule A normalized rule with a `create` method @@ -926,22 +874,6 @@ function createRuleListeners(rule, ruleContext) { } } -/** - * Gets all the ancestors of a given node - * @param {ASTNode} node The node - * @returns {ASTNode[]} All the ancestor nodes in the AST, not including the provided node, starting - * from the root node and going inwards to the parent node. - */ -function getAncestors(node) { - const ancestorsStartingAtParent = []; - - for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) { - ancestorsStartingAtParent.push(ancestor); - } - - return ancestorsStartingAtParent.reverse(); -} - // methods that exist on SourceCode object const DEPRECATED_SOURCECODE_PASSTHROUGHS = { getSource: "getText", @@ -971,7 +903,7 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze( (contextInfo, methodName) => Object.assign(contextInfo, { [methodName](...args) { - return this.getSourceCode()[DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]](...args); + return this.sourceCode[DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]](...args); } }), {} @@ -990,7 +922,7 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze( * @param {boolean} disableFixes If true, it doesn't make `fix` properties. * @param {string | undefined} cwd cwd of the cli * @param {string} physicalFilename The full path of the file on disk without any code block information - * @returns {Problem[]} An array of reported problems + * @returns {LintMessage[]} An array of reported problems */ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename) { const emitter = createEmitter(); @@ -1017,14 +949,18 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO Object.assign( Object.create(BASE_TRAVERSAL_CONTEXT), { - getAncestors: () => getAncestors(currentNode), - getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager), + getAncestors: () => sourceCode.getAncestors(currentNode), + getDeclaredVariables: node => sourceCode.getDeclaredVariables(node), getCwd: () => cwd, + cwd, getFilename: () => filename, + filename, getPhysicalFilename: () => physicalFilename || filename, - getScope: () => getScope(sourceCode.scopeManager, currentNode), + physicalFilename: physicalFilename || filename, + getScope: () => sourceCode.getScope(currentNode), getSourceCode: () => sourceCode, - markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, currentNode, languageOptions, name), + sourceCode, + markVariableAsUsed: name => sourceCode.markVariableAsUsed(name, currentNode), parserOptions: { ...languageOptions.parserOptions }, @@ -1318,7 +1254,8 @@ class Linter { severity: 2, message: `Configured parser '${config.parser}' was not found.`, line: 0, - column: 0 + column: 0, + nodeType: null }]; } parserName = config.parser; @@ -1529,7 +1466,8 @@ class Linter { severity: 2, message, line: ex.lineNumber, - column: ex.column + column: ex.column, + nodeType: null } ]; } @@ -1601,12 +1539,18 @@ class Linter { languageOptions.ecmaVersion ); - // add configured globals and language globals - const configuredGlobals = { - ...(getGlobalsForEcmaVersion(languageOptions.ecmaVersion)), - ...(languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0), - ...languageOptions.globals - }; + /* + * add configured globals and language globals + * + * using Object.assign instead of object spread for performance reasons + * https://github.com/eslint/eslint/issues/16302 + */ + const configuredGlobals = Object.assign( + {}, + getGlobalsForEcmaVersion(languageOptions.ecmaVersion), + languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0, + languageOptions.globals + ); // double check that there is a parser to avoid mysterious error messages if (!languageOptions.parser) { @@ -1788,7 +1732,8 @@ class Linter { severity: 1, message: `No matching configuration found for ${filename}.`, line: 0, - column: 0 + column: 0, + nodeType: null } ]; } @@ -1853,7 +1798,8 @@ class Linter { severity: 2, message, line: ex.lineNumber, - column: ex.column + column: ex.column, + nodeType: null } ]; } @@ -1899,7 +1845,7 @@ class Linter { /** * Given a list of reported problems, distinguish problems between normal messages and suppressed messages. * The normal messages will be returned and the suppressed messages will be stored as lastSuppressedMessages. - * @param {Problem[]} problems A list of reported problems. + * @param {Array} problems A list of reported problems. * @returns {LintMessage[]} A list of LintMessage. */ _distinguishSuppressedMessages(problems) { diff --git a/eslint/lib/linter/report-translator.js b/eslint/lib/linter/report-translator.js index 781b313..7d27052 100644 --- a/eslint/lib/linter/report-translator.js +++ b/eslint/lib/linter/report-translator.js @@ -17,6 +17,8 @@ const interpolate = require("./interpolate"); // Typedefs //------------------------------------------------------------------------------ +/** @typedef {import("../shared/types").LintMessage} LintMessage */ + /** * An error message description * @typedef {Object} MessageDescriptor @@ -29,23 +31,6 @@ const interpolate = require("./interpolate"); * @property {Array<{desc?: string, messageId?: string, fix: Function}>} suggest Suggestion descriptions and functions to create a the associated fixes. */ -/** - * Information about the report - * @typedef {Object} ReportInfo - * @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 - */ - //------------------------------------------------------------------------------ // Module Definition //------------------------------------------------------------------------------ @@ -239,7 +224,7 @@ function mapSuggestions(descriptor, sourceCode, messages) { * @param {{start: SourceLocation, end: (SourceLocation|null)}} options.loc Start and end location * @param {{text: string, range: (number[]|null)}} options.fix The fix object * @param {Array<{text: string, range: (number[]|null)}>} options.suggestions The array of suggestions objects - * @returns {function(...args): ReportInfo} Function that returns information about the report + * @returns {LintMessage} Information about the report */ function createProblem(options) { const problem = { @@ -314,7 +299,7 @@ function validateSuggestions(suggest, messages) { * problem for the Node.js API. * @param {{ruleId: string, severity: number, sourceCode: SourceCode, messageIds: Object, disableFixes: boolean}} metadata Metadata for the reported problem * @param {SourceCode} sourceCode The `SourceCode` instance for the text being linted - * @returns {function(...args): ReportInfo} Function that returns information about the report + * @returns {function(...args): LintMessage} Function that returns information about the report */ module.exports = function createReportTranslator(metadata) { diff --git a/eslint/lib/options.js b/eslint/lib/options.js index 0d95f2a..2bc4018 100644 --- a/eslint/lib/options.js +++ b/eslint/lib/options.js @@ -67,7 +67,7 @@ const optionator = require("optionator"); /** * Creates the CLI options for ESLint. * @param {boolean} usingFlatConfig Indicates if flat config is being used. - * @returns {Object} The opinionator instance. + * @returns {Object} The optionator instance. */ module.exports = function(usingFlatConfig) { @@ -129,6 +129,16 @@ module.exports = function(usingFlatConfig) { }; } + let ignorePathFlag; + + if (!usingFlatConfig) { + ignorePathFlag = { + option: "ignore-path", + type: "path::String", + description: "Specify path of ignore file" + }; + } + return optionator({ prepend: "eslint [options] file.js [file.js] [dir]", defaults: { @@ -167,7 +177,7 @@ module.exports = function(usingFlatConfig) { }, resolvePluginsFlag, { - heading: "Specifying rules and plugins" + heading: "Specify Rules and Plugins" }, { option: "plugin", @@ -181,7 +191,7 @@ module.exports = function(usingFlatConfig) { }, rulesDirFlag, { - heading: "Fixing problems" + heading: "Fix Problems" }, { option: "fix", @@ -201,13 +211,9 @@ module.exports = function(usingFlatConfig) { description: "Specify the types of fixes to apply (directive, problem, suggestion, layout)" }, { - heading: "Ignoring files" - }, - { - option: "ignore-path", - type: "path::String", - description: "Specify path of ignore file" + heading: "Ignore Files" }, + ignorePathFlag, { option: "ignore", type: "Boolean", @@ -223,7 +229,7 @@ module.exports = function(usingFlatConfig) { }] }, { - heading: "Using stdin" + heading: "Use stdin" }, { option: "stdin", @@ -237,7 +243,7 @@ module.exports = function(usingFlatConfig) { description: "Specify filename to process STDIN as" }, { - heading: "Handling warnings" + heading: "Handle Warnings" }, { option: "quiet", diff --git a/eslint/lib/rule-tester/flat-rule-tester.js b/eslint/lib/rule-tester/flat-rule-tester.js index f915924..97055d1 100644 --- a/eslint/lib/rule-tester/flat-rule-tester.js +++ b/eslint/lib/rule-tester/flat-rule-tester.js @@ -345,7 +345,7 @@ class FlatRuleTester { * @returns {void} */ static setDefaultConfig(config) { - if (typeof config !== "object") { + if (typeof config !== "object" || config === null) { throw new TypeError("FlatRuleTester.setDefaultConfig: config must be an object"); } sharedDefaultConfig = config; @@ -430,7 +430,7 @@ class FlatRuleTester { 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." + "See https://eslint.org/docs/latest/integrate/nodejs-api#customizing-ruletester for more." ); } if (typeof it === "function") { @@ -619,15 +619,17 @@ class FlatRuleTester { plugins: { "rule-tester": { rules: { - "validate-ast"() { - return { - Program(node) { - beforeAST = cloneDeeplyExcludesParent(node); - }, - "Program:exit"(node) { - afterAST = node; - } - }; + "validate-ast": { + create() { + return { + Program(node) { + beforeAST = cloneDeeplyExcludesParent(node); + }, + "Program:exit"(node) { + afterAST = node; + } + }; + } } } } diff --git a/eslint/lib/rule-tester/rule-tester.js b/eslint/lib/rule-tester/rule-tester.js index 2af272b..8518299 100644 --- a/eslint/lib/rule-tester/rule-tester.js +++ b/eslint/lib/rule-tester/rule-tester.js @@ -314,7 +314,7 @@ function emitLegacyRuleAPIWarning(ruleName) { if (!emitLegacyRuleAPIWarning[`warned-${ruleName}`]) { emitLegacyRuleAPIWarning[`warned-${ruleName}`] = true; process.emitWarning( - `"${ruleName}" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/developer-guide/working-with-rules`, + `"${ruleName}" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/latest/extend/custom-rules`, "DeprecationWarning" ); } @@ -329,7 +329,7 @@ function emitMissingSchemaWarning(ruleName) { if (!emitMissingSchemaWarning[`warned-${ruleName}`]) { emitMissingSchemaWarning[`warned-${ruleName}`] = true; process.emitWarning( - `"${ruleName}" rule has options but is missing the "meta.schema" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/developer-guide/working-with-rules#options-schemas`, + `"${ruleName}" rule has options but is missing the "meta.schema" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas`, "DeprecationWarning" ); } @@ -412,7 +412,7 @@ class RuleTester { * @returns {void} */ static setDefaultConfig(config) { - if (typeof config !== "object") { + if (typeof config !== "object" || config === null) { throw new TypeError("RuleTester.setDefaultConfig: config must be an object"); } defaultConfig = config; @@ -493,7 +493,7 @@ class RuleTester { 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." + "See https://eslint.org/docs/latest/integrate/nodejs-api#customizing-ruletester for more." ); } if (typeof it === "function") { @@ -632,14 +632,18 @@ class RuleTester { * The goal is to check whether or not AST was modified when * running the rule under test. */ - linter.defineRule("rule-tester/validate-ast", () => ({ - Program(node) { - beforeAST = cloneDeeplyExcludesParent(node); - }, - "Program:exit"(node) { - afterAST = node; + linter.defineRule("rule-tester/validate-ast", { + create() { + return { + Program(node) { + beforeAST = cloneDeeplyExcludesParent(node); + }, + "Program:exit"(node) { + afterAST = node; + } + }; } - })); + }); if (typeof config.parser === "string") { assert(path.isAbsolute(config.parser), "Parsers provided as strings to RuleTester must be absolute paths"); diff --git a/eslint/lib/rules/accessor-pairs.js b/eslint/lib/rules/accessor-pairs.js index 112d0dd..03b51e4 100644 --- a/eslint/lib/rules/accessor-pairs.js +++ b/eslint/lib/rules/accessor-pairs.js @@ -142,7 +142,7 @@ module.exports = { docs: { description: "Enforce getter and setter pairs in objects and classes", recommended: false, - url: "https://eslint.org/docs/rules/accessor-pairs" + url: "https://eslint.org/docs/latest/rules/accessor-pairs" }, schema: [{ @@ -178,7 +178,7 @@ module.exports = { const checkGetWithoutSet = config.getWithoutSet === true; const checkSetWithoutGet = config.setWithoutGet !== false; const enforceForClassMembers = config.enforceForClassMembers !== false; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Reports the given node. diff --git a/eslint/lib/rules/array-bracket-newline.js b/eslint/lib/rules/array-bracket-newline.js index deeae81..c3676bf 100644 --- a/eslint/lib/rules/array-bracket-newline.js +++ b/eslint/lib/rules/array-bracket-newline.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Enforce linebreaks after opening and before closing array brackets", recommended: false, - url: "https://eslint.org/docs/rules/array-bracket-newline" + url: "https://eslint.org/docs/latest/rules/array-bracket-newline" }, fixable: "whitespace", @@ -56,7 +56,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //---------------------------------------------------------------------- diff --git a/eslint/lib/rules/array-bracket-spacing.js b/eslint/lib/rules/array-bracket-spacing.js index 5e7cea9..e3a46d8 100644 --- a/eslint/lib/rules/array-bracket-spacing.js +++ b/eslint/lib/rules/array-bracket-spacing.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Enforce consistent spacing inside array brackets", recommended: false, - url: "https://eslint.org/docs/rules/array-bracket-spacing" + url: "https://eslint.org/docs/latest/rules/array-bracket-spacing" }, fixable: "whitespace", @@ -53,7 +53,7 @@ module.exports = { }, create(context) { const spaced = context.options[0] === "always", - sourceCode = context.getSourceCode(); + sourceCode = context.sourceCode; /** * Determines whether an option is set, relative to the spacing option. diff --git a/eslint/lib/rules/array-callback-return.js b/eslint/lib/rules/array-callback-return.js index 7d4a564..05cd4ed 100644 --- a/eslint/lib/rules/array-callback-return.js +++ b/eslint/lib/rules/array-callback-return.js @@ -16,7 +16,7 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u; -const TARGET_METHODS = /^(?:every|filter|find(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort)$/u; +const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort|toSorted)$/u; /** * Checks a given code path segment is reachable. @@ -141,7 +141,7 @@ module.exports = { docs: { description: "Enforce `return` statements in callbacks of array methods", recommended: false, - url: "https://eslint.org/docs/rules/array-callback-return" + url: "https://eslint.org/docs/latest/rules/array-callback-return" }, schema: [ @@ -172,7 +172,7 @@ module.exports = { create(context) { const options = context.options[0] || { allowImplicit: false, checkForEach: false }; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; let funcInfo = { arrayMethodName: null, diff --git a/eslint/lib/rules/array-element-newline.js b/eslint/lib/rules/array-element-newline.js index c762755..be547ec 100644 --- a/eslint/lib/rules/array-element-newline.js +++ b/eslint/lib/rules/array-element-newline.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Enforce line breaks after each array element", recommended: false, - url: "https://eslint.org/docs/rules/array-element-newline" + url: "https://eslint.org/docs/latest/rules/array-element-newline" }, fixable: "whitespace", @@ -47,6 +47,7 @@ module.exports = { ] } }, + type: "array", items: [ { oneOf: [ @@ -78,7 +79,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //---------------------------------------------------------------------- // Helpers diff --git a/eslint/lib/rules/arrow-body-style.js b/eslint/lib/rules/arrow-body-style.js index 8bb9e8c..7590704 100644 --- a/eslint/lib/rules/arrow-body-style.js +++ b/eslint/lib/rules/arrow-body-style.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Require braces around arrow function bodies", recommended: false, - url: "https://eslint.org/docs/rules/arrow-body-style" + url: "https://eslint.org/docs/latest/rules/arrow-body-style" }, schema: { @@ -74,7 +74,7 @@ module.exports = { const asNeeded = !options[0] || options[0] === "as-needed"; const never = options[0] === "never"; const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; let funcInfo = null; /** diff --git a/eslint/lib/rules/arrow-parens.js b/eslint/lib/rules/arrow-parens.js index 05012fc..0463323 100644 --- a/eslint/lib/rules/arrow-parens.js +++ b/eslint/lib/rules/arrow-parens.js @@ -35,7 +35,7 @@ module.exports = { docs: { description: "Require parentheses around arrow function arguments", recommended: false, - url: "https://eslint.org/docs/rules/arrow-parens" + url: "https://eslint.org/docs/latest/rules/arrow-parens" }, fixable: "code", @@ -69,7 +69,7 @@ module.exports = { const asNeeded = context.options[0] === "as-needed"; const requireForBlockBody = asNeeded && context.options[1] && context.options[1].requireForBlockBody === true; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Finds opening paren of parameters for the given arrow function, if it exists. diff --git a/eslint/lib/rules/arrow-spacing.js b/eslint/lib/rules/arrow-spacing.js index 2dcc175..fb74d2c 100644 --- a/eslint/lib/rules/arrow-spacing.js +++ b/eslint/lib/rules/arrow-spacing.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Enforce consistent spacing before and after the arrow in arrow functions", recommended: false, - url: "https://eslint.org/docs/rules/arrow-spacing" + url: "https://eslint.org/docs/latest/rules/arrow-spacing" }, fixable: "whitespace", @@ -61,7 +61,7 @@ module.exports = { rule.before = rule.before !== false; rule.after = rule.after !== false; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Get tokens of arrow(`=>`) and before/after arrow. diff --git a/eslint/lib/rules/block-scoped-var.js b/eslint/lib/rules/block-scoped-var.js index 731d06d..5a95127 100644 --- a/eslint/lib/rules/block-scoped-var.js +++ b/eslint/lib/rules/block-scoped-var.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Enforce the use of variables within the scope they are defined", recommended: false, - url: "https://eslint.org/docs/rules/block-scoped-var" + url: "https://eslint.org/docs/latest/rules/block-scoped-var" }, schema: [], @@ -28,6 +28,7 @@ module.exports = { create(context) { let stack = []; + const sourceCode = context.sourceCode; /** * Makes a block scope. @@ -83,7 +84,7 @@ module.exports = { } // Gets declared variables, and checks its references. - const variables = context.getDeclaredVariables(node); + const variables = sourceCode.getDeclaredVariables(node); for (let i = 0; i < variables.length; ++i) { diff --git a/eslint/lib/rules/block-spacing.js b/eslint/lib/rules/block-spacing.js index 9fbf159..dd4851c 100644 --- a/eslint/lib/rules/block-spacing.js +++ b/eslint/lib/rules/block-spacing.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Disallow or enforce spaces inside of blocks after opening block and before closing block", recommended: false, - url: "https://eslint.org/docs/rules/block-spacing" + url: "https://eslint.org/docs/latest/rules/block-spacing" }, fixable: "whitespace", @@ -37,7 +37,7 @@ module.exports = { create(context) { const always = (context.options[0] !== "never"), messageId = always ? "missing" : "extra", - sourceCode = context.getSourceCode(); + sourceCode = context.sourceCode; /** * Gets the open brace token from a given node. diff --git a/eslint/lib/rules/brace-style.js b/eslint/lib/rules/brace-style.js index 52d8920..59758c9 100644 --- a/eslint/lib/rules/brace-style.js +++ b/eslint/lib/rules/brace-style.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Enforce consistent brace style for blocks", recommended: false, - url: "https://eslint.org/docs/rules/brace-style" + url: "https://eslint.org/docs/latest/rules/brace-style" }, schema: [ @@ -53,7 +53,7 @@ module.exports = { create(context) { const style = context.options[0] || "1tbs", params = context.options[1] || {}, - sourceCode = context.getSourceCode(); + sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers diff --git a/eslint/lib/rules/callback-return.js b/eslint/lib/rules/callback-return.js index fe5b649..5d441bd 100644 --- a/eslint/lib/rules/callback-return.js +++ b/eslint/lib/rules/callback-return.js @@ -21,7 +21,7 @@ module.exports = { docs: { description: "Require `return` statements after callbacks", recommended: false, - url: "https://eslint.org/docs/rules/callback-return" + url: "https://eslint.org/docs/latest/rules/callback-return" }, schema: [{ @@ -37,7 +37,7 @@ module.exports = { create(context) { const callbacks = context.options[0] || ["callback", "cb", "next"], - sourceCode = context.getSourceCode(); + sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers diff --git a/eslint/lib/rules/camelcase.js b/eslint/lib/rules/camelcase.js index ee1b6bf..51bb412 100644 --- a/eslint/lib/rules/camelcase.js +++ b/eslint/lib/rules/camelcase.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Enforce camelcase naming convention", recommended: false, - url: "https://eslint.org/docs/rules/camelcase" + url: "https://eslint.org/docs/latest/rules/camelcase" }, schema: [ @@ -73,6 +73,7 @@ module.exports = { const ignoreImports = options.ignoreImports; const ignoreGlobals = options.ignoreGlobals; const allow = options.allow || []; + const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers @@ -245,8 +246,8 @@ module.exports = { return { // Report camelcase of global variable references ------------------ - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); if (!ignoreGlobals) { @@ -295,7 +296,7 @@ module.exports = { "ClassExpression", "CatchClause" ]](node) { - for (const variable of context.getDeclaredVariables(node)) { + for (const variable of sourceCode.getDeclaredVariables(node)) { if (isGoodName(variable.name)) { continue; } @@ -345,7 +346,7 @@ module.exports = { // Report camelcase in import -------------------------------------- ImportDeclaration(node) { - for (const variable of context.getDeclaredVariables(node)) { + for (const variable of sourceCode.getDeclaredVariables(node)) { if (isGoodName(variable.name)) { continue; } diff --git a/eslint/lib/rules/capitalized-comments.js b/eslint/lib/rules/capitalized-comments.js index ba798d4..3a17b05 100644 --- a/eslint/lib/rules/capitalized-comments.js +++ b/eslint/lib/rules/capitalized-comments.js @@ -107,7 +107,7 @@ module.exports = { docs: { description: "Enforce or disallow capitalization of the first letter of a comment", recommended: false, - url: "https://eslint.org/docs/rules/capitalized-comments" + url: "https://eslint.org/docs/latest/rules/capitalized-comments" }, fixable: "code", @@ -139,7 +139,7 @@ module.exports = { const capitalize = context.options[0] || "always", normalizedOptions = getAllNormalizedOptions(context.options[1]), - sourceCode = context.getSourceCode(); + sourceCode = context.sourceCode; createRegExpForIgnorePatterns(normalizedOptions); diff --git a/eslint/lib/rules/class-methods-use-this.js b/eslint/lib/rules/class-methods-use-this.js index 05a9158..9cf8a1b 100644 --- a/eslint/lib/rules/class-methods-use-this.js +++ b/eslint/lib/rules/class-methods-use-this.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Enforce that class methods utilize `this`", recommended: false, - url: "https://eslint.org/docs/rules/class-methods-use-this" + url: "https://eslint.org/docs/latest/rules/class-methods-use-this" }, schema: [{ @@ -133,7 +133,7 @@ module.exports = { if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) { context.report({ node, - loc: astUtils.getFunctionHeadLoc(node, context.getSourceCode()), + loc: astUtils.getFunctionHeadLoc(node, context.sourceCode), messageId: "missingThis", data: { name: astUtils.getFunctionNameWithKind(node) diff --git a/eslint/lib/rules/comma-dangle.js b/eslint/lib/rules/comma-dangle.js index 9518da9..e49983b 100644 --- a/eslint/lib/rules/comma-dangle.js +++ b/eslint/lib/rules/comma-dangle.js @@ -50,7 +50,7 @@ function normalizeOptions(optionValue, ecmaVersion) { objects: optionValue, imports: optionValue, exports: optionValue, - functions: (!ecmaVersion || ecmaVersion < 8) ? "ignore" : optionValue + functions: ecmaVersion < 2017 ? "ignore" : optionValue }; } if (typeof optionValue === "object" && optionValue !== null) { @@ -78,7 +78,7 @@ module.exports = { docs: { description: "Require or disallow trailing commas", recommended: false, - url: "https://eslint.org/docs/rules/comma-dangle" + url: "https://eslint.org/docs/latest/rules/comma-dangle" }, fixable: "code", @@ -134,9 +134,9 @@ module.exports = { }, create(context) { - const options = normalizeOptions(context.options[0], context.parserOptions.ecmaVersion); + const options = normalizeOptions(context.options[0], context.languageOptions.ecmaVersion); - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Gets the last item of the given node. @@ -346,7 +346,7 @@ module.exports = { "always-multiline": forceTrailingCommaIfMultiline, "only-multiline": allowTrailingCommaIfMultiline, never: forbidTrailingComma, - ignore: () => {} + ignore() {} }; return { diff --git a/eslint/lib/rules/comma-spacing.js b/eslint/lib/rules/comma-spacing.js index 76d5dc4..96015ef 100644 --- a/eslint/lib/rules/comma-spacing.js +++ b/eslint/lib/rules/comma-spacing.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Enforce consistent spacing before and after commas", recommended: false, - url: "https://eslint.org/docs/rules/comma-spacing" + url: "https://eslint.org/docs/latest/rules/comma-spacing" }, fixable: "whitespace", @@ -48,7 +48,7 @@ module.exports = { create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const tokensAndComments = sourceCode.tokensAndComments; const options = { diff --git a/eslint/lib/rules/comma-style.js b/eslint/lib/rules/comma-style.js index 4969f59..bc69de4 100644 --- a/eslint/lib/rules/comma-style.js +++ b/eslint/lib/rules/comma-style.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Enforce consistent comma style", recommended: false, - url: "https://eslint.org/docs/rules/comma-style" + url: "https://eslint.org/docs/latest/rules/comma-style" }, fixable: "code", @@ -51,7 +51,7 @@ module.exports = { create(context) { const style = context.options[0] || "last", - sourceCode = context.getSourceCode(); + sourceCode = context.sourceCode; const exceptions = { ArrayPattern: true, ArrowFunctionExpression: true, diff --git a/eslint/lib/rules/complexity.js b/eslint/lib/rules/complexity.js index 541d3a9..b792507 100644 --- a/eslint/lib/rules/complexity.js +++ b/eslint/lib/rules/complexity.js @@ -25,7 +25,7 @@ module.exports = { docs: { description: "Enforce a maximum cyclomatic complexity allowed in a program", recommended: false, - url: "https://eslint.org/docs/rules/complexity" + url: "https://eslint.org/docs/latest/rules/complexity" }, schema: [ diff --git a/eslint/lib/rules/computed-property-spacing.js b/eslint/lib/rules/computed-property-spacing.js index 3d033fc..1e4e17c 100644 --- a/eslint/lib/rules/computed-property-spacing.js +++ b/eslint/lib/rules/computed-property-spacing.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Enforce consistent spacing inside computed property brackets", recommended: false, - url: "https://eslint.org/docs/rules/computed-property-spacing" + url: "https://eslint.org/docs/latest/rules/computed-property-spacing" }, fixable: "whitespace", @@ -49,7 +49,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never" const enforceForClassMembers = !context.options[1] || context.options[1].enforceForClassMembers; diff --git a/eslint/lib/rules/consistent-return.js b/eslint/lib/rules/consistent-return.js index f007297..e2d3f07 100644 --- a/eslint/lib/rules/consistent-return.js +++ b/eslint/lib/rules/consistent-return.js @@ -48,7 +48,7 @@ module.exports = { docs: { description: "Require `return` statements to either always or never specify values", recommended: false, - url: "https://eslint.org/docs/rules/consistent-return" + url: "https://eslint.org/docs/latest/rules/consistent-return" }, schema: [{ @@ -104,7 +104,7 @@ module.exports = { } else if (node.type === "ArrowFunctionExpression") { // `=>` token - loc = context.getSourceCode().getTokenBefore(node.body, astUtils.isArrowToken).loc; + loc = context.sourceCode.getTokenBefore(node.body, astUtils.isArrowToken).loc; } else if ( node.parent.type === "MethodDefinition" || (node.parent.type === "Property" && node.parent.method) @@ -115,7 +115,7 @@ module.exports = { } else { // Function name or `function` keyword. - loc = (node.id || context.getSourceCode().getFirstToken(node)).loc; + loc = (node.id || context.sourceCode.getFirstToken(node)).loc; } if (!name) { diff --git a/eslint/lib/rules/consistent-this.js b/eslint/lib/rules/consistent-this.js index 947873b..658957a 100644 --- a/eslint/lib/rules/consistent-this.js +++ b/eslint/lib/rules/consistent-this.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Enforce consistent naming when capturing the current execution context", recommended: false, - url: "https://eslint.org/docs/rules/consistent-this" + url: "https://eslint.org/docs/latest/rules/consistent-this" }, schema: { @@ -36,6 +36,7 @@ module.exports = { create(context) { let aliases = []; + const sourceCode = context.sourceCode; if (context.options.length === 0) { aliases.push("that"); @@ -115,10 +116,11 @@ module.exports = { /** * Check each alias to ensure that is was assigned to the correct value. + * @param {ASTNode} node The node that represents the scope to check. * @returns {void} */ - function ensureWasAssigned() { - const scope = context.getScope(); + function ensureWasAssigned(node) { + const scope = sourceCode.getScope(node); aliases.forEach(alias => { checkWasAssigned(alias, scope); diff --git a/eslint/lib/rules/constructor-super.js b/eslint/lib/rules/constructor-super.js index fff6584..5f40588 100644 --- a/eslint/lib/rules/constructor-super.js +++ b/eslint/lib/rules/constructor-super.js @@ -124,7 +124,7 @@ module.exports = { docs: { description: "Require `super()` calls in constructors", recommended: true, - url: "https://eslint.org/docs/rules/constructor-super" + url: "https://eslint.org/docs/latest/rules/constructor-super" }, schema: [], diff --git a/eslint/lib/rules/curly.js b/eslint/lib/rules/curly.js index 7b5d140..3540824 100644 --- a/eslint/lib/rules/curly.js +++ b/eslint/lib/rules/curly.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Enforce consistent brace style for all control statements", recommended: false, - url: "https://eslint.org/docs/rules/curly" + url: "https://eslint.org/docs/latest/rules/curly" }, schema: { @@ -70,7 +70,7 @@ module.exports = { const multiOrNest = (context.options[0] === "multi-or-nest"); const consistent = (context.options[1] === "consistent"); - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers diff --git a/eslint/lib/rules/default-case-last.js b/eslint/lib/rules/default-case-last.js index 313a0d8..d4a83b5 100644 --- a/eslint/lib/rules/default-case-last.js +++ b/eslint/lib/rules/default-case-last.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Enforce default clauses in switch statements to be last", recommended: false, - url: "https://eslint.org/docs/rules/default-case-last" + url: "https://eslint.org/docs/latest/rules/default-case-last" }, schema: [], diff --git a/eslint/lib/rules/default-case.js b/eslint/lib/rules/default-case.js index f28de1a..4f2fad0 100644 --- a/eslint/lib/rules/default-case.js +++ b/eslint/lib/rules/default-case.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Require `default` cases in `switch` statements", recommended: false, - url: "https://eslint.org/docs/rules/default-case" + url: "https://eslint.org/docs/latest/rules/default-case" }, schema: [{ @@ -42,7 +42,7 @@ module.exports = { ? new RegExp(options.commentPattern, "u") : DEFAULT_COMMENT_PATTERN; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers diff --git a/eslint/lib/rules/default-param-last.js b/eslint/lib/rules/default-param-last.js index 61df5f6..3254fa8 100644 --- a/eslint/lib/rules/default-param-last.js +++ b/eslint/lib/rules/default-param-last.js @@ -13,7 +13,7 @@ module.exports = { docs: { description: "Enforce default parameters to be last", recommended: false, - url: "https://eslint.org/docs/rules/default-param-last" + url: "https://eslint.org/docs/latest/rules/default-param-last" }, schema: [], diff --git a/eslint/lib/rules/dot-location.js b/eslint/lib/rules/dot-location.js index 36b50b2..dac98b0 100644 --- a/eslint/lib/rules/dot-location.js +++ b/eslint/lib/rules/dot-location.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Enforce consistent newlines before and after dots", recommended: false, - url: "https://eslint.org/docs/rules/dot-location" + url: "https://eslint.org/docs/latest/rules/dot-location" }, schema: [ @@ -43,7 +43,7 @@ module.exports = { // default to onObject if no preference is passed const onObject = config === "object" || !config; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Reports if the dot between object and property is on the correct location. diff --git a/eslint/lib/rules/dot-notation.js b/eslint/lib/rules/dot-notation.js index 5f6e818..78f1368 100644 --- a/eslint/lib/rules/dot-notation.js +++ b/eslint/lib/rules/dot-notation.js @@ -28,7 +28,7 @@ module.exports = { docs: { description: "Enforce dot notation whenever possible", recommended: false, - url: "https://eslint.org/docs/rules/dot-notation" + url: "https://eslint.org/docs/latest/rules/dot-notation" }, schema: [ @@ -59,7 +59,7 @@ module.exports = { create(context) { const options = context.options[0] || {}; const allowKeywords = options.allowKeywords === void 0 || options.allowKeywords; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; let allowPattern; diff --git a/eslint/lib/rules/eol-last.js b/eslint/lib/rules/eol-last.js index fb87971..1036db1 100644 --- a/eslint/lib/rules/eol-last.js +++ b/eslint/lib/rules/eol-last.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Require or disallow newline at the end of files", recommended: false, - url: "https://eslint.org/docs/rules/eol-last" + url: "https://eslint.org/docs/latest/rules/eol-last" }, fixable: "whitespace", @@ -40,7 +40,7 @@ module.exports = { return { Program: function checkBadEOF(node) { - const sourceCode = context.getSourceCode(), + const sourceCode = context.sourceCode, src = sourceCode.getText(), lastLine = sourceCode.lines[sourceCode.lines.length - 1], location = { diff --git a/eslint/lib/rules/eqeqeq.js b/eslint/lib/rules/eqeqeq.js index b3990e2..12b1e80 100644 --- a/eslint/lib/rules/eqeqeq.js +++ b/eslint/lib/rules/eqeqeq.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Require the use of `===` and `!==`", recommended: false, - url: "https://eslint.org/docs/rules/eqeqeq" + url: "https://eslint.org/docs/latest/rules/eqeqeq" }, schema: { @@ -68,7 +68,7 @@ module.exports = { create(context) { const config = context.options[0] || "always"; const options = context.options[1] || {}; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const nullOption = (config === "always") ? options.null || "always" diff --git a/eslint/lib/rules/for-direction.js b/eslint/lib/rules/for-direction.js index 7df3d7e..4ed7350 100644 --- a/eslint/lib/rules/for-direction.js +++ b/eslint/lib/rules/for-direction.js @@ -15,9 +15,9 @@ module.exports = { type: "problem", docs: { - description: "Enforce \"for\" loop update clause moving the counter in the right direction.", + description: "Enforce \"for\" loop update clause moving the counter in the right direction", recommended: true, - url: "https://eslint.org/docs/rules/for-direction" + url: "https://eslint.org/docs/latest/rules/for-direction" }, fixable: null, diff --git a/eslint/lib/rules/func-call-spacing.js b/eslint/lib/rules/func-call-spacing.js index fec6763..3d5e538 100644 --- a/eslint/lib/rules/func-call-spacing.js +++ b/eslint/lib/rules/func-call-spacing.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Require or disallow spacing between function identifiers and their invocations", recommended: false, - url: "https://eslint.org/docs/rules/func-call-spacing" + url: "https://eslint.org/docs/latest/rules/func-call-spacing" }, fixable: "whitespace", @@ -73,7 +73,7 @@ module.exports = { const never = context.options[0] !== "always"; const allowNewlines = !never && context.options[1] && context.options[1].allowNewlines; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const text = sourceCode.getText(); /** diff --git a/eslint/lib/rules/func-name-matching.js b/eslint/lib/rules/func-name-matching.js index 391b2a2..b9555d6 100644 --- a/eslint/lib/rules/func-name-matching.js +++ b/eslint/lib/rules/func-name-matching.js @@ -44,7 +44,7 @@ function isModuleExports(pattern) { * @returns {boolean} True if the string is a valid identifier */ function isIdentifier(name, ecmaVersion) { - if (ecmaVersion >= 6) { + if (ecmaVersion >= 2015) { return esutils.keyword.isIdentifierES6(name); } return esutils.keyword.isIdentifierES5(name); @@ -76,7 +76,7 @@ module.exports = { docs: { description: "Require function names to match the name of the variable or property to which they are assigned", recommended: false, - url: "https://eslint.org/docs/rules/func-name-matching" + url: "https://eslint.org/docs/latest/rules/func-name-matching" }, schema: { @@ -104,7 +104,7 @@ module.exports = { const nameMatches = typeof context.options[0] === "string" ? context.options[0] : "always"; const considerPropertyDescriptor = options.considerPropertyDescriptor; const includeModuleExports = options.includeCommonJSModuleExports; - const ecmaVersion = context.parserOptions && context.parserOptions.ecmaVersion ? context.parserOptions.ecmaVersion : 5; + const ecmaVersion = context.languageOptions.ecmaVersion; /** * Check whether node is a certain CallExpression. diff --git a/eslint/lib/rules/func-names.js b/eslint/lib/rules/func-names.js index ee46645..b180580 100644 --- a/eslint/lib/rules/func-names.js +++ b/eslint/lib/rules/func-names.js @@ -32,7 +32,7 @@ module.exports = { docs: { description: "Require or disallow named `function` expressions", recommended: false, - url: "https://eslint.org/docs/rules/func-names" + url: "https://eslint.org/docs/latest/rules/func-names" }, schema: { @@ -69,7 +69,7 @@ module.exports = { create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Returns the config option for the given node. @@ -159,7 +159,7 @@ module.exports = { function handleFunction(node) { // Skip recursive functions. - const nameVar = context.getDeclaredVariables(node)[0]; + const nameVar = sourceCode.getDeclaredVariables(node)[0]; if (isFunctionName(nameVar) && nameVar.references.length > 0) { return; diff --git a/eslint/lib/rules/func-style.js b/eslint/lib/rules/func-style.js index 0e1ba9f..ab83772 100644 --- a/eslint/lib/rules/func-style.js +++ b/eslint/lib/rules/func-style.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Enforce the consistent use of either `function` declarations or expressions", recommended: false, - url: "https://eslint.org/docs/rules/func-style" + url: "https://eslint.org/docs/latest/rules/func-style" }, schema: [ diff --git a/eslint/lib/rules/function-call-argument-newline.js b/eslint/lib/rules/function-call-argument-newline.js index 4661091..4462afd 100644 --- a/eslint/lib/rules/function-call-argument-newline.js +++ b/eslint/lib/rules/function-call-argument-newline.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Enforce line breaks between arguments of a function call", recommended: false, - url: "https://eslint.org/docs/rules/function-call-argument-newline" + url: "https://eslint.org/docs/latest/rules/function-call-argument-newline" }, fixable: "whitespace", @@ -35,7 +35,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const checkers = { unexpected: { diff --git a/eslint/lib/rules/function-paren-newline.js b/eslint/lib/rules/function-paren-newline.js index e61d17b..8a8714a 100644 --- a/eslint/lib/rules/function-paren-newline.js +++ b/eslint/lib/rules/function-paren-newline.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Enforce consistent line breaks inside function parentheses", recommended: false, - url: "https://eslint.org/docs/rules/function-paren-newline" + url: "https://eslint.org/docs/latest/rules/function-paren-newline" }, fixable: "whitespace", @@ -57,7 +57,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const rawOption = context.options[0] || "multiline"; const multilineOption = rawOption === "multiline"; const multilineArgumentsOption = rawOption === "multiline-arguments"; diff --git a/eslint/lib/rules/generator-star-spacing.js b/eslint/lib/rules/generator-star-spacing.js index d32b21f..81c0b61 100644 --- a/eslint/lib/rules/generator-star-spacing.js +++ b/eslint/lib/rules/generator-star-spacing.js @@ -33,7 +33,7 @@ module.exports = { docs: { description: "Enforce consistent spacing around `*` operators in generator functions", recommended: false, - url: "https://eslint.org/docs/rules/generator-star-spacing" + url: "https://eslint.org/docs/latest/rules/generator-star-spacing" }, fixable: "whitespace", @@ -102,7 +102,7 @@ module.exports = { }; }(context.options[0] || {})); - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Checks if the given token is a star token or not. diff --git a/eslint/lib/rules/getter-return.js b/eslint/lib/rules/getter-return.js index 5209ab1..622b6a7 100644 --- a/eslint/lib/rules/getter-return.js +++ b/eslint/lib/rules/getter-return.js @@ -37,7 +37,7 @@ module.exports = { docs: { description: "Enforce `return` statements in getters", recommended: true, - url: "https://eslint.org/docs/rules/getter-return" + url: "https://eslint.org/docs/latest/rules/getter-return" }, fixable: null, @@ -64,7 +64,7 @@ module.exports = { create(context) { const options = context.options[0] || { allowImplicit: false }; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; let funcInfo = { upper: null, @@ -112,18 +112,24 @@ module.exports = { } if (parent.type === "Property" && astUtils.getStaticPropertyName(parent) === "get" && parent.parent.type === "ObjectExpression") { - // Object.defineProperty() - if (parent.parent.parent.type === "CallExpression" && - astUtils.getStaticPropertyName(parent.parent.parent.callee) === "defineProperty") { - return true; + // Object.defineProperty() or Reflect.defineProperty() + if (parent.parent.parent.type === "CallExpression") { + const callNode = parent.parent.parent.callee; + + if (astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperty") || + astUtils.isSpecificMemberAccess(callNode, "Reflect", "defineProperty")) { + return true; + } } - // Object.defineProperties() + // Object.defineProperties() or Object.create() if (parent.parent.parent.type === "Property" && parent.parent.parent.parent.type === "ObjectExpression" && - parent.parent.parent.parent.parent.type === "CallExpression" && - astUtils.getStaticPropertyName(parent.parent.parent.parent.parent.callee) === "defineProperties") { - return true; + parent.parent.parent.parent.parent.type === "CallExpression") { + const callNode = parent.parent.parent.parent.parent.callee; + + return astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperties") || + astUtils.isSpecificMemberAccess(callNode, "Object", "create"); } } } diff --git a/eslint/lib/rules/global-require.js b/eslint/lib/rules/global-require.js index ceb0a8e..deae9d2 100644 --- a/eslint/lib/rules/global-require.js +++ b/eslint/lib/rules/global-require.js @@ -61,7 +61,7 @@ module.exports = { docs: { description: "Require `require()` calls to be placed at top-level module scope", recommended: false, - url: "https://eslint.org/docs/rules/global-require" + url: "https://eslint.org/docs/latest/rules/global-require" }, schema: [], @@ -71,12 +71,14 @@ module.exports = { }, create(context) { + const sourceCode = context.sourceCode; + return { CallExpression(node) { - const currentScope = context.getScope(); + const currentScope = sourceCode.getScope(node); if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) { - const isGoodRequire = context.getAncestors().every(parent => ACCEPTABLE_PARENTS.has(parent.type)); + const isGoodRequire = sourceCode.getAncestors(node).every(parent => ACCEPTABLE_PARENTS.has(parent.type)); if (!isGoodRequire) { context.report({ node, messageId: "unexpected" }); diff --git a/eslint/lib/rules/grouped-accessor-pairs.js b/eslint/lib/rules/grouped-accessor-pairs.js index 21374be..c08e1c4 100644 --- a/eslint/lib/rules/grouped-accessor-pairs.js +++ b/eslint/lib/rules/grouped-accessor-pairs.js @@ -98,7 +98,7 @@ module.exports = { docs: { description: "Require grouped accessor pairs in object literals and classes", recommended: false, - url: "https://eslint.org/docs/rules/grouped-accessor-pairs" + url: "https://eslint.org/docs/latest/rules/grouped-accessor-pairs" }, schema: [ @@ -115,7 +115,7 @@ module.exports = { create(context) { const order = context.options[0] || "anyOrder"; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Reports the given accessor pair. diff --git a/eslint/lib/rules/guard-for-in.js b/eslint/lib/rules/guard-for-in.js index 3b99143..d6e70d0 100644 --- a/eslint/lib/rules/guard-for-in.js +++ b/eslint/lib/rules/guard-for-in.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Require `for-in` loops to include an `if` statement", recommended: false, - url: "https://eslint.org/docs/rules/guard-for-in" + url: "https://eslint.org/docs/latest/rules/guard-for-in" }, schema: [], diff --git a/eslint/lib/rules/handle-callback-err.js b/eslint/lib/rules/handle-callback-err.js index 5189564..ad84931 100644 --- a/eslint/lib/rules/handle-callback-err.js +++ b/eslint/lib/rules/handle-callback-err.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Require error handling in callbacks", recommended: false, - url: "https://eslint.org/docs/rules/handle-callback-err" + url: "https://eslint.org/docs/latest/rules/handle-callback-err" }, schema: [ @@ -38,6 +38,7 @@ module.exports = { create(context) { const errorArgument = context.options[0] || "err"; + const sourceCode = context.sourceCode; /** * Checks if the given argument should be interpreted as a regexp pattern. @@ -79,7 +80,7 @@ module.exports = { * @returns {void} */ function checkForError(node) { - const scope = context.getScope(), + const scope = sourceCode.getScope(node), parameters = getParameters(scope), firstParameter = parameters[0]; diff --git a/eslint/lib/rules/id-blacklist.js b/eslint/lib/rules/id-blacklist.js index 5ea61e9..6b7f561 100644 --- a/eslint/lib/rules/id-blacklist.js +++ b/eslint/lib/rules/id-blacklist.js @@ -121,7 +121,7 @@ module.exports = { docs: { description: "Disallow specified identifiers", recommended: false, - url: "https://eslint.org/docs/rules/id-blacklist" + url: "https://eslint.org/docs/latest/rules/id-blacklist" }, schema: { @@ -140,6 +140,7 @@ module.exports = { const denyList = new Set(context.options); const reportedNodes = new Set(); + const sourceCode = context.sourceCode; let globalScope; @@ -231,8 +232,8 @@ module.exports = { return { - Program() { - globalScope = context.getScope(); + Program(node) { + globalScope = sourceCode.getScope(node); }, Identifier(node) { diff --git a/eslint/lib/rules/id-denylist.js b/eslint/lib/rules/id-denylist.js index fe0a0b5..baaa65f 100644 --- a/eslint/lib/rules/id-denylist.js +++ b/eslint/lib/rules/id-denylist.js @@ -101,7 +101,7 @@ module.exports = { docs: { description: "Disallow specified identifiers", recommended: false, - url: "https://eslint.org/docs/rules/id-denylist" + url: "https://eslint.org/docs/latest/rules/id-denylist" }, schema: { @@ -121,6 +121,7 @@ module.exports = { const denyList = new Set(context.options); const reportedNodes = new Set(); + const sourceCode = context.sourceCode; let globalScope; @@ -210,8 +211,8 @@ module.exports = { return { - Program() { - globalScope = context.getScope(); + Program(node) { + globalScope = sourceCode.getScope(node); }, [[ diff --git a/eslint/lib/rules/id-length.js b/eslint/lib/rules/id-length.js index 99f833f..97bc0e4 100644 --- a/eslint/lib/rules/id-length.js +++ b/eslint/lib/rules/id-length.js @@ -6,6 +6,12 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { getGraphemeCount } = require("../shared/string-utils"); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -18,7 +24,7 @@ module.exports = { docs: { description: "Enforce minimum and maximum identifier lengths", recommended: false, - url: "https://eslint.org/docs/rules/id-length" + url: "https://eslint.org/docs/latest/rules/id-length" }, schema: [ @@ -130,8 +136,10 @@ module.exports = { const name = node.name; const parent = node.parent; - const isShort = name.length < minLength; - const isLong = name.length > maxLength; + const nameLength = getGraphemeCount(name); + + const isShort = nameLength < minLength; + const isLong = nameLength > maxLength; if (!(isShort || isLong) || exceptions.has(name) || matchesExceptionPattern(name)) { return; // Nothing to report diff --git a/eslint/lib/rules/id-match.js b/eslint/lib/rules/id-match.js index ec87af1..e225454 100644 --- a/eslint/lib/rules/id-match.js +++ b/eslint/lib/rules/id-match.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Require identifiers to match a specified regular expression", recommended: false, - url: "https://eslint.org/docs/rules/id-match" + url: "https://eslint.org/docs/latest/rules/id-match" }, schema: [ @@ -67,6 +67,7 @@ module.exports = { onlyDeclarations = !!options.onlyDeclarations, ignoreDestructuring = !!options.ignoreDestructuring; + const sourceCode = context.sourceCode; let globalScope; //-------------------------------------------------------------------------- @@ -170,8 +171,8 @@ module.exports = { return { - Program() { - globalScope = context.getScope(); + Program(node) { + globalScope = sourceCode.getScope(node); }, Identifier(node) { diff --git a/eslint/lib/rules/implicit-arrow-linebreak.js b/eslint/lib/rules/implicit-arrow-linebreak.js index c765960..30ab1a5 100644 --- a/eslint/lib/rules/implicit-arrow-linebreak.js +++ b/eslint/lib/rules/implicit-arrow-linebreak.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Enforce the location of arrow function bodies", recommended: false, - url: "https://eslint.org/docs/rules/implicit-arrow-linebreak" + url: "https://eslint.org/docs/latest/rules/implicit-arrow-linebreak" }, fixable: "whitespace", @@ -34,7 +34,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const option = context.options[0] || "beside"; /** diff --git a/eslint/lib/rules/indent-legacy.js b/eslint/lib/rules/indent-legacy.js index d4793bd..78bf965 100644 --- a/eslint/lib/rules/indent-legacy.js +++ b/eslint/lib/rules/indent-legacy.js @@ -28,7 +28,7 @@ module.exports = { docs: { description: "Enforce consistent indentation", recommended: false, - url: "https://eslint.org/docs/rules/indent-legacy" + url: "https://eslint.org/docs/latest/rules/indent-legacy" }, deprecated: true, @@ -206,7 +206,7 @@ module.exports = { ObjectExpression: 1 }; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; if (context.options.length) { if (context.options[0] === "tab") { diff --git a/eslint/lib/rules/indent.js b/eslint/lib/rules/indent.js index cda0203..bcc5143 100644 --- a/eslint/lib/rules/indent.js +++ b/eslint/lib/rules/indent.js @@ -12,8 +12,6 @@ // Requirements //------------------------------------------------------------------------------ -const { OrderedMap } = require("js-sdsl"); - const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ @@ -125,43 +123,48 @@ const KNOWN_NODES = new Set([ /** - * A mutable balanced binary search tree that stores (key, value) pairs. The keys are numeric, and must be unique. - * This is intended to be a generic wrapper around a balanced binary search tree library, so that the underlying implementation + * A mutable map that stores (key, value) pairs. The keys are numeric indices, and must be unique. + * This is intended to be a generic wrapper around a map with non-negative integer keys, so that the underlying implementation * can easily be swapped out. */ -class BinarySearchTree { +class IndexMap { /** - * Creates an empty tree + * Creates an empty map + * @param {number} maxKey The maximum key */ - constructor() { - this._orderedMap = new OrderedMap(); - this._orderedMapEnd = this._orderedMap.end(); + constructor(maxKey) { + + // Initializing the array with the maximum expected size avoids dynamic reallocations that could degrade performance. + this._values = Array(maxKey + 1); } /** - * Inserts an entry into the tree. + * Inserts an entry into the map. * @param {number} key The entry's key * @param {any} value The entry's value * @returns {void} */ insert(key, value) { - this._orderedMap.setElement(key, value); + this._values[key] = value; } /** - * Finds the entry with the largest key less than or equal to the provided key + * Finds the value of the entry with the largest key less than or equal to the provided key * @param {number} key The provided key - * @returns {{key: number, value: *}|null} The found entry, or null if no such entry exists. + * @returns {*|undefined} The value of the found entry, or undefined if no such entry exists. */ - findLe(key) { - const iterator = this._orderedMap.reverseLowerBound(key); + findLastNotAfter(key) { + const values = this._values; - if (iterator.equals(this._orderedMapEnd)) { - return {}; + for (let index = key; index >= 0; index--) { + const value = values[index]; + + if (value) { + return value; + } } - - return { key: iterator.pointer[0], value: iterator.pointer[1] }; + return void 0; } /** @@ -171,26 +174,7 @@ class BinarySearchTree { * @returns {void} */ deleteRange(start, end) { - - // Exit without traversing the tree if the range has zero size. - if (start === end) { - return; - } - const iterator = this._orderedMap.lowerBound(start); - - if (iterator.equals(this._orderedMapEnd)) { - return; - } - - if (end > this._orderedMap.back()[0]) { - while (!iterator.equals(this._orderedMapEnd)) { - this._orderedMap.eraseElementByIterator(iterator); - } - } else { - while (iterator.pointer[0] < end) { - this._orderedMap.eraseElementByIterator(iterator); - } - } + this._values.fill(void 0, start, end); } } @@ -252,14 +236,15 @@ class OffsetStorage { * @param {TokenInfo} tokenInfo a TokenInfo instance * @param {number} indentSize The desired size of each indentation level * @param {string} indentType The indentation character + * @param {number} maxIndex The maximum end index of any token */ - constructor(tokenInfo, indentSize, indentType) { + constructor(tokenInfo, indentSize, indentType, maxIndex) { this._tokenInfo = tokenInfo; this._indentSize = indentSize; this._indentType = indentType; - this._tree = new BinarySearchTree(); - this._tree.insert(0, { offset: 0, from: null, force: false }); + this._indexMap = new IndexMap(maxIndex); + this._indexMap.insert(0, { offset: 0, from: null, force: false }); this._lockedFirstTokens = new WeakMap(); this._desiredIndentCache = new WeakMap(); @@ -267,7 +252,7 @@ class OffsetStorage { } _getOffsetDescriptor(token) { - return this._tree.findLe(token.range[0]).value; + return this._indexMap.findLastNotAfter(token.range[0]); } /** @@ -388,37 +373,36 @@ class OffsetStorage { * * key: 820, value: { offset: 1, from: bazToken } * * To find the offset descriptor for any given token, one needs to find the node with the largest key - * which is <= token.start. To make this operation fast, the nodes are stored in a balanced binary - * search tree indexed by key. + * which is <= token.start. To make this operation fast, the nodes are stored in a map indexed by key. */ const descriptorToInsert = { offset, from: fromToken, force }; - const descriptorAfterRange = this._tree.findLe(range[1]).value; + const descriptorAfterRange = this._indexMap.findLastNotAfter(range[1]); const fromTokenIsInRange = fromToken && fromToken.range[0] >= range[0] && fromToken.range[1] <= range[1]; const fromTokenDescriptor = fromTokenIsInRange && this._getOffsetDescriptor(fromToken); - // First, remove any existing nodes in the range from the tree. - this._tree.deleteRange(range[0] + 1, range[1]); + // First, remove any existing nodes in the range from the map. + this._indexMap.deleteRange(range[0] + 1, range[1]); - // Insert a new node into the tree for this range - this._tree.insert(range[0], descriptorToInsert); + // Insert a new node into the map for this range + this._indexMap.insert(range[0], descriptorToInsert); /* * To avoid circular offset dependencies, keep the `fromToken` token mapped to whatever it was mapped to previously, * even if it's in the current range. */ if (fromTokenIsInRange) { - this._tree.insert(fromToken.range[0], fromTokenDescriptor); - this._tree.insert(fromToken.range[1], descriptorToInsert); + this._indexMap.insert(fromToken.range[0], fromTokenDescriptor); + this._indexMap.insert(fromToken.range[1], descriptorToInsert); } /* * To avoid modifying the offset of tokens after the range, insert another node to keep the offset of the following * tokens the same as it was before. */ - this._tree.insert(range[1], descriptorAfterRange); + this._indexMap.insert(range[1], descriptorAfterRange); } /** @@ -510,7 +494,7 @@ module.exports = { docs: { description: "Enforce consistent indentation", recommended: false, - url: "https://eslint.org/docs/rules/indent" + url: "https://eslint.org/docs/latest/rules/indent" }, fixable: "whitespace", @@ -703,9 +687,9 @@ module.exports = { } } - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const tokenInfo = new TokenInfo(sourceCode); - const offsets = new OffsetStorage(tokenInfo, indentSize, indentType === "space" ? " " : "\t"); + const offsets = new OffsetStorage(tokenInfo, indentSize, indentType === "space" ? " " : "\t", sourceCode.text.length); const parameterParens = new WeakSet(); /** diff --git a/eslint/lib/rules/index.js b/eslint/lib/rules/index.js index aef47f5..e426396 100644 --- a/eslint/lib/rules/index.js +++ b/eslint/lib/rules/index.js @@ -72,6 +72,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "lines-around-comment": () => require("./lines-around-comment"), "lines-around-directive": () => require("./lines-around-directive"), "lines-between-class-members": () => require("./lines-between-class-members"), + "logical-assignment-operators": () => require("./logical-assignment-operators"), "max-classes-per-file": () => require("./max-classes-per-file"), "max-depth": () => require("./max-depth"), "max-len": () => require("./max-len"), @@ -122,6 +123,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "no-empty-character-class": () => require("./no-empty-character-class"), "no-empty-function": () => require("./no-empty-function"), "no-empty-pattern": () => require("./no-empty-pattern"), + "no-empty-static-block": () => require("./no-empty-static-block"), "no-eq-null": () => require("./no-eq-null"), "no-eval": () => require("./no-eval"), "no-ex-assign": () => require("./no-ex-assign"), @@ -166,6 +168,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "no-nested-ternary": () => require("./no-nested-ternary"), "no-new": () => require("./no-new"), "no-new-func": () => require("./no-new-func"), + "no-new-native-nonconstructor": () => require("./no-new-native-nonconstructor"), "no-new-object": () => require("./no-new-object"), "no-new-require": () => require("./no-new-require"), "no-new-symbol": () => require("./no-new-symbol"), diff --git a/eslint/lib/rules/init-declarations.js b/eslint/lib/rules/init-declarations.js index b2ddf64..3abe107 100644 --- a/eslint/lib/rules/init-declarations.js +++ b/eslint/lib/rules/init-declarations.js @@ -50,7 +50,7 @@ module.exports = { docs: { description: "Require or disallow initialization in variable declarations", recommended: false, - url: "https://eslint.org/docs/rules/init-declarations" + url: "https://eslint.org/docs/latest/rules/init-declarations" }, schema: { diff --git a/eslint/lib/rules/jsx-quotes.js b/eslint/lib/rules/jsx-quotes.js index 6745bb6..a41c851 100644 --- a/eslint/lib/rules/jsx-quotes.js +++ b/eslint/lib/rules/jsx-quotes.js @@ -44,7 +44,7 @@ module.exports = { docs: { description: "Enforce the consistent use of either double or single quotes in JSX attributes", recommended: false, - url: "https://eslint.org/docs/rules/jsx-quotes" + url: "https://eslint.org/docs/latest/rules/jsx-quotes" }, fixable: "whitespace", diff --git a/eslint/lib/rules/key-spacing.js b/eslint/lib/rules/key-spacing.js index b764b72..0b51eb3 100644 --- a/eslint/lib/rules/key-spacing.js +++ b/eslint/lib/rules/key-spacing.js @@ -9,13 +9,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const GraphemeSplitter = require("grapheme-splitter"); - -const splitter = new GraphemeSplitter(); - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ +const { getGraphemeCount } = require("../shared/string-utils"); /** * Checks whether a string contains a line terminator as defined in @@ -144,7 +138,7 @@ module.exports = { docs: { description: "Enforce consistent spacing between keys and values in object literal properties", recommended: false, - url: "https://eslint.org/docs/rules/key-spacing" + url: "https://eslint.org/docs/latest/rules/key-spacing" }, fixable: "whitespace", @@ -332,44 +326,7 @@ module.exports = { singleLineOptions = ruleOptions.singleLine, alignmentOptions = ruleOptions.align || null; - const sourceCode = context.getSourceCode(); - - /** - * Checks whether a property is a member of the property group it follows. - * @param {ASTNode} lastMember The last Property known to be in the group. - * @param {ASTNode} candidate The next Property that might be in the group. - * @returns {boolean} True if the candidate property is part of the group. - */ - function continuesPropertyGroup(lastMember, candidate) { - const groupEndLine = lastMember.loc.start.line, - candidateStartLine = candidate.loc.start.line; - - if (candidateStartLine - groupEndLine <= 1) { - return true; - } - - /* - * Check that the first comment is adjacent to the end of the group, the - * last comment is adjacent to the candidate property, and that successive - * comments are adjacent to each other. - */ - const leadingComments = sourceCode.getCommentsBefore(candidate); - - if ( - leadingComments.length && - leadingComments[0].loc.start.line - groupEndLine <= 1 && - candidateStartLine - last(leadingComments).loc.end.line <= 1 - ) { - for (let i = 1; i < leadingComments.length; i++) { - if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) { - return false; - } - } - return true; - } - - return false; - } + const sourceCode = context.sourceCode; /** * Determines if the given property is key-value property. @@ -385,19 +342,7 @@ module.exports = { } /** - * Starting from the given a node (a property.key node here) looks forward - * until it finds the last token before a colon punctuator and returns it. - * @param {ASTNode} node The node to start looking from. - * @returns {ASTNode} The last token before a colon punctuator. - */ - function getLastTokenBeforeColon(node) { - const colonToken = sourceCode.getTokenAfter(node, astUtils.isColonToken); - - return sourceCode.getTokenBefore(colonToken); - } - - /** - * Starting from the given a node (a property.key node here) looks forward + * Starting from the given node (a property.key node here) looks forward * until it finds the colon punctuator and returns it. * @param {ASTNode} node The node to start looking from. * @returns {ASTNode} The colon punctuator. @@ -406,6 +351,67 @@ module.exports = { return sourceCode.getTokenAfter(node, astUtils.isColonToken); } + /** + * Starting from the given node (a property.key node here) looks forward + * until it finds the last token before a colon punctuator and returns it. + * @param {ASTNode} node The node to start looking from. + * @returns {ASTNode} The last token before a colon punctuator. + */ + function getLastTokenBeforeColon(node) { + const colonToken = getNextColon(node); + + return sourceCode.getTokenBefore(colonToken); + } + + /** + * Starting from the given node (a property.key node here) looks forward + * until it finds the first token after a colon punctuator and returns it. + * @param {ASTNode} node The node to start looking from. + * @returns {ASTNode} The first token after a colon punctuator. + */ + function getFirstTokenAfterColon(node) { + const colonToken = getNextColon(node); + + return sourceCode.getTokenAfter(colonToken); + } + + /** + * Checks whether a property is a member of the property group it follows. + * @param {ASTNode} lastMember The last Property known to be in the group. + * @param {ASTNode} candidate The next Property that might be in the group. + * @returns {boolean} True if the candidate property is part of the group. + */ + function continuesPropertyGroup(lastMember, candidate) { + const groupEndLine = lastMember.loc.start.line, + candidateValueStartLine = (isKeyValueProperty(candidate) ? getFirstTokenAfterColon(candidate.key) : candidate).loc.start.line; + + if (candidateValueStartLine - groupEndLine <= 1) { + return true; + } + + /* + * Check that the first comment is adjacent to the end of the group, the + * last comment is adjacent to the candidate property, and that successive + * comments are adjacent to each other. + */ + const leadingComments = sourceCode.getCommentsBefore(candidate); + + if ( + leadingComments.length && + leadingComments[0].loc.start.line - groupEndLine <= 1 && + candidateValueStartLine - last(leadingComments).loc.end.line <= 1 + ) { + for (let i = 1; i < leadingComments.length; i++) { + if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) { + return false; + } + } + return true; + } + + return false; + } + /** * Gets an object literal property's key as the identifier name or string value. * @param {ASTNode} property Property node whose key to retrieve. @@ -511,7 +517,7 @@ module.exports = { const startToken = sourceCode.getFirstToken(property); const endToken = getLastTokenBeforeColon(property.key); - return splitter.countGraphemes(sourceCode.getText().slice(startToken.range[0], endToken.range[1])); + return getGraphemeCount(sourceCode.getText().slice(startToken.range[0], endToken.range[1])); } /** diff --git a/eslint/lib/rules/keyword-spacing.js b/eslint/lib/rules/keyword-spacing.js index 59a500f..8ed8219 100644 --- a/eslint/lib/rules/keyword-spacing.js +++ b/eslint/lib/rules/keyword-spacing.js @@ -69,7 +69,7 @@ module.exports = { docs: { description: "Enforce consistent spacing before and after keywords", recommended: false, - url: "https://eslint.org/docs/rules/keyword-spacing" + url: "https://eslint.org/docs/latest/rules/keyword-spacing" }, fixable: "whitespace", @@ -108,7 +108,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const tokensToIgnore = new WeakSet(); diff --git a/eslint/lib/rules/line-comment-position.js b/eslint/lib/rules/line-comment-position.js index 0631ebe..314fac1 100644 --- a/eslint/lib/rules/line-comment-position.js +++ b/eslint/lib/rules/line-comment-position.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Enforce position of line comments", recommended: false, - url: "https://eslint.org/docs/rules/line-comment-position" + url: "https://eslint.org/docs/latest/rules/line-comment-position" }, schema: [ @@ -78,7 +78,7 @@ module.exports = { const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN; const fallThroughRegExp = /^\s*falls?\s?through/u; const customIgnoreRegExp = new RegExp(ignorePattern, "u"); - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Public diff --git a/eslint/lib/rules/linebreak-style.js b/eslint/lib/rules/linebreak-style.js index a5dc39d..d8f3609 100644 --- a/eslint/lib/rules/linebreak-style.js +++ b/eslint/lib/rules/linebreak-style.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Enforce consistent linebreak style", recommended: false, - url: "https://eslint.org/docs/rules/linebreak-style" + url: "https://eslint.org/docs/latest/rules/linebreak-style" }, fixable: "whitespace", @@ -40,7 +40,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers diff --git a/eslint/lib/rules/lines-around-comment.js b/eslint/lib/rules/lines-around-comment.js index bd7d1cd..10aeba3 100644 --- a/eslint/lib/rules/lines-around-comment.js +++ b/eslint/lib/rules/lines-around-comment.js @@ -15,7 +15,7 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ /** - * Return an array with with any line numbers that are empty. + * Return an array with any line numbers that are empty. * @param {Array} lines An array of each line of the file. * @returns {Array} An array of line numbers. */ @@ -29,7 +29,7 @@ function getEmptyLineNums(lines) { } /** - * Return an array with with any line numbers that contain comments. + * Return an array with any line numbers that contain comments. * @param {Array} comments An array of comment tokens. * @returns {Array} An array of line numbers. */ @@ -57,7 +57,7 @@ module.exports = { docs: { description: "Require empty lines around comments", recommended: false, - url: "https://eslint.org/docs/rules/lines-around-comment" + url: "https://eslint.org/docs/latest/rules/lines-around-comment" }, fixable: "whitespace", @@ -113,6 +113,10 @@ module.exports = { }, applyDefaultIgnorePatterns: { type: "boolean" + }, + afterHashbangComment: { + type: "boolean", + default: false } }, additionalProperties: false @@ -134,7 +138,7 @@ module.exports = { options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const lines = sourceCode.lines, numLines = lines.length + 1, @@ -449,6 +453,13 @@ module.exports = { before: options.beforeBlockComment }); } + } else if (token.type === "Shebang") { + if (options.afterHashbangComment) { + checkForEmptyLine(token, { + after: options.afterHashbangComment, + before: false + }); + } } }); } diff --git a/eslint/lib/rules/lines-around-directive.js b/eslint/lib/rules/lines-around-directive.js index 816efc9..1c82d8f 100644 --- a/eslint/lib/rules/lines-around-directive.js +++ b/eslint/lib/rules/lines-around-directive.js @@ -20,7 +20,7 @@ module.exports = { docs: { description: "Require or disallow newlines around directives", recommended: false, - url: "https://eslint.org/docs/rules/lines-around-directive" + url: "https://eslint.org/docs/latest/rules/lines-around-directive" }, schema: [{ @@ -54,7 +54,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const config = context.options[0] || "always"; const expectLineBefore = typeof config === "string" ? config : config.before; const expectLineAfter = typeof config === "string" ? config : config.after; diff --git a/eslint/lib/rules/lines-between-class-members.js b/eslint/lib/rules/lines-between-class-members.js index 26357aa..dee4bab 100644 --- a/eslint/lib/rules/lines-between-class-members.js +++ b/eslint/lib/rules/lines-between-class-members.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Require or disallow an empty line between class members", recommended: false, - url: "https://eslint.org/docs/rules/lines-between-class-members" + url: "https://eslint.org/docs/latest/rules/lines-between-class-members" }, fixable: "whitespace", @@ -55,7 +55,7 @@ module.exports = { options[0] = context.options[0] || "always"; options[1] = context.options[1] || { exceptAfterSingleLine: false }; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Gets a pair of tokens that should be used to check lines between two class member nodes. diff --git a/eslint/lib/rules/logical-assignment-operators.js b/eslint/lib/rules/logical-assignment-operators.js new file mode 100644 index 0000000..d373bec --- /dev/null +++ b/eslint/lib/rules/logical-assignment-operators.js @@ -0,0 +1,474 @@ +/** + * @fileoverview Rule to replace assignment expressions with logical operator assignment + * @author Daniel Martens + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ +const astUtils = require("./utils/ast-utils.js"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const baseTypes = new Set(["Identifier", "Super", "ThisExpression"]); + +/** + * Returns true iff either "undefined" or a void expression (eg. "void 0") + * @param {ASTNode} expression Expression to check + * @param {import('eslint-scope').Scope} scope Scope of the expression + * @returns {boolean} True iff "undefined" or "void ..." + */ +function isUndefined(expression, scope) { + if (expression.type === "Identifier" && expression.name === "undefined") { + return astUtils.isReferenceToGlobalVariable(scope, expression); + } + + return expression.type === "UnaryExpression" && + expression.operator === "void" && + expression.argument.type === "Literal" && + expression.argument.value === 0; +} + +/** + * Returns true iff the reference is either an identifier or member expression + * @param {ASTNode} expression Expression to check + * @returns {boolean} True for identifiers and member expressions + */ +function isReference(expression) { + return (expression.type === "Identifier" && expression.name !== "undefined") || + expression.type === "MemberExpression"; +} + +/** + * Returns true iff the expression checks for nullish with loose equals. + * Examples: value == null, value == void 0 + * @param {ASTNode} expression Test condition + * @param {import('eslint-scope').Scope} scope Scope of the expression + * @returns {boolean} True iff implicit nullish comparison + */ +function isImplicitNullishComparison(expression, scope) { + if (expression.type !== "BinaryExpression" || expression.operator !== "==") { + return false; + } + + const reference = isReference(expression.left) ? "left" : "right"; + const nullish = reference === "left" ? "right" : "left"; + + return isReference(expression[reference]) && + (astUtils.isNullLiteral(expression[nullish]) || isUndefined(expression[nullish], scope)); +} + +/** + * Condition with two equal comparisons. + * @param {ASTNode} expression Condition + * @returns {boolean} True iff matches ? === ? || ? === ? + */ +function isDoubleComparison(expression) { + return expression.type === "LogicalExpression" && + expression.operator === "||" && + expression.left.type === "BinaryExpression" && + expression.left.operator === "===" && + expression.right.type === "BinaryExpression" && + expression.right.operator === "==="; +} + +/** + * Returns true iff the expression checks for undefined and null. + * Example: value === null || value === undefined + * @param {ASTNode} expression Test condition + * @param {import('eslint-scope').Scope} scope Scope of the expression + * @returns {boolean} True iff explicit nullish comparison + */ +function isExplicitNullishComparison(expression, scope) { + if (!isDoubleComparison(expression)) { + return false; + } + const leftReference = isReference(expression.left.left) ? "left" : "right"; + const leftNullish = leftReference === "left" ? "right" : "left"; + const rightReference = isReference(expression.right.left) ? "left" : "right"; + const rightNullish = rightReference === "left" ? "right" : "left"; + + return astUtils.isSameReference(expression.left[leftReference], expression.right[rightReference]) && + ((astUtils.isNullLiteral(expression.left[leftNullish]) && isUndefined(expression.right[rightNullish], scope)) || + (isUndefined(expression.left[leftNullish], scope) && astUtils.isNullLiteral(expression.right[rightNullish]))); +} + +/** + * Returns true for Boolean(arg) calls + * @param {ASTNode} expression Test condition + * @param {import('eslint-scope').Scope} scope Scope of the expression + * @returns {boolean} Whether the expression is a boolean cast + */ +function isBooleanCast(expression, scope) { + return expression.type === "CallExpression" && + expression.callee.name === "Boolean" && + expression.arguments.length === 1 && + astUtils.isReferenceToGlobalVariable(scope, expression.callee); +} + +/** + * Returns true for: + * truthiness checks: value, Boolean(value), !!value + * falsiness checks: !value, !Boolean(value) + * nullish checks: value == null, value === undefined || value === null + * @param {ASTNode} expression Test condition + * @param {import('eslint-scope').Scope} scope Scope of the expression + * @returns {?{ reference: ASTNode, operator: '??'|'||'|'&&'}} Null if not a known existence + */ +function getExistence(expression, scope) { + const isNegated = expression.type === "UnaryExpression" && expression.operator === "!"; + const base = isNegated ? expression.argument : expression; + + switch (true) { + case isReference(base): + return { reference: base, operator: isNegated ? "||" : "&&" }; + case base.type === "UnaryExpression" && base.operator === "!" && isReference(base.argument): + return { reference: base.argument, operator: "&&" }; + case isBooleanCast(base, scope) && isReference(base.arguments[0]): + return { reference: base.arguments[0], operator: isNegated ? "||" : "&&" }; + case isImplicitNullishComparison(expression, scope): + return { reference: isReference(expression.left) ? expression.left : expression.right, operator: "??" }; + case isExplicitNullishComparison(expression, scope): + return { reference: isReference(expression.left.left) ? expression.left.left : expression.left.right, operator: "??" }; + default: return null; + } +} + +/** + * Returns true iff the node is inside a with block + * @param {ASTNode} node Node to check + * @returns {boolean} True iff passed node is inside a with block + */ +function isInsideWithBlock(node) { + if (node.type === "Program") { + return false; + } + + return node.parent.type === "WithStatement" && node.parent.body === node ? true : isInsideWithBlock(node.parent); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +/** @type {import('../shared/types').Rule} */ +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "Require or disallow logical assignment operator shorthand", + recommended: false, + url: "https://eslint.org/docs/latest/rules/logical-assignment-operators" + }, + + schema: { + type: "array", + oneOf: [{ + items: [ + { const: "always" }, + { + type: "object", + properties: { + enforceForIfStatements: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + minItems: 0, // 0 for allowing passing no options + maxItems: 2 + }, { + items: [{ const: "never" }], + minItems: 1, + maxItems: 1 + }] + }, + fixable: "code", + // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- Does not detect conditional suggestions + hasSuggestions: true, + messages: { + assignment: "Assignment (=) can be replaced with operator assignment ({{operator}}).", + useLogicalOperator: "Convert this assignment to use the operator {{ operator }}.", + logical: "Logical expression can be replaced with an assignment ({{ operator }}).", + convertLogical: "Replace this logical expression with an assignment with the operator {{ operator }}.", + if: "'if' statement can be replaced with a logical operator assignment with operator {{ operator }}.", + convertIf: "Replace this 'if' statement with a logical assignment with operator {{ operator }}.", + unexpected: "Unexpected logical operator assignment ({{operator}}) shorthand.", + separate: "Separate the logical assignment into an assignment with a logical operator." + } + }, + + create(context) { + const mode = context.options[0] === "never" ? "never" : "always"; + const checkIf = mode === "always" && context.options.length > 1 && context.options[1].enforceForIfStatements; + const sourceCode = context.sourceCode; + const isStrict = sourceCode.getScope(sourceCode.ast).isStrict; + + /** + * Returns false if the access could be a getter + * @param {ASTNode} node Assignment expression + * @returns {boolean} True iff the fix is safe + */ + function cannotBeGetter(node) { + return node.type === "Identifier" && + (isStrict || !isInsideWithBlock(node)); + } + + /** + * Check whether only a single property is accessed + * @param {ASTNode} node reference + * @returns {boolean} True iff a single property is accessed + */ + function accessesSingleProperty(node) { + if (!isStrict && isInsideWithBlock(node)) { + return node.type === "Identifier"; + } + + return node.type === "MemberExpression" && + baseTypes.has(node.object.type) && + (!node.computed || (node.property.type !== "MemberExpression" && node.property.type !== "ChainExpression")); + } + + /** + * Adds a fixer or suggestion whether on the fix is safe. + * @param {{ messageId: string, node: ASTNode }} descriptor Report descriptor without fix or suggest + * @param {{ messageId: string, fix: Function }} suggestion Adds the fix or the whole suggestion as only element in "suggest" to suggestion + * @param {boolean} shouldBeFixed Fix iff the condition is true + * @returns {Object} Descriptor with either an added fix or suggestion + */ + function createConditionalFixer(descriptor, suggestion, shouldBeFixed) { + if (shouldBeFixed) { + return { + ...descriptor, + fix: suggestion.fix + }; + } + + return { + ...descriptor, + suggest: [suggestion] + }; + } + + + /** + * Returns the operator token for assignments and binary expressions + * @param {ASTNode} node AssignmentExpression or BinaryExpression + * @returns {import('eslint').AST.Token} Operator token between the left and right expression + */ + function getOperatorToken(node) { + return sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator); + } + + if (mode === "never") { + return { + + // foo ||= bar + "AssignmentExpression"(assignment) { + if (!astUtils.isLogicalAssignmentOperator(assignment.operator)) { + return; + } + + const descriptor = { + messageId: "unexpected", + node: assignment, + data: { operator: assignment.operator } + }; + const suggestion = { + messageId: "separate", + *fix(ruleFixer) { + if (sourceCode.getCommentsInside(assignment).length > 0) { + return; + } + + const operatorToken = getOperatorToken(assignment); + + // -> foo = bar + yield ruleFixer.replaceText(operatorToken, "="); + + const assignmentText = sourceCode.getText(assignment.left); + const operator = assignment.operator.slice(0, -1); + + // -> foo = foo || bar + yield ruleFixer.insertTextAfter(operatorToken, ` ${assignmentText} ${operator}`); + + const precedence = astUtils.getPrecedence(assignment.right) <= astUtils.getPrecedence({ type: "LogicalExpression", operator }); + + // ?? and || / && cannot be mixed but have same precedence + const mixed = assignment.operator === "??=" && astUtils.isLogicalExpression(assignment.right); + + if (!astUtils.isParenthesised(sourceCode, assignment.right) && (precedence || mixed)) { + + // -> foo = foo || (bar) + yield ruleFixer.insertTextBefore(assignment.right, "("); + yield ruleFixer.insertTextAfter(assignment.right, ")"); + } + } + }; + + context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left))); + } + }; + } + + return { + + // foo = foo || bar + "AssignmentExpression[operator='='][right.type='LogicalExpression']"(assignment) { + if (!astUtils.isSameReference(assignment.left, assignment.right.left)) { + return; + } + + const descriptor = { + messageId: "assignment", + node: assignment, + data: { operator: `${assignment.right.operator}=` } + }; + const suggestion = { + messageId: "useLogicalOperator", + data: { operator: `${assignment.right.operator}=` }, + *fix(ruleFixer) { + if (sourceCode.getCommentsInside(assignment).length > 0) { + return; + } + + // No need for parenthesis around the assignment based on precedence as the precedence stays the same even with changed operator + const assignmentOperatorToken = getOperatorToken(assignment); + + // -> foo ||= foo || bar + yield ruleFixer.insertTextBefore(assignmentOperatorToken, assignment.right.operator); + + // -> foo ||= bar + const logicalOperatorToken = getOperatorToken(assignment.right); + const firstRightOperandToken = sourceCode.getTokenAfter(logicalOperatorToken); + + yield ruleFixer.removeRange([assignment.right.range[0], firstRightOperandToken.range[0]]); + } + }; + + context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left))); + }, + + // foo || (foo = bar) + 'LogicalExpression[right.type="AssignmentExpression"][right.operator="="]'(logical) { + + // Right side has to be parenthesized, otherwise would be parsed as (foo || foo) = bar which is illegal + if (isReference(logical.left) && astUtils.isSameReference(logical.left, logical.right.left)) { + const descriptor = { + messageId: "logical", + node: logical, + data: { operator: `${logical.operator}=` } + }; + const suggestion = { + messageId: "convertLogical", + data: { operator: `${logical.operator}=` }, + *fix(ruleFixer) { + if (sourceCode.getCommentsInside(logical).length > 0) { + return; + } + + const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" && + (astUtils.getPrecedence({ type: "AssignmentExpression" }) < astUtils.getPrecedence(logical.parent)); + + if (!astUtils.isParenthesised(sourceCode, logical) && requiresOuterParenthesis) { + yield ruleFixer.insertTextBefore(logical, "("); + yield ruleFixer.insertTextAfter(logical, ")"); + } + + // Also removes all opening parenthesis + yield ruleFixer.removeRange([logical.range[0], logical.right.range[0]]); // -> foo = bar) + + // Also removes all ending parenthesis + yield ruleFixer.removeRange([logical.right.range[1], logical.range[1]]); // -> foo = bar + + const operatorToken = getOperatorToken(logical.right); + + yield ruleFixer.insertTextBefore(operatorToken, logical.operator); // -> foo ||= bar + } + }; + const fix = cannotBeGetter(logical.left) || accessesSingleProperty(logical.left); + + context.report(createConditionalFixer(descriptor, suggestion, fix)); + } + }, + + // if (foo) foo = bar + "IfStatement[alternate=null]"(ifNode) { + if (!checkIf) { + return; + } + + const hasBody = ifNode.consequent.type === "BlockStatement"; + + if (hasBody && ifNode.consequent.body.length !== 1) { + return; + } + + const body = hasBody ? ifNode.consequent.body[0] : ifNode.consequent; + const scope = sourceCode.getScope(ifNode); + const existence = getExistence(ifNode.test, scope); + + if ( + body.type === "ExpressionStatement" && + body.expression.type === "AssignmentExpression" && + body.expression.operator === "=" && + existence !== null && + astUtils.isSameReference(existence.reference, body.expression.left) + ) { + const descriptor = { + messageId: "if", + node: ifNode, + data: { operator: `${existence.operator}=` } + }; + const suggestion = { + messageId: "convertIf", + data: { operator: `${existence.operator}=` }, + *fix(ruleFixer) { + if (sourceCode.getCommentsInside(ifNode).length > 0) { + return; + } + + const firstBodyToken = sourceCode.getFirstToken(body); + const prevToken = sourceCode.getTokenBefore(ifNode); + + if ( + prevToken !== null && + prevToken.value !== ";" && + prevToken.value !== "{" && + firstBodyToken.type !== "Identifier" && + firstBodyToken.type !== "Keyword" + ) { + + // Do not fix if the fixed statement could be part of the previous statement (eg. fn() if (a == null) (a) = b --> fn()(a) ??= b) + return; + } + + + const operatorToken = getOperatorToken(body.expression); + + yield ruleFixer.insertTextBefore(operatorToken, existence.operator); // -> if (foo) foo ||= bar + + yield ruleFixer.removeRange([ifNode.range[0], body.range[0]]); // -> foo ||= bar + + yield ruleFixer.removeRange([body.range[1], ifNode.range[1]]); // -> foo ||= bar, only present if "if" had a body + + const nextToken = sourceCode.getTokenAfter(body.expression); + + if (hasBody && (nextToken !== null && nextToken.value !== ";")) { + yield ruleFixer.insertTextAfter(ifNode, ";"); + } + } + }; + const shouldBeFixed = cannotBeGetter(existence.reference) || + (ifNode.test.type !== "LogicalExpression" && accessesSingleProperty(existence.reference)); + + context.report(createConditionalFixer(descriptor, suggestion, shouldBeFixed)); + } + } + }; + } +}; diff --git a/eslint/lib/rules/max-classes-per-file.js b/eslint/lib/rules/max-classes-per-file.js index 0bd626f..241e060 100644 --- a/eslint/lib/rules/max-classes-per-file.js +++ b/eslint/lib/rules/max-classes-per-file.js @@ -21,7 +21,7 @@ module.exports = { docs: { description: "Enforce a maximum number of classes per file", recommended: false, - url: "https://eslint.org/docs/rules/max-classes-per-file" + url: "https://eslint.org/docs/latest/rules/max-classes-per-file" }, schema: [ diff --git a/eslint/lib/rules/max-depth.js b/eslint/lib/rules/max-depth.js index 6b428ce..7a8e9f9 100644 --- a/eslint/lib/rules/max-depth.js +++ b/eslint/lib/rules/max-depth.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Enforce a maximum depth that blocks can be nested", recommended: false, - url: "https://eslint.org/docs/rules/max-depth" + url: "https://eslint.org/docs/latest/rules/max-depth" }, schema: [ diff --git a/eslint/lib/rules/max-len.js b/eslint/lib/rules/max-len.js index 0d3b2af..59e8521 100644 --- a/eslint/lib/rules/max-len.js +++ b/eslint/lib/rules/max-len.js @@ -71,7 +71,7 @@ module.exports = { docs: { description: "Enforce a maximum line length", recommended: false, - url: "https://eslint.org/docs/rules/max-len" + url: "https://eslint.org/docs/latest/rules/max-len" }, schema: [ @@ -97,7 +97,7 @@ module.exports = { */ const URL_REGEXP = /[^:/?#]:\/\/[^?#]/u; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Computes the length of a line that may contain tabs. The width of each diff --git a/eslint/lib/rules/max-lines-per-function.js b/eslint/lib/rules/max-lines-per-function.js index fad646c..f981922 100644 --- a/eslint/lib/rules/max-lines-per-function.js +++ b/eslint/lib/rules/max-lines-per-function.js @@ -73,7 +73,7 @@ module.exports = { docs: { description: "Enforce a maximum number of lines of code in a function", recommended: false, - url: "https://eslint.org/docs/rules/max-lines-per-function" + url: "https://eslint.org/docs/latest/rules/max-lines-per-function" }, schema: [ @@ -85,7 +85,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const lines = sourceCode.lines; const option = context.options[0]; diff --git a/eslint/lib/rules/max-lines.js b/eslint/lib/rules/max-lines.js index d0e5bad..e85d442 100644 --- a/eslint/lib/rules/max-lines.js +++ b/eslint/lib/rules/max-lines.js @@ -36,7 +36,7 @@ module.exports = { docs: { description: "Enforce a maximum number of lines per file", recommended: false, - url: "https://eslint.org/docs/rules/max-lines" + url: "https://eslint.org/docs/latest/rules/max-lines" }, schema: [ @@ -87,7 +87,7 @@ module.exports = { const skipComments = option && option.skipComments; const skipBlankLines = option && option.skipBlankLines; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Returns whether or not a token is a comment node type diff --git a/eslint/lib/rules/max-nested-callbacks.js b/eslint/lib/rules/max-nested-callbacks.js index 3764d5d..d8f380b 100644 --- a/eslint/lib/rules/max-nested-callbacks.js +++ b/eslint/lib/rules/max-nested-callbacks.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Enforce a maximum depth that callbacks can be nested", recommended: false, - url: "https://eslint.org/docs/rules/max-nested-callbacks" + url: "https://eslint.org/docs/latest/rules/max-nested-callbacks" }, schema: [ diff --git a/eslint/lib/rules/max-params.js b/eslint/lib/rules/max-params.js index 8de1ab4..213477a 100644 --- a/eslint/lib/rules/max-params.js +++ b/eslint/lib/rules/max-params.js @@ -24,7 +24,7 @@ module.exports = { docs: { description: "Enforce a maximum number of parameters in function definitions", recommended: false, - url: "https://eslint.org/docs/rules/max-params" + url: "https://eslint.org/docs/latest/rules/max-params" }, schema: [ @@ -57,7 +57,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const option = context.options[0]; let numParams = 3; diff --git a/eslint/lib/rules/max-statements-per-line.js b/eslint/lib/rules/max-statements-per-line.js index ada9cf0..b966504 100644 --- a/eslint/lib/rules/max-statements-per-line.js +++ b/eslint/lib/rules/max-statements-per-line.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Enforce a maximum number of statements allowed per line", recommended: false, - url: "https://eslint.org/docs/rules/max-statements-per-line" + url: "https://eslint.org/docs/latest/rules/max-statements-per-line" }, schema: [ @@ -45,7 +45,7 @@ module.exports = { create(context) { - const sourceCode = context.getSourceCode(), + const sourceCode = context.sourceCode, options = context.options[0] || {}, maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1; diff --git a/eslint/lib/rules/max-statements.js b/eslint/lib/rules/max-statements.js index c598b10..7805383 100644 --- a/eslint/lib/rules/max-statements.js +++ b/eslint/lib/rules/max-statements.js @@ -24,7 +24,7 @@ module.exports = { docs: { description: "Enforce a maximum number of statements allowed in function blocks", recommended: false, - url: "https://eslint.org/docs/rules/max-statements" + url: "https://eslint.org/docs/latest/rules/max-statements" }, schema: [ diff --git a/eslint/lib/rules/multiline-comment-style.js b/eslint/lib/rules/multiline-comment-style.js index 68cd666..6da9862 100644 --- a/eslint/lib/rules/multiline-comment-style.js +++ b/eslint/lib/rules/multiline-comment-style.js @@ -18,11 +18,41 @@ module.exports = { docs: { description: "Enforce a particular style for multiline comments", recommended: false, - url: "https://eslint.org/docs/rules/multiline-comment-style" + url: "https://eslint.org/docs/latest/rules/multiline-comment-style" }, fixable: "whitespace", - schema: [{ enum: ["starred-block", "separate-lines", "bare-block"] }], + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["starred-block", "bare-block"] + } + ], + additionalItems: false + }, + { + type: "array", + items: [ + { + enum: ["separate-lines"] + }, + { + type: "object", + properties: { + checkJSDoc: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + additionalItems: false + } + ] + }, messages: { expectedBlock: "Expected a block comment instead of consecutive line comments.", expectedBareBlock: "Expected a block comment without padding stars.", @@ -35,8 +65,10 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const option = context.options[0] || "starred-block"; + const params = context.options[1] || {}; + const checkJSDoc = !!params.checkJSDoc; //---------------------------------------------------------------------- // Helpers @@ -333,11 +365,18 @@ module.exports = { "separate-lines"(commentGroup) { const [firstComment] = commentGroup; - if (firstComment.type !== "Block" || isJSDocComment(commentGroup)) { + const isJSDoc = isJSDocComment(commentGroup); + + if (firstComment.type !== "Block" || (!checkJSDoc && isJSDoc)) { return; } - const commentLines = getCommentLines(commentGroup); + let commentLines = getCommentLines(commentGroup); + + if (isJSDoc) { + commentLines = commentLines.slice(1, commentLines.length - 1); + } + const tokenAfter = sourceCode.getTokenAfter(firstComment, { includeComments: true }); if (tokenAfter && firstComment.loc.end.line === tokenAfter.loc.start.line) { diff --git a/eslint/lib/rules/multiline-ternary.js b/eslint/lib/rules/multiline-ternary.js index 62c84bb..f156fe3 100644 --- a/eslint/lib/rules/multiline-ternary.js +++ b/eslint/lib/rules/multiline-ternary.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Enforce newlines between operands of ternary expressions", recommended: false, - url: "https://eslint.org/docs/rules/multiline-ternary" + url: "https://eslint.org/docs/latest/rules/multiline-ternary" }, schema: [ @@ -39,7 +39,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const option = context.options[0]; const multiline = option !== "never"; const allowSingleLine = option === "always-multiline"; @@ -73,7 +73,7 @@ module.exports = { end: lastTokenOfTest.loc.end }, messageId: "unexpectedTestCons", - fix: fixer => { + fix(fixer) { if (hasComments) { return null; } @@ -101,7 +101,7 @@ module.exports = { end: lastTokenOfConsequent.loc.end }, messageId: "unexpectedConsAlt", - fix: fixer => { + fix(fixer) { if (hasComments) { return null; } diff --git a/eslint/lib/rules/new-cap.js b/eslint/lib/rules/new-cap.js index 7e0b21e..f81e42f 100644 --- a/eslint/lib/rules/new-cap.js +++ b/eslint/lib/rules/new-cap.js @@ -84,7 +84,7 @@ module.exports = { docs: { description: "Require constructor names to begin with a capital letter", recommended: false, - url: "https://eslint.org/docs/rules/new-cap" + url: "https://eslint.org/docs/latest/rules/new-cap" }, schema: [ @@ -147,7 +147,7 @@ module.exports = { const listeners = {}; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers diff --git a/eslint/lib/rules/new-parens.js b/eslint/lib/rules/new-parens.js index f5a98a4..e866731 100644 --- a/eslint/lib/rules/new-parens.js +++ b/eslint/lib/rules/new-parens.js @@ -27,24 +27,15 @@ module.exports = { docs: { description: "Enforce or disallow parentheses when invoking a constructor with no arguments", recommended: false, - url: "https://eslint.org/docs/rules/new-parens" + url: "https://eslint.org/docs/latest/rules/new-parens" }, fixable: "code", - schema: { - anyOf: [ - { - type: "array", - items: [ - { - enum: ["always", "never"] - } - ], - minItems: 0, - maxItems: 1 - } - ] - }, + schema: [ + { + enum: ["always", "never"] + } + ], messages: { missing: "Missing '()' invoking a constructor.", unnecessary: "Unnecessary '()' invoking a constructor with no arguments." @@ -55,7 +46,7 @@ module.exports = { const options = context.options; const always = options[0] !== "never"; // Default is always - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { NewExpression(node) { diff --git a/eslint/lib/rules/newline-after-var.js b/eslint/lib/rules/newline-after-var.js index 2b4d858..5c9b5fb 100644 --- a/eslint/lib/rules/newline-after-var.js +++ b/eslint/lib/rules/newline-after-var.js @@ -24,7 +24,7 @@ module.exports = { docs: { description: "Require or disallow an empty line after variable declarations", recommended: false, - url: "https://eslint.org/docs/rules/newline-after-var" + url: "https://eslint.org/docs/latest/rules/newline-after-var" }, schema: [ { @@ -43,7 +43,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; // Default `mode` to "always". const mode = context.options[0] === "never" ? "never" : "always"; diff --git a/eslint/lib/rules/newline-before-return.js b/eslint/lib/rules/newline-before-return.js index 007d942..73d8ef9 100644 --- a/eslint/lib/rules/newline-before-return.js +++ b/eslint/lib/rules/newline-before-return.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Require an empty line before `return` statements", recommended: false, - url: "https://eslint.org/docs/rules/newline-before-return" + url: "https://eslint.org/docs/latest/rules/newline-before-return" }, fixable: "whitespace", @@ -31,7 +31,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers diff --git a/eslint/lib/rules/newline-per-chained-call.js b/eslint/lib/rules/newline-per-chained-call.js index 83844a5..b2e6cd9 100644 --- a/eslint/lib/rules/newline-per-chained-call.js +++ b/eslint/lib/rules/newline-per-chained-call.js @@ -20,7 +20,7 @@ module.exports = { docs: { description: "Require a newline after each call in a method chain", recommended: false, - url: "https://eslint.org/docs/rules/newline-per-chained-call" + url: "https://eslint.org/docs/latest/rules/newline-per-chained-call" }, fixable: "whitespace", @@ -47,7 +47,7 @@ module.exports = { const options = context.options[0] || {}, ignoreChainWithDepth = options.ignoreChainWithDepth || 2; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Get the prefix of a given MemberExpression node. diff --git a/eslint/lib/rules/no-alert.js b/eslint/lib/rules/no-alert.js index ba0125c..cc87285 100644 --- a/eslint/lib/rules/no-alert.js +++ b/eslint/lib/rules/no-alert.js @@ -90,7 +90,7 @@ module.exports = { docs: { description: "Disallow the use of `alert`, `confirm`, and `prompt`", recommended: false, - url: "https://eslint.org/docs/rules/no-alert" + url: "https://eslint.org/docs/latest/rules/no-alert" }, schema: [], @@ -101,10 +101,12 @@ module.exports = { }, create(context) { + const sourceCode = context.sourceCode; + return { CallExpression(node) { const callee = skipChainExpression(node.callee), - currentScope = context.getScope(); + currentScope = sourceCode.getScope(node); // without window. if (callee.type === "Identifier") { diff --git a/eslint/lib/rules/no-array-constructor.js b/eslint/lib/rules/no-array-constructor.js index 93b79ab..b539926 100644 --- a/eslint/lib/rules/no-array-constructor.js +++ b/eslint/lib/rules/no-array-constructor.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow `Array` constructors", recommended: false, - url: "https://eslint.org/docs/rules/no-array-constructor" + url: "https://eslint.org/docs/latest/rules/no-array-constructor" }, schema: [], diff --git a/eslint/lib/rules/no-async-promise-executor.js b/eslint/lib/rules/no-async-promise-executor.js index 52c5186..ea6c851 100644 --- a/eslint/lib/rules/no-async-promise-executor.js +++ b/eslint/lib/rules/no-async-promise-executor.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Disallow using an async function as a Promise executor", recommended: true, - url: "https://eslint.org/docs/rules/no-async-promise-executor" + url: "https://eslint.org/docs/latest/rules/no-async-promise-executor" }, fixable: null, @@ -30,7 +30,7 @@ module.exports = { return { "NewExpression[callee.name='Promise'][arguments.0.async=true]"(node) { context.report({ - node: context.getSourceCode().getFirstToken(node.arguments[0], token => token.value === "async"), + node: context.sourceCode.getFirstToken(node.arguments[0], token => token.value === "async"), messageId: "async" }); } diff --git a/eslint/lib/rules/no-await-in-loop.js b/eslint/lib/rules/no-await-in-loop.js index 905a793..20230de 100644 --- a/eslint/lib/rules/no-await-in-loop.js +++ b/eslint/lib/rules/no-await-in-loop.js @@ -61,7 +61,7 @@ module.exports = { docs: { description: "Disallow `await` inside of loops", recommended: false, - url: "https://eslint.org/docs/rules/no-await-in-loop" + url: "https://eslint.org/docs/latest/rules/no-await-in-loop" }, schema: [], diff --git a/eslint/lib/rules/no-bitwise.js b/eslint/lib/rules/no-bitwise.js index 172ea04..d90992b 100644 --- a/eslint/lib/rules/no-bitwise.js +++ b/eslint/lib/rules/no-bitwise.js @@ -28,7 +28,7 @@ module.exports = { docs: { description: "Disallow bitwise operators", recommended: false, - url: "https://eslint.org/docs/rules/no-bitwise" + url: "https://eslint.org/docs/latest/rules/no-bitwise" }, schema: [ diff --git a/eslint/lib/rules/no-buffer-constructor.js b/eslint/lib/rules/no-buffer-constructor.js index 9300392..0b73c76 100644 --- a/eslint/lib/rules/no-buffer-constructor.js +++ b/eslint/lib/rules/no-buffer-constructor.js @@ -21,7 +21,7 @@ module.exports = { docs: { description: "Disallow use of the `Buffer()` constructor", recommended: false, - url: "https://eslint.org/docs/rules/no-buffer-constructor" + url: "https://eslint.org/docs/latest/rules/no-buffer-constructor" }, schema: [], diff --git a/eslint/lib/rules/no-caller.js b/eslint/lib/rules/no-caller.js index 884a02b..3e61a8e 100644 --- a/eslint/lib/rules/no-caller.js +++ b/eslint/lib/rules/no-caller.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow the use of `arguments.caller` or `arguments.callee`", recommended: false, - url: "https://eslint.org/docs/rules/no-caller" + url: "https://eslint.org/docs/latest/rules/no-caller" }, schema: [], diff --git a/eslint/lib/rules/no-case-declarations.js b/eslint/lib/rules/no-case-declarations.js index 6557ba3..8dc5b02 100644 --- a/eslint/lib/rules/no-case-declarations.js +++ b/eslint/lib/rules/no-case-declarations.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Disallow lexical declarations in case clauses", recommended: true, - url: "https://eslint.org/docs/rules/no-case-declarations" + url: "https://eslint.org/docs/latest/rules/no-case-declarations" }, schema: [], diff --git a/eslint/lib/rules/no-catch-shadow.js b/eslint/lib/rules/no-catch-shadow.js index 49f1ba9..f9d8552 100644 --- a/eslint/lib/rules/no-catch-shadow.js +++ b/eslint/lib/rules/no-catch-shadow.js @@ -24,7 +24,7 @@ module.exports = { docs: { description: "Disallow `catch` clause parameters from shadowing variables in the outer scope", recommended: false, - url: "https://eslint.org/docs/rules/no-catch-shadow" + url: "https://eslint.org/docs/latest/rules/no-catch-shadow" }, replacedBy: ["no-shadow"], @@ -39,6 +39,8 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- @@ -60,7 +62,7 @@ module.exports = { return { "CatchClause[param!=null]"(node) { - let scope = context.getScope(); + let scope = sourceCode.getScope(node); /* * When ecmaVersion >= 6, CatchClause creates its own scope diff --git a/eslint/lib/rules/no-class-assign.js b/eslint/lib/rules/no-class-assign.js index 32e48e2..49f3b84 100644 --- a/eslint/lib/rules/no-class-assign.js +++ b/eslint/lib/rules/no-class-assign.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Disallow reassigning class members", recommended: true, - url: "https://eslint.org/docs/rules/no-class-assign" + url: "https://eslint.org/docs/latest/rules/no-class-assign" }, schema: [], @@ -31,6 +31,8 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + /** * Finds and reports references that are non initializer and writable. * @param {Variable} variable A variable to check. @@ -49,7 +51,7 @@ module.exports = { * @returns {void} */ function checkForClass(node) { - context.getDeclaredVariables(node).forEach(checkVariable); + sourceCode.getDeclaredVariables(node).forEach(checkVariable); } return { diff --git a/eslint/lib/rules/no-compare-neg-zero.js b/eslint/lib/rules/no-compare-neg-zero.js index 9715c2f..112f6c1 100644 --- a/eslint/lib/rules/no-compare-neg-zero.js +++ b/eslint/lib/rules/no-compare-neg-zero.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Disallow comparing against -0", recommended: true, - url: "https://eslint.org/docs/rules/no-compare-neg-zero" + url: "https://eslint.org/docs/latest/rules/no-compare-neg-zero" }, fixable: null, diff --git a/eslint/lib/rules/no-cond-assign.js b/eslint/lib/rules/no-cond-assign.js index 59efb34..9529202 100644 --- a/eslint/lib/rules/no-cond-assign.js +++ b/eslint/lib/rules/no-cond-assign.js @@ -36,7 +36,7 @@ module.exports = { docs: { description: "Disallow assignment operators in conditional expressions", recommended: true, - url: "https://eslint.org/docs/rules/no-cond-assign" + url: "https://eslint.org/docs/latest/rules/no-cond-assign" }, schema: [ @@ -57,7 +57,7 @@ module.exports = { const prohibitAssign = (context.options[0] || "except-parens"); - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Check whether an AST node is the test expression for a conditional statement. diff --git a/eslint/lib/rules/no-confusing-arrow.js b/eslint/lib/rules/no-confusing-arrow.js index d2b6641..de6e2f3 100644 --- a/eslint/lib/rules/no-confusing-arrow.js +++ b/eslint/lib/rules/no-confusing-arrow.js @@ -33,7 +33,7 @@ module.exports = { docs: { description: "Disallow arrow functions where they could be confused with comparisons", recommended: false, - url: "https://eslint.org/docs/rules/no-confusing-arrow" + url: "https://eslint.org/docs/latest/rules/no-confusing-arrow" }, fixable: "code", @@ -56,7 +56,7 @@ module.exports = { const config = context.options[0] || {}; const allowParens = config.allowParens || (config.allowParens === void 0); const onlyOneSimpleParam = config.onlyOneSimpleParam; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** diff --git a/eslint/lib/rules/no-console.js b/eslint/lib/rules/no-console.js index bad6b6f..f257098 100644 --- a/eslint/lib/rules/no-console.js +++ b/eslint/lib/rules/no-console.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Disallow the use of `console`", recommended: false, - url: "https://eslint.org/docs/rules/no-console" + url: "https://eslint.org/docs/latest/rules/no-console" }, schema: [ @@ -51,6 +51,7 @@ module.exports = { create(context) { const options = context.options[0] || {}; const allowed = options.allow || []; + const sourceCode = context.sourceCode; /** * Checks whether the given reference is 'console' or not. @@ -109,8 +110,8 @@ module.exports = { } return { - "Program:exit"() { - const scope = context.getScope(); + "Program:exit"(node) { + const scope = sourceCode.getScope(node); const consoleVar = astUtils.getVariableByName(scope, "console"); const shadowed = consoleVar && consoleVar.defs.length > 0; diff --git a/eslint/lib/rules/no-const-assign.js b/eslint/lib/rules/no-const-assign.js index 55e40c8..0ceaf7e 100644 --- a/eslint/lib/rules/no-const-assign.js +++ b/eslint/lib/rules/no-const-assign.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Disallow reassigning `const` variables", recommended: true, - url: "https://eslint.org/docs/rules/no-const-assign" + url: "https://eslint.org/docs/latest/rules/no-const-assign" }, schema: [], @@ -31,6 +31,8 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + /** * Finds and reports references that are non initializer and writable. * @param {Variable} variable A variable to check. @@ -45,7 +47,7 @@ module.exports = { return { VariableDeclaration(node) { if (node.kind === "const") { - context.getDeclaredVariables(node).forEach(checkVariable); + sourceCode.getDeclaredVariables(node).forEach(checkVariable); } } }; diff --git a/eslint/lib/rules/no-constant-binary-expression.js b/eslint/lib/rules/no-constant-binary-expression.js index dccfa2f..845255a 100644 --- a/eslint/lib/rules/no-constant-binary-expression.js +++ b/eslint/lib/rules/no-constant-binary-expression.js @@ -14,6 +14,23 @@ const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set(["+", "-", "*", "/", "%", "|" // Helpers //------------------------------------------------------------------------------ +/** + * Checks whether or not a node is `null` or `undefined`. Similar to the one + * found in ast-utils.js, but this one correctly handles the edge case that + * `undefined` has been redefined. + * @param {Scope} scope Scope in which the expression was found. + * @param {ASTNode} node A node to check. + * @returns {boolean} Whether or not the node is a `null` or `undefined`. + * @public + */ +function isNullOrUndefined(scope, node) { + return ( + isNullLiteral(node) || + (node.type === "Identifier" && node.name === "undefined" && isReferenceToGlobalVariable(scope, node)) || + (node.type === "UnaryExpression" && node.operator === "void") + ); +} + /** * Test if an AST node has a statically knowable constant nullishness. Meaning, * it will always resolve to a constant value of either: `null`, `undefined` @@ -21,9 +38,14 @@ const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set(["+", "-", "*", "/", "%", "|" * three states at runtime would return `false`. * @param {Scope} scope The scope in which the node was found. * @param {ASTNode} node The AST node being tested. + * @param {boolean} nonNullish if `true` then nullish values are not considered constant. * @returns {boolean} Does `node` have constant nullishness? */ -function hasConstantNullishness(scope, node) { +function hasConstantNullishness(scope, node, nonNullish) { + if (nonNullish && isNullOrUndefined(scope, node)) { + return false; + } + switch (node.type) { case "ObjectExpression": // Objects are never nullish case "ArrayExpression": // Arrays are never nullish @@ -45,9 +67,12 @@ function hasConstantNullishness(scope, node) { return (functionName === "Boolean" || functionName === "String" || functionName === "Number") && isReferenceToGlobalVariable(scope, node.callee); } + case "LogicalExpression": { + return node.operator === "??" && hasConstantNullishness(scope, node.right, true); + } case "AssignmentExpression": if (node.operator === "=") { - return hasConstantNullishness(scope, node.right); + return hasConstantNullishness(scope, node.right, nonNullish); } /* @@ -80,7 +105,7 @@ function hasConstantNullishness(scope, node) { case "SequenceExpression": { const last = node.expressions[node.expressions.length - 1]; - return hasConstantNullishness(scope, last); + return hasConstantNullishness(scope, last, nonNullish); } case "Identifier": return node.name === "undefined" && isReferenceToGlobalVariable(scope, node); @@ -348,7 +373,7 @@ function isAlwaysNew(scope, node) { * user-defined constructors could return a sentinel * object. * - * Catching these is especially useful for primitive constructures + * Catching these is especially useful for primitive constructors * which return boxed values, a surprising gotcha' in JavaScript. */ return Object.hasOwnProperty.call(globals.builtin, node.callee.name) && @@ -378,24 +403,6 @@ function isAlwaysNew(scope, node) { } } -/** - * Checks whether or not a node is `null` or `undefined`. Similar to the one - * found in ast-utils.js, but this one correctly handles the edge case that - * `undefined` has been redefined. - * @param {Scope} scope Scope in which the expression was found. - * @param {ASTNode} node A node to check. - * @returns {boolean} Whether or not the node is a `null` or `undefined`. - * @public - */ -function isNullOrUndefined(scope, node) { - return ( - isNullLiteral(node) || - (node.type === "Identifier" && node.name === "undefined" && isReferenceToGlobalVariable(scope, node)) || - (node.type === "UnaryExpression" && node.operator === "void") - ); -} - - /** * Checks if one operand will cause the result to be constant. * @param {Scope} scope Scope in which the expression was found. @@ -407,14 +414,14 @@ function isNullOrUndefined(scope, node) { function findBinaryExpressionConstantOperand(scope, a, b, operator) { if (operator === "==" || operator === "!=") { if ( - (isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b)) || + (isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b, false)) || (isStaticBoolean(scope, a) && hasConstantLooseBooleanComparison(scope, b)) ) { return b; } } else if (operator === "===" || operator === "!==") { if ( - (isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b)) || + (isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b, false)) || (isStaticBoolean(scope, a) && hasConstantStrictBooleanComparison(scope, b)) ) { return b; @@ -434,7 +441,7 @@ module.exports = { docs: { description: "Disallow expressions where the operation doesn't affect the value", recommended: false, - url: "https://eslint.org/docs/rules/no-constant-binary-expression" + url: "https://eslint.org/docs/latest/rules/no-constant-binary-expression" }, schema: [], messages: { @@ -446,19 +453,21 @@ module.exports = { }, create(context) { + const sourceCode = context.sourceCode; + return { LogicalExpression(node) { const { operator, left } = node; - const scope = context.getScope(); + const scope = sourceCode.getScope(node); if ((operator === "&&" || operator === "||") && isConstant(scope, left, true)) { context.report({ node: left, messageId: "constantShortCircuit", data: { property: "truthiness", operator } }); - } else if (operator === "??" && hasConstantNullishness(scope, left)) { + } else if (operator === "??" && hasConstantNullishness(scope, left, false)) { context.report({ node: left, messageId: "constantShortCircuit", data: { property: "nullishness", operator } }); } }, BinaryExpression(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const { right, left, operator } = node; const rightConstantOperand = findBinaryExpressionConstantOperand(scope, left, right, operator); const leftConstantOperand = findBinaryExpressionConstantOperand(scope, right, left, operator); diff --git a/eslint/lib/rules/no-constant-condition.js b/eslint/lib/rules/no-constant-condition.js index 2ef687f..24abe36 100644 --- a/eslint/lib/rules/no-constant-condition.js +++ b/eslint/lib/rules/no-constant-condition.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Disallow constant expressions in conditions", recommended: true, - url: "https://eslint.org/docs/rules/no-constant-condition" + url: "https://eslint.org/docs/latest/rules/no-constant-condition" }, schema: [ @@ -48,6 +48,7 @@ module.exports = { const options = context.options[0] || {}, checkLoops = options.checkLoops !== false, loopSetStack = []; + const sourceCode = context.sourceCode; let loopsInCurrentScope = new Set(); @@ -62,7 +63,7 @@ module.exports = { * @private */ function trackConstantConditionLoop(node) { - if (node.test && isConstant(context.getScope(), node.test, true)) { + if (node.test && isConstant(sourceCode.getScope(node), node.test, true)) { loopsInCurrentScope.add(node); } } @@ -87,7 +88,7 @@ module.exports = { * @private */ function reportIfConstant(node) { - if (node.test && isConstant(context.getScope(), node.test, true)) { + if (node.test && isConstant(sourceCode.getScope(node), node.test, true)) { context.report({ node: node.test, messageId: "unexpected" }); } } diff --git a/eslint/lib/rules/no-constructor-return.js b/eslint/lib/rules/no-constructor-return.js index 911a32a..d7d9893 100644 --- a/eslint/lib/rules/no-constructor-return.js +++ b/eslint/lib/rules/no-constructor-return.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow returning value from constructor", recommended: false, - url: "https://eslint.org/docs/rules/no-constructor-return" + url: "https://eslint.org/docs/latest/rules/no-constructor-return" }, schema: {}, diff --git a/eslint/lib/rules/no-continue.js b/eslint/lib/rules/no-continue.js index 80381fc..f6e484b 100644 --- a/eslint/lib/rules/no-continue.js +++ b/eslint/lib/rules/no-continue.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow `continue` statements", recommended: false, - url: "https://eslint.org/docs/rules/no-continue" + url: "https://eslint.org/docs/latest/rules/no-continue" }, schema: [], diff --git a/eslint/lib/rules/no-control-regex.js b/eslint/lib/rules/no-control-regex.js index ba10843..086747f 100644 --- a/eslint/lib/rules/no-control-regex.js +++ b/eslint/lib/rules/no-control-regex.js @@ -5,7 +5,7 @@ "use strict"; -const RegExpValidator = require("regexpp").RegExpValidator; +const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator; const collector = new (class { constructor() { this._source = ""; @@ -56,7 +56,7 @@ module.exports = { docs: { description: "Disallow control characters in regular expressions", recommended: true, - url: "https://eslint.org/docs/rules/no-control-regex" + url: "https://eslint.org/docs/latest/rules/no-control-regex" }, schema: [], diff --git a/eslint/lib/rules/no-debugger.js b/eslint/lib/rules/no-debugger.js index 3b88079..f698435 100644 --- a/eslint/lib/rules/no-debugger.js +++ b/eslint/lib/rules/no-debugger.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow the use of `debugger`", recommended: true, - url: "https://eslint.org/docs/rules/no-debugger" + url: "https://eslint.org/docs/latest/rules/no-debugger" }, fixable: null, diff --git a/eslint/lib/rules/no-delete-var.js b/eslint/lib/rules/no-delete-var.js index 41021bd..126603c 100644 --- a/eslint/lib/rules/no-delete-var.js +++ b/eslint/lib/rules/no-delete-var.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow deleting variables", recommended: true, - url: "https://eslint.org/docs/rules/no-delete-var" + url: "https://eslint.org/docs/latest/rules/no-delete-var" }, schema: [], diff --git a/eslint/lib/rules/no-div-regex.js b/eslint/lib/rules/no-div-regex.js index dd1c578..208f840 100644 --- a/eslint/lib/rules/no-div-regex.js +++ b/eslint/lib/rules/no-div-regex.js @@ -15,9 +15,9 @@ module.exports = { type: "suggestion", docs: { - description: "Disallow division operators explicitly at the beginning of regular expressions", + description: "Disallow equal signs explicitly at the beginning of regular expressions", recommended: false, - url: "https://eslint.org/docs/rules/no-div-regex" + url: "https://eslint.org/docs/latest/rules/no-div-regex" }, fixable: "code", @@ -30,7 +30,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { diff --git a/eslint/lib/rules/no-dupe-args.js b/eslint/lib/rules/no-dupe-args.js index faf2537..c04ede5 100644 --- a/eslint/lib/rules/no-dupe-args.js +++ b/eslint/lib/rules/no-dupe-args.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow duplicate arguments in `function` definitions", recommended: true, - url: "https://eslint.org/docs/rules/no-dupe-args" + url: "https://eslint.org/docs/latest/rules/no-dupe-args" }, schema: [], @@ -29,6 +29,8 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- @@ -49,7 +51,7 @@ module.exports = { * @private */ function checkParams(node) { - const variables = context.getDeclaredVariables(node); + const variables = sourceCode.getDeclaredVariables(node); for (let i = 0; i < variables.length; ++i) { const variable = variables[i]; diff --git a/eslint/lib/rules/no-dupe-class-members.js b/eslint/lib/rules/no-dupe-class-members.js index 8eca787..2a7a9e8 100644 --- a/eslint/lib/rules/no-dupe-class-members.js +++ b/eslint/lib/rules/no-dupe-class-members.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Disallow duplicate class members", recommended: true, - url: "https://eslint.org/docs/rules/no-dupe-class-members" + url: "https://eslint.org/docs/latest/rules/no-dupe-class-members" }, schema: [], diff --git a/eslint/lib/rules/no-dupe-else-if.js b/eslint/lib/rules/no-dupe-else-if.js index 49db5ec..60f436d 100644 --- a/eslint/lib/rules/no-dupe-else-if.js +++ b/eslint/lib/rules/no-dupe-else-if.js @@ -54,7 +54,7 @@ module.exports = { docs: { description: "Disallow duplicate conditions in if-else-if chains", recommended: true, - url: "https://eslint.org/docs/rules/no-dupe-else-if" + url: "https://eslint.org/docs/latest/rules/no-dupe-else-if" }, schema: [], @@ -65,7 +65,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Determines whether the two given nodes are considered to be equal. In particular, given that the nodes diff --git a/eslint/lib/rules/no-dupe-keys.js b/eslint/lib/rules/no-dupe-keys.js index 65c34bc..980b004 100644 --- a/eslint/lib/rules/no-dupe-keys.js +++ b/eslint/lib/rules/no-dupe-keys.js @@ -90,7 +90,7 @@ module.exports = { docs: { description: "Disallow duplicate keys in object literals", recommended: true, - url: "https://eslint.org/docs/rules/no-dupe-keys" + url: "https://eslint.org/docs/latest/rules/no-dupe-keys" }, schema: [], diff --git a/eslint/lib/rules/no-duplicate-case.js b/eslint/lib/rules/no-duplicate-case.js index d436afd..839f357 100644 --- a/eslint/lib/rules/no-duplicate-case.js +++ b/eslint/lib/rules/no-duplicate-case.js @@ -24,7 +24,7 @@ module.exports = { docs: { description: "Disallow duplicate case labels", recommended: true, - url: "https://eslint.org/docs/rules/no-duplicate-case" + url: "https://eslint.org/docs/latest/rules/no-duplicate-case" }, schema: [], @@ -35,7 +35,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Determines whether the two given nodes are considered to be equal. diff --git a/eslint/lib/rules/no-duplicate-imports.js b/eslint/lib/rules/no-duplicate-imports.js index 619e258..25c07b7 100644 --- a/eslint/lib/rules/no-duplicate-imports.js +++ b/eslint/lib/rules/no-duplicate-imports.js @@ -235,7 +235,7 @@ module.exports = { docs: { description: "Disallow duplicate module imports", recommended: false, - url: "https://eslint.org/docs/rules/no-duplicate-imports" + url: "https://eslint.org/docs/latest/rules/no-duplicate-imports" }, schema: [ diff --git a/eslint/lib/rules/no-else-return.js b/eslint/lib/rules/no-else-return.js index d1da3aa..9dbf569 100644 --- a/eslint/lib/rules/no-else-return.js +++ b/eslint/lib/rules/no-else-return.js @@ -24,7 +24,7 @@ module.exports = { docs: { description: "Disallow `else` blocks after `return` statements in `if` statements", recommended: false, - url: "https://eslint.org/docs/rules/no-else-return" + url: "https://eslint.org/docs/latest/rules/no-else-return" }, schema: [{ @@ -47,6 +47,8 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- @@ -169,25 +171,24 @@ module.exports = { /** * Display the context report if rule is violated - * @param {Node} node The 'else' node + * @param {Node} elseNode The 'else' node * @returns {void} */ - function displayReport(node) { - const currentScope = context.getScope(); + function displayReport(elseNode) { + const currentScope = sourceCode.getScope(elseNode.parent); context.report({ - node, + node: elseNode, messageId: "unexpected", - fix: fixer => { + fix(fixer) { - if (!isSafeFromNameCollisions(node, currentScope)) { + if (!isSafeFromNameCollisions(elseNode, currentScope)) { return null; } - const sourceCode = context.getSourceCode(); - const startToken = sourceCode.getFirstToken(node); + const startToken = sourceCode.getFirstToken(elseNode); const elseToken = sourceCode.getTokenBefore(startToken); - const source = sourceCode.getText(node); + const source = sourceCode.getText(elseNode); const lastIfToken = sourceCode.getTokenBefore(elseToken); let fixedSource, firstTokenOfElseBlock; @@ -203,14 +204,14 @@ module.exports = { * safe to remove the else keyword, because ASI will not add a semicolon * after the if block */ - const ifBlockMaybeUnsafe = node.parent.consequent.type !== "BlockStatement" && lastIfToken.value !== ";"; + const ifBlockMaybeUnsafe = elseNode.parent.consequent.type !== "BlockStatement" && lastIfToken.value !== ";"; const elseBlockUnsafe = /^[([/+`-]/u.test(firstTokenOfElseBlock.value); if (ifBlockMaybeUnsafe && elseBlockUnsafe) { return null; } - const endToken = sourceCode.getLastToken(node); + const endToken = sourceCode.getLastToken(elseNode); const lastTokenOfElseBlock = sourceCode.getTokenBefore(endToken); if (lastTokenOfElseBlock.value !== ";") { @@ -244,8 +245,8 @@ module.exports = { * Also, to avoid name collisions between two else blocks. */ return new FixTracker(fixer, sourceCode) - .retainEnclosingFunction(node) - .replaceTextRange([elseToken.range[0], node.range[1]], fixedSource); + .retainEnclosingFunction(elseNode) + .replaceTextRange([elseToken.range[0], elseNode.range[1]], fixedSource); } }); } diff --git a/eslint/lib/rules/no-empty-character-class.js b/eslint/lib/rules/no-empty-character-class.js index 2d294f4..da29bbe 100644 --- a/eslint/lib/rules/no-empty-character-class.js +++ b/eslint/lib/rules/no-empty-character-class.js @@ -32,7 +32,7 @@ module.exports = { docs: { description: "Disallow empty character classes in regular expressions", recommended: true, - url: "https://eslint.org/docs/rules/no-empty-character-class" + url: "https://eslint.org/docs/latest/rules/no-empty-character-class" }, schema: [], diff --git a/eslint/lib/rules/no-empty-function.js b/eslint/lib/rules/no-empty-function.js index 4c9daa9..2fcb755 100644 --- a/eslint/lib/rules/no-empty-function.js +++ b/eslint/lib/rules/no-empty-function.js @@ -97,7 +97,7 @@ module.exports = { docs: { description: "Disallow empty functions", recommended: false, - url: "https://eslint.org/docs/rules/no-empty-function" + url: "https://eslint.org/docs/latest/rules/no-empty-function" }, schema: [ @@ -123,7 +123,7 @@ module.exports = { const options = context.options[0] || {}; const allowed = options.allow || []; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Reports a given function node if the node matches the following patterns. diff --git a/eslint/lib/rules/no-empty-pattern.js b/eslint/lib/rules/no-empty-pattern.js index 5a497f0..abb1a7c 100644 --- a/eslint/lib/rules/no-empty-pattern.js +++ b/eslint/lib/rules/no-empty-pattern.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Disallow empty destructuring patterns", recommended: true, - url: "https://eslint.org/docs/rules/no-empty-pattern" + url: "https://eslint.org/docs/latest/rules/no-empty-pattern" }, schema: [], diff --git a/eslint/lib/rules/no-empty-static-block.js b/eslint/lib/rules/no-empty-static-block.js new file mode 100644 index 0000000..81fc449 --- /dev/null +++ b/eslint/lib/rules/no-empty-static-block.js @@ -0,0 +1,47 @@ +/** + * @fileoverview Rule to disallow empty static blocks. + * @author Sosuke Suzuki + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +/** @type {import('../shared/types').Rule} */ +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "Disallow empty static blocks", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-empty-static-block" + }, + + schema: [], + + messages: { + unexpected: "Unexpected empty static block." + } + }, + + create(context) { + const sourceCode = context.sourceCode; + + return { + StaticBlock(node) { + if (node.body.length === 0) { + const closingBrace = sourceCode.getLastToken(node); + + if (sourceCode.getCommentsBefore(closingBrace).length === 0) { + context.report({ + node, + messageId: "unexpected" + }); + } + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-empty.js b/eslint/lib/rules/no-empty.js index 459140a..1c15796 100644 --- a/eslint/lib/rules/no-empty.js +++ b/eslint/lib/rules/no-empty.js @@ -17,12 +17,13 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + hasSuggestions: true, type: "suggestion", docs: { description: "Disallow empty block statements", recommended: true, - url: "https://eslint.org/docs/rules/no-empty" + url: "https://eslint.org/docs/latest/rules/no-empty" }, schema: [ @@ -39,7 +40,8 @@ module.exports = { ], messages: { - unexpected: "Empty {{type}} statement." + unexpected: "Empty {{type}} statement.", + suggestComment: "Add comment inside empty {{type}} statement." } }, @@ -47,7 +49,7 @@ module.exports = { const options = context.options[0] || {}, allowEmptyCatch = options.allowEmptyCatch || false; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { BlockStatement(node) { @@ -71,7 +73,22 @@ module.exports = { return; } - context.report({ node, messageId: "unexpected", data: { type: "block" } }); + context.report({ + node, + messageId: "unexpected", + data: { type: "block" }, + suggest: [ + { + messageId: "suggestComment", + data: { type: "block" }, + fix(fixer) { + const range = [node.range[0] + 1, node.range[1] - 1]; + + return fixer.replaceTextRange(range, " /* empty */ "); + } + } + ] + }); }, SwitchStatement(node) { diff --git a/eslint/lib/rules/no-eq-null.js b/eslint/lib/rules/no-eq-null.js index 9a88680..9252907 100644 --- a/eslint/lib/rules/no-eq-null.js +++ b/eslint/lib/rules/no-eq-null.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Disallow `null` comparisons without type-checking operators", recommended: false, - url: "https://eslint.org/docs/rules/no-eq-null" + url: "https://eslint.org/docs/latest/rules/no-eq-null" }, schema: [], diff --git a/eslint/lib/rules/no-eval.js b/eslint/lib/rules/no-eval.js index 03f7b1f..a059526 100644 --- a/eslint/lib/rules/no-eval.js +++ b/eslint/lib/rules/no-eval.js @@ -45,7 +45,7 @@ module.exports = { docs: { description: "Disallow the use of `eval()`", recommended: false, - url: "https://eslint.org/docs/rules/no-eval" + url: "https://eslint.org/docs/latest/rules/no-eval" }, schema: [ @@ -68,11 +68,11 @@ module.exports = { context.options[0] && context.options[0].allowIndirect ); - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; let funcInfo = null; /** - * Pushs a `this` scope (non-arrow function, class static block, or class field initializer) information to the stack. + * Pushes a `this` scope (non-arrow function, class static block, or class field initializer) information to the stack. * Top-level scopes are handled separately. * * This is used in order to check whether or not `this` binding is a @@ -84,7 +84,7 @@ module.exports = { * @returns {void} */ function enterThisScope(node) { - const strict = context.getScope().isStrict; + const strict = sourceCode.getScope(node).isStrict; funcInfo = { upper: funcInfo, @@ -221,7 +221,7 @@ module.exports = { }, Program(node) { - const scope = context.getScope(), + const scope = sourceCode.getScope(node), features = context.parserOptions.ecmaFeatures || {}, strict = scope.isStrict || @@ -239,8 +239,8 @@ module.exports = { }; }, - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); exitThisScope(); reportAccessingEval(globalScope); diff --git a/eslint/lib/rules/no-ex-assign.js b/eslint/lib/rules/no-ex-assign.js index 4c77b11..d0e9feb 100644 --- a/eslint/lib/rules/no-ex-assign.js +++ b/eslint/lib/rules/no-ex-assign.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Disallow reassigning exceptions in `catch` clauses", recommended: true, - url: "https://eslint.org/docs/rules/no-ex-assign" + url: "https://eslint.org/docs/latest/rules/no-ex-assign" }, schema: [], @@ -31,6 +31,8 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + /** * Finds and reports references that are non initializer and writable. * @param {Variable} variable A variable to check. @@ -44,7 +46,7 @@ module.exports = { return { CatchClause(node) { - context.getDeclaredVariables(node).forEach(checkVariable); + sourceCode.getDeclaredVariables(node).forEach(checkVariable); } }; diff --git a/eslint/lib/rules/no-extend-native.js b/eslint/lib/rules/no-extend-native.js index 52c6bd3..fcbb385 100644 --- a/eslint/lib/rules/no-extend-native.js +++ b/eslint/lib/rules/no-extend-native.js @@ -24,7 +24,7 @@ module.exports = { docs: { description: "Disallow extending native types", recommended: false, - url: "https://eslint.org/docs/rules/no-extend-native" + url: "https://eslint.org/docs/latest/rules/no-extend-native" }, schema: [ @@ -51,6 +51,7 @@ module.exports = { create(context) { const config = context.options[0] || {}; + const sourceCode = context.sourceCode; const exceptions = new Set(config.exceptions || []); const modifiedBuiltins = new Set( Object.keys(globals.builtin) @@ -159,8 +160,8 @@ module.exports = { return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); modifiedBuiltins.forEach(builtin => { const builtinVar = globalScope.set.get(builtin); diff --git a/eslint/lib/rules/no-extra-bind.js b/eslint/lib/rules/no-extra-bind.js index caf6d8b..e1e72b0 100644 --- a/eslint/lib/rules/no-extra-bind.js +++ b/eslint/lib/rules/no-extra-bind.js @@ -28,7 +28,7 @@ module.exports = { docs: { description: "Disallow unnecessary calls to `.bind()`", recommended: false, - url: "https://eslint.org/docs/rules/no-extra-bind" + url: "https://eslint.org/docs/latest/rules/no-extra-bind" }, schema: [], @@ -40,7 +40,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; let scopeInfo = null; /** diff --git a/eslint/lib/rules/no-extra-boolean-cast.js b/eslint/lib/rules/no-extra-boolean-cast.js index 45252fe..f342533 100644 --- a/eslint/lib/rules/no-extra-boolean-cast.js +++ b/eslint/lib/rules/no-extra-boolean-cast.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const eslintUtils = require("eslint-utils"); +const eslintUtils = require("@eslint-community/eslint-utils"); const precedence = astUtils.getPrecedence; @@ -26,7 +26,7 @@ module.exports = { docs: { description: "Disallow unnecessary boolean casts", recommended: true, - url: "https://eslint.org/docs/rules/no-extra-boolean-cast" + url: "https://eslint.org/docs/latest/rules/no-extra-boolean-cast" }, schema: [{ @@ -48,7 +48,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; // Node types which have a test which will coerce values to booleans. const BOOLEAN_NODE_TYPES = new Set([ diff --git a/eslint/lib/rules/no-extra-label.js b/eslint/lib/rules/no-extra-label.js index bda3dd0..45ff441 100644 --- a/eslint/lib/rules/no-extra-label.js +++ b/eslint/lib/rules/no-extra-label.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Disallow unnecessary labels", recommended: false, - url: "https://eslint.org/docs/rules/no-extra-label" + url: "https://eslint.org/docs/latest/rules/no-extra-label" }, schema: [], @@ -35,7 +35,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; let scopeInfo = null; /** diff --git a/eslint/lib/rules/no-extra-parens.js b/eslint/lib/rules/no-extra-parens.js index 246a5a0..0f59d7d 100644 --- a/eslint/lib/rules/no-extra-parens.js +++ b/eslint/lib/rules/no-extra-parens.js @@ -8,7 +8,7 @@ // Rule Definition //------------------------------------------------------------------------------ -const { isParenthesized: isParenthesizedRaw } = require("eslint-utils"); +const { isParenthesized: isParenthesizedRaw } = require("@eslint-community/eslint-utils"); const astUtils = require("./utils/ast-utils.js"); /** @type {import('../shared/types').Rule} */ @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Disallow unnecessary parentheses", recommended: false, - url: "https://eslint.org/docs/rules/no-extra-parens" + url: "https://eslint.org/docs/latest/rules/no-extra-parens" }, fixable: "code", @@ -52,7 +52,8 @@ module.exports = { enforceForArrowConditionals: { type: "boolean" }, enforceForSequenceExpressions: { type: "boolean" }, enforceForNewInMemberExpressions: { type: "boolean" }, - enforceForFunctionPrototypeMethods: { type: "boolean" } + enforceForFunctionPrototypeMethods: { type: "boolean" }, + allowParensAfterCommentPattern: { type: "string" } }, additionalProperties: false } @@ -69,7 +70,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const tokensToIgnore = new WeakSet(); const precedence = astUtils.getPrecedence; @@ -86,6 +87,7 @@ module.exports = { context.options[1].enforceForNewInMemberExpressions === false; const IGNORE_FUNCTION_PROTOTYPE_METHODS = ALL_NODES && context.options[1] && context.options[1].enforceForFunctionPrototypeMethods === false; + const ALLOW_PARENS_AFTER_COMMENT_PATTERN = ALL_NODES && context.options[1] && context.options[1].allowParensAfterCommentPattern; const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" }); const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" }); @@ -402,6 +404,19 @@ module.exports = { if (isIIFE(node) && !isParenthesised(node.callee)) { return; } + + if (ALLOW_PARENS_AFTER_COMMENT_PATTERN) { + const commentsBeforeLeftParenToken = sourceCode.getCommentsBefore(leftParenToken); + const totalCommentsBeforeLeftParenTokenCount = commentsBeforeLeftParenToken.length; + const ignorePattern = new RegExp(ALLOW_PARENS_AFTER_COMMENT_PATTERN, "u"); + + if ( + totalCommentsBeforeLeftParenTokenCount > 0 && + ignorePattern.test(commentsBeforeLeftParenToken[totalCommentsBeforeLeftParenTokenCount - 1].value) + ) { + return; + } + } } /** @@ -751,6 +766,38 @@ module.exports = { return false; } + /** + * Checks if the left-hand side of an assignment is an identifier, the operator is one of + * `=`, `&&=`, `||=` or `??=` and the right-hand side is an anonymous class or function. + * + * As per https://tc39.es/ecma262/#sec-assignment-operators-runtime-semantics-evaluation, an + * assignment involving one of the operators `=`, `&&=`, `||=` or `??=` where the right-hand + * side is an anonymous class or function and the left-hand side is an *unparenthesized* + * identifier has different semantics than other assignments. + * Specifically, when an expression like `foo = function () {}` is evaluated, `foo.name` + * will be set to the string "foo", i.e. the identifier name. The same thing does not happen + * when evaluating `(foo) = function () {}`. + * Since the parenthesizing of the identifier in the left-hand side is significant in this + * special case, the parentheses, if present, should not be flagged as unnecessary. + * @param {ASTNode} node an AssignmentExpression node. + * @returns {boolean} `true` if the left-hand side of the assignment is an identifier, the + * operator is one of `=`, `&&=`, `||=` or `??=` and the right-hand side is an anonymous + * class or function; otherwise, `false`. + */ + function isAnonymousFunctionAssignmentException({ left, operator, right }) { + if (left.type === "Identifier" && ["=", "&&=", "||=", "??="].includes(operator)) { + const rhsType = right.type; + + if (rhsType === "ArrowFunctionExpression") { + return true; + } + if ((rhsType === "FunctionExpression" || rhsType === "ClassExpression") && !right.id) { + return true; + } + } + return false; + } + return { ArrayExpression(node) { node.elements @@ -789,7 +836,8 @@ module.exports = { }, AssignmentExpression(node) { - if (canBeAssignmentTarget(node.left) && hasExcessParens(node.left)) { + if (canBeAssignmentTarget(node.left) && hasExcessParens(node.left) && + (!isAnonymousFunctionAssignmentException(node) || isParenthesisedTwice(node.left))) { report(node.left); } diff --git a/eslint/lib/rules/no-extra-semi.js b/eslint/lib/rules/no-extra-semi.js index c61ad37..ebf145f 100644 --- a/eslint/lib/rules/no-extra-semi.js +++ b/eslint/lib/rules/no-extra-semi.js @@ -24,7 +24,7 @@ module.exports = { docs: { description: "Disallow unnecessary semicolons", recommended: true, - url: "https://eslint.org/docs/rules/no-extra-semi" + url: "https://eslint.org/docs/latest/rules/no-extra-semi" }, fixable: "code", @@ -36,7 +36,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Reports an unnecessary semicolon error. @@ -54,7 +54,7 @@ module.exports = { * tokens to avoid conflicting with semi. * https://github.com/eslint/eslint/issues/7928 */ - return new FixTracker(fixer, context.getSourceCode()) + return new FixTracker(fixer, context.sourceCode) .retainSurroundingTokens(nodeOrToken) .remove(nodeOrToken); } diff --git a/eslint/lib/rules/no-fallthrough.js b/eslint/lib/rules/no-fallthrough.js index 536aa21..bd2ee9b 100644 --- a/eslint/lib/rules/no-fallthrough.js +++ b/eslint/lib/rules/no-fallthrough.js @@ -4,12 +4,28 @@ */ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { directivesPattern } = require("../shared/directives"); + //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu; +/** + * Checks whether or not a given comment string is really a fallthrough comment and not an ESLint directive. + * @param {string} comment The comment string to check. + * @param {RegExp} fallthroughCommentPattern The regular expression used for checking for fallthrough comments. + * @returns {boolean} `true` if the comment string is truly a fallthrough comment. + */ +function isFallThroughComment(comment, fallthroughCommentPattern) { + return fallthroughCommentPattern.test(comment) && !directivesPattern.test(comment.trim()); +} + /** * Checks whether or not a given case has a fallthrough comment. * @param {ASTNode} caseWhichFallsThrough SwitchCase node which falls through. @@ -19,20 +35,20 @@ const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu; * @returns {boolean} `true` if the case has a valid fallthrough comment. */ function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; if (caseWhichFallsThrough.consequent.length === 1 && caseWhichFallsThrough.consequent[0].type === "BlockStatement") { const trailingCloseBrace = sourceCode.getLastToken(caseWhichFallsThrough.consequent[0]); const commentInBlock = sourceCode.getCommentsBefore(trailingCloseBrace).pop(); - if (commentInBlock && fallthroughCommentPattern.test(commentInBlock.value)) { + if (commentInBlock && isFallThroughComment(commentInBlock.value, fallthroughCommentPattern)) { return true; } } const comment = sourceCode.getCommentsBefore(subsequentCase).pop(); - return Boolean(comment && fallthroughCommentPattern.test(comment.value)); + return Boolean(comment && isFallThroughComment(comment.value, fallthroughCommentPattern)); } /** @@ -66,7 +82,7 @@ module.exports = { docs: { description: "Disallow fallthrough of `case` statements", recommended: true, - url: "https://eslint.org/docs/rules/no-fallthrough" + url: "https://eslint.org/docs/latest/rules/no-fallthrough" }, schema: [ @@ -94,7 +110,7 @@ module.exports = { create(context) { const options = context.options[0] || {}; let currentCodePath = null; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const allowEmptyCase = options.allowEmptyCase || false; /* diff --git a/eslint/lib/rules/no-floating-decimal.js b/eslint/lib/rules/no-floating-decimal.js index cce50bf..c268764 100644 --- a/eslint/lib/rules/no-floating-decimal.js +++ b/eslint/lib/rules/no-floating-decimal.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Disallow leading or trailing decimal points in numeric literals", recommended: false, - url: "https://eslint.org/docs/rules/no-floating-decimal" + url: "https://eslint.org/docs/latest/rules/no-floating-decimal" }, schema: [], @@ -35,7 +35,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { Literal(node) { diff --git a/eslint/lib/rules/no-func-assign.js b/eslint/lib/rules/no-func-assign.js index 2c8fa6a..8084af6 100644 --- a/eslint/lib/rules/no-func-assign.js +++ b/eslint/lib/rules/no-func-assign.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Disallow reassigning `function` declarations", recommended: true, - url: "https://eslint.org/docs/rules/no-func-assign" + url: "https://eslint.org/docs/latest/rules/no-func-assign" }, schema: [], @@ -31,6 +31,8 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + /** * Reports a reference if is non initializer and writable. * @param {References} references Collection of reference to check. @@ -65,7 +67,7 @@ module.exports = { * @returns {void} */ function checkForFunction(node) { - context.getDeclaredVariables(node).forEach(checkVariable); + sourceCode.getDeclaredVariables(node).forEach(checkVariable); } return { diff --git a/eslint/lib/rules/no-global-assign.js b/eslint/lib/rules/no-global-assign.js index 9f2f0ee..99ae7a2 100644 --- a/eslint/lib/rules/no-global-assign.js +++ b/eslint/lib/rules/no-global-assign.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow assignments to native objects or read-only global variables", recommended: true, - url: "https://eslint.org/docs/rules/no-global-assign" + url: "https://eslint.org/docs/latest/rules/no-global-assign" }, schema: [ @@ -41,6 +41,7 @@ module.exports = { create(context) { const config = context.options[0]; + const sourceCode = context.sourceCode; const exceptions = (config && config.exceptions) || []; /** @@ -84,8 +85,8 @@ module.exports = { } return { - Program() { - const globalScope = context.getScope(); + Program(node) { + const globalScope = sourceCode.getScope(node); globalScope.variables.forEach(checkVariable); } diff --git a/eslint/lib/rules/no-implicit-coercion.js b/eslint/lib/rules/no-implicit-coercion.js index c236771..36baad3 100644 --- a/eslint/lib/rules/no-implicit-coercion.js +++ b/eslint/lib/rules/no-implicit-coercion.js @@ -71,6 +71,24 @@ function isMultiplyByOne(node) { ); } +/** + * Checks whether the given node logically represents multiplication by a fraction of `1`. + * For example, `a * 1` in `a * 1 / b` is technically multiplication by `1`, but the + * whole expression can be logically interpreted as `a * (1 / b)` rather than `(a * 1) / b`. + * @param {BinaryExpression} node A BinaryExpression node to check. + * @param {SourceCode} sourceCode The source code object. + * @returns {boolean} Whether or not the node is a multiplying by a fraction of `1`. + */ +function isMultiplyByFractionOfOne(node, sourceCode) { + return node.type === "BinaryExpression" && + node.operator === "*" && + (node.right.type === "Literal" && node.right.value === 1) && + node.parent.type === "BinaryExpression" && + node.parent.operator === "/" && + node.parent.left === node && + !astUtils.isParenthesised(sourceCode, node); +} + /** * Checks whether the result of a node is numeric or not * @param {ASTNode} node The node to test @@ -175,7 +193,7 @@ module.exports = { docs: { description: "Disallow shorthand type conversions", recommended: false, - url: "https://eslint.org/docs/rules/no-implicit-coercion" + url: "https://eslint.org/docs/latest/rules/no-implicit-coercion" }, fixable: "code", @@ -217,7 +235,7 @@ module.exports = { create(context) { const options = parseOptions(context.options[0] || {}); - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Reports an error and autofixes the node @@ -290,7 +308,8 @@ module.exports = { // 1 * foo operatorAllowed = options.allow.includes("*"); - const nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && getNonNumericOperand(node); + const nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && !isMultiplyByFractionOfOne(node, sourceCode) && + getNonNumericOperand(node); if (nonNumericOperand) { const recommendation = `Number(${sourceCode.getText(nonNumericOperand)})`; diff --git a/eslint/lib/rules/no-implicit-globals.js b/eslint/lib/rules/no-implicit-globals.js index 934630e..2a18247 100644 --- a/eslint/lib/rules/no-implicit-globals.js +++ b/eslint/lib/rules/no-implicit-globals.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow declarations in the global scope", recommended: false, - url: "https://eslint.org/docs/rules/no-implicit-globals" + url: "https://eslint.org/docs/latest/rules/no-implicit-globals" }, schema: [{ @@ -43,6 +43,7 @@ module.exports = { create(context) { const checkLexicalBindings = context.options[0] && context.options[0].lexicalBindings === true; + const sourceCode = context.sourceCode; /** * Reports the node. @@ -62,8 +63,8 @@ module.exports = { } return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); scope.variables.forEach(variable => { @@ -77,6 +78,11 @@ module.exports = { return; } + // Variables exported by "exported" block comments + if (variable.eslintExported) { + return; + } + variable.defs.forEach(def => { const defNode = def.node; diff --git a/eslint/lib/rules/no-implied-eval.js b/eslint/lib/rules/no-implied-eval.js index 44f1461..9a84f8c 100644 --- a/eslint/lib/rules/no-implied-eval.js +++ b/eslint/lib/rules/no-implied-eval.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const { getStaticValue } = require("eslint-utils"); +const { getStaticValue } = require("@eslint-community/eslint-utils"); //------------------------------------------------------------------------------ // Rule Definition @@ -24,7 +24,7 @@ module.exports = { docs: { description: "Disallow the use of `eval()`-like methods", recommended: false, - url: "https://eslint.org/docs/rules/no-implied-eval" + url: "https://eslint.org/docs/latest/rules/no-implied-eval" }, schema: [], @@ -37,6 +37,7 @@ module.exports = { create(context) { const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]); const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u; + const sourceCode = context.sourceCode; /** * Checks whether a node is evaluated as a string or not. @@ -66,7 +67,7 @@ module.exports = { if (firstArgument) { - const staticValue = getStaticValue(firstArgument, context.getScope()); + const staticValue = getStaticValue(firstArgument, sourceCode.getScope(node)); const isStaticString = staticValue && typeof staticValue.value === "string"; const isString = isStaticString || isEvaluatedString(firstArgument); @@ -117,8 +118,8 @@ module.exports = { reportImpliedEvalCallExpression(node); } }, - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); GLOBAL_CANDIDATES .map(candidate => astUtils.getVariableByName(globalScope, candidate)) diff --git a/eslint/lib/rules/no-import-assign.js b/eslint/lib/rules/no-import-assign.js index fc104fe..c699886 100644 --- a/eslint/lib/rules/no-import-assign.js +++ b/eslint/lib/rules/no-import-assign.js @@ -9,7 +9,7 @@ // Helpers //------------------------------------------------------------------------------ -const { findVariable } = require("eslint-utils"); +const { findVariable } = require("@eslint-community/eslint-utils"); const astUtils = require("./utils/ast-utils"); const WellKnownMutationFunctions = { @@ -182,7 +182,7 @@ module.exports = { docs: { description: "Disallow assigning to imported bindings", recommended: true, - url: "https://eslint.org/docs/rules/no-import-assign" + url: "https://eslint.org/docs/latest/rules/no-import-assign" }, schema: [], @@ -194,11 +194,13 @@ module.exports = { }, create(context) { + const sourceCode = context.sourceCode; + return { ImportDeclaration(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); - for (const variable of context.getDeclaredVariables(node)) { + for (const variable of sourceCode.getDeclaredVariables(node)) { const shouldCheckMembers = variable.defs.some( d => d.node.type === "ImportNamespaceSpecifier" ); diff --git a/eslint/lib/rules/no-inline-comments.js b/eslint/lib/rules/no-inline-comments.js index 366f567..d96e647 100644 --- a/eslint/lib/rules/no-inline-comments.js +++ b/eslint/lib/rules/no-inline-comments.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Disallow inline comments after code", recommended: false, - url: "https://eslint.org/docs/rules/no-inline-comments" + url: "https://eslint.org/docs/latest/rules/no-inline-comments" }, schema: [ @@ -39,7 +39,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const options = context.options[0]; let customIgnoreRegExp; diff --git a/eslint/lib/rules/no-inner-declarations.js b/eslint/lib/rules/no-inner-declarations.js index 9328166..f4bae43 100644 --- a/eslint/lib/rules/no-inner-declarations.js +++ b/eslint/lib/rules/no-inner-declarations.js @@ -50,7 +50,7 @@ module.exports = { docs: { description: "Disallow variable or `function` declarations in nested blocks", recommended: true, - url: "https://eslint.org/docs/rules/no-inner-declarations" + url: "https://eslint.org/docs/latest/rules/no-inner-declarations" }, schema: [ diff --git a/eslint/lib/rules/no-invalid-regexp.js b/eslint/lib/rules/no-invalid-regexp.js index 0f1d9c7..9a35743 100644 --- a/eslint/lib/rules/no-invalid-regexp.js +++ b/eslint/lib/rules/no-invalid-regexp.js @@ -8,7 +8,7 @@ // Requirements //------------------------------------------------------------------------------ -const RegExpValidator = require("regexpp").RegExpValidator; +const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator; const validator = new RegExpValidator(); const validFlags = /[dgimsuy]/gu; const undefined1 = void 0; @@ -25,7 +25,7 @@ module.exports = { docs: { description: "Disallow invalid regular expression strings in `RegExp` constructors", recommended: true, - url: "https://eslint.org/docs/rules/no-invalid-regexp" + url: "https://eslint.org/docs/latest/rules/no-invalid-regexp" }, schema: [{ @@ -59,6 +59,20 @@ module.exports = { } } + /** + * Reports error with the provided message. + * @param {ASTNode} node The node holding the invalid RegExp + * @param {string} message The message to report. + * @returns {void} + */ + function report(node, message) { + context.report({ + node, + messageId: "regexMessage", + data: { message } + }); + } + /** * Check if node is a string * @param {ASTNode} node node to evaluate @@ -108,10 +122,13 @@ module.exports = { /** * Check syntax error in a given flags. - * @param {string} flags The RegExp flags to validate. + * @param {string|null} flags The RegExp flags to validate. * @returns {string|null} The syntax error. */ function validateRegExpFlags(flags) { + if (!flags) { + return null; + } try { validator.validateFlags(flags); return null; @@ -122,34 +139,39 @@ module.exports = { return { "CallExpression, NewExpression"(node) { - if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp" || !isString(node.arguments[0])) { + if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp") { return; } - const pattern = node.arguments[0].value; + let flags = getFlags(node); if (flags && allowedFlags) { flags = flags.replace(allowedFlags, ""); } - const message = - ( - flags && validateRegExpFlags(flags) - ) || - ( - - // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag - flags === null - ? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false) - : validateRegExpPattern(pattern, flags.includes("u")) - ); + let message = validateRegExpFlags(flags); if (message) { - context.report({ - node, - messageId: "regexMessage", - data: { message } - }); + report(node, message); + return; + } + + if (!isString(node.arguments[0])) { + return; + } + + const pattern = node.arguments[0].value; + + message = ( + + // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag + flags === null + ? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false) + : validateRegExpPattern(pattern, flags.includes("u")) + ); + + if (message) { + report(node, message); } } }; diff --git a/eslint/lib/rules/no-invalid-this.js b/eslint/lib/rules/no-invalid-this.js index b9cb43a..49ecbe5 100644 --- a/eslint/lib/rules/no-invalid-this.js +++ b/eslint/lib/rules/no-invalid-this.js @@ -38,7 +38,7 @@ module.exports = { docs: { description: "Disallow use of `this` in contexts where the value of `this` is `undefined`", recommended: false, - url: "https://eslint.org/docs/rules/no-invalid-this" + url: "https://eslint.org/docs/latest/rules/no-invalid-this" }, schema: [ @@ -63,7 +63,7 @@ module.exports = { const options = context.options[0] || {}; const capIsConstructor = options.capIsConstructor !== false; const stack = [], - sourceCode = context.getSourceCode(); + sourceCode = context.sourceCode; /** * Gets the current checking context. @@ -95,7 +95,7 @@ module.exports = { } if (codePath.origin === "program") { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const features = context.parserOptions.ecmaFeatures || {}; // `this` at the top level of scripts always refers to the global object @@ -120,7 +120,7 @@ module.exports = { * always valid, so we can set `init: true` right away. */ stack.push({ - init: !context.getScope().isStrict, + init: !sourceCode.getScope(node).isStrict, node, valid: true }); diff --git a/eslint/lib/rules/no-irregular-whitespace.js b/eslint/lib/rules/no-irregular-whitespace.js index d1646c7..67519b5 100644 --- a/eslint/lib/rules/no-irregular-whitespace.js +++ b/eslint/lib/rules/no-irregular-whitespace.js @@ -33,7 +33,7 @@ module.exports = { docs: { description: "Disallow irregular whitespace", recommended: true, - url: "https://eslint.org/docs/rules/no-irregular-whitespace" + url: "https://eslint.org/docs/latest/rules/no-irregular-whitespace" }, schema: [ @@ -78,7 +78,7 @@ module.exports = { const skipRegExps = !!options.skipRegExps; const skipTemplates = !!options.skipTemplates; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const commentNodes = sourceCode.getAllComments(); /** diff --git a/eslint/lib/rules/no-iterator.js b/eslint/lib/rules/no-iterator.js index 3550c7b..dcd9683 100644 --- a/eslint/lib/rules/no-iterator.js +++ b/eslint/lib/rules/no-iterator.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Disallow the use of the `__iterator__` property", recommended: false, - url: "https://eslint.org/docs/rules/no-iterator" + url: "https://eslint.org/docs/latest/rules/no-iterator" }, schema: [], diff --git a/eslint/lib/rules/no-label-var.js b/eslint/lib/rules/no-label-var.js index a07d283..bf33cd1 100644 --- a/eslint/lib/rules/no-label-var.js +++ b/eslint/lib/rules/no-label-var.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Disallow labels that share a name with a variable", recommended: false, - url: "https://eslint.org/docs/rules/no-label-var" + url: "https://eslint.org/docs/latest/rules/no-label-var" }, schema: [], @@ -34,6 +34,7 @@ module.exports = { }, create(context) { + const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers @@ -59,7 +60,7 @@ module.exports = { LabeledStatement(node) { // Fetch the innermost scope. - const scope = context.getScope(); + const scope = sourceCode.getScope(node); /* * Recursively find the identifier walking up the scope, starting diff --git a/eslint/lib/rules/no-labels.js b/eslint/lib/rules/no-labels.js index 7257307..d991a0a 100644 --- a/eslint/lib/rules/no-labels.js +++ b/eslint/lib/rules/no-labels.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Disallow labeled statements", recommended: false, - url: "https://eslint.org/docs/rules/no-labels" + url: "https://eslint.org/docs/latest/rules/no-labels" }, schema: [ diff --git a/eslint/lib/rules/no-lone-blocks.js b/eslint/lib/rules/no-lone-blocks.js index eb97f95..767eec2 100644 --- a/eslint/lib/rules/no-lone-blocks.js +++ b/eslint/lib/rules/no-lone-blocks.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow unnecessary nested blocks", recommended: false, - url: "https://eslint.org/docs/rules/no-lone-blocks" + url: "https://eslint.org/docs/latest/rules/no-lone-blocks" }, schema: [], @@ -33,6 +33,7 @@ module.exports = { // A stack of lone blocks to be checked for block-level bindings const loneBlocks = []; let ruleDef; + const sourceCode = context.sourceCode; /** * Reports a node as invalid. @@ -67,14 +68,15 @@ module.exports = { /** * Checks the enclosing block of the current node for block-level bindings, * and "marks it" as valid if any. + * @param {ASTNode} node The current node to check. * @returns {void} */ - function markLoneBlock() { + function markLoneBlock(node) { if (loneBlocks.length === 0) { return; } - const block = context.getAncestors().pop(); + const block = node.parent; if (loneBlocks[loneBlocks.length - 1] === block) { loneBlocks.pop(); @@ -116,13 +118,13 @@ module.exports = { ruleDef.VariableDeclaration = function(node) { if (node.kind === "let" || node.kind === "const") { - markLoneBlock(); + markLoneBlock(node); } }; - ruleDef.FunctionDeclaration = function() { - if (context.getScope().isStrict) { - markLoneBlock(); + ruleDef.FunctionDeclaration = function(node) { + if (sourceCode.getScope(node).isStrict) { + markLoneBlock(node); } }; diff --git a/eslint/lib/rules/no-lonely-if.js b/eslint/lib/rules/no-lonely-if.js index 0774b9f..eefd2c6 100644 --- a/eslint/lib/rules/no-lonely-if.js +++ b/eslint/lib/rules/no-lonely-if.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Disallow `if` statements as the only statement in `else` blocks", recommended: false, - url: "https://eslint.org/docs/rules/no-lonely-if" + url: "https://eslint.org/docs/latest/rules/no-lonely-if" }, schema: [], @@ -28,13 +28,12 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { IfStatement(node) { - const ancestors = context.getAncestors(), - parent = ancestors.pop(), - grandparent = ancestors.pop(); + const parent = node.parent, + grandparent = parent.parent; if (parent && parent.type === "BlockStatement" && parent.body.length === 1 && grandparent && diff --git a/eslint/lib/rules/no-loop-func.js b/eslint/lib/rules/no-loop-func.js index f81a713..e1d65fd 100644 --- a/eslint/lib/rules/no-loop-func.js +++ b/eslint/lib/rules/no-loop-func.js @@ -156,7 +156,7 @@ module.exports = { docs: { description: "Disallow function declarations that contain unsafe references inside loop statements", recommended: false, - url: "https://eslint.org/docs/rules/no-loop-func" + url: "https://eslint.org/docs/latest/rules/no-loop-func" }, schema: [], @@ -168,6 +168,8 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + /** * Reports functions which match the following condition: * @@ -183,7 +185,7 @@ module.exports = { return; } - const references = context.getScope().through; + const references = sourceCode.getScope(node).through; const unsafeRefs = references.filter(r => !isSafe(loopNode, r)).map(r => r.identifier.name); if (unsafeRefs.length > 0) { diff --git a/eslint/lib/rules/no-loss-of-precision.js b/eslint/lib/rules/no-loss-of-precision.js index 6dc6d86..22ca7f9 100644 --- a/eslint/lib/rules/no-loss-of-precision.js +++ b/eslint/lib/rules/no-loss-of-precision.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow literal numbers that lose precision", recommended: true, - url: "https://eslint.org/docs/rules/no-loss-of-precision" + url: "https://eslint.org/docs/latest/rules/no-loss-of-precision" }, schema: [], messages: { @@ -105,7 +105,7 @@ module.exports = { } /** - * Converts an integer to to an object containing the integer's coefficient and order of magnitude + * Converts an integer to an object containing the integer's coefficient and order of magnitude * @param {string} stringInteger the string representation of the integer being converted * @returns {Object} the object containing the integer's coefficient and order of magnitude */ @@ -120,7 +120,7 @@ module.exports = { /** * - * Converts a float to to an object containing the floats's coefficient and order of magnitude + * Converts a float to an object containing the floats's coefficient and order of magnitude * @param {string} stringFloat the string representation of the float being converted * @returns {Object} the object containing the integer's coefficient and order of magnitude */ diff --git a/eslint/lib/rules/no-magic-numbers.js b/eslint/lib/rules/no-magic-numbers.js index 9b08588..f48a62d 100644 --- a/eslint/lib/rules/no-magic-numbers.js +++ b/eslint/lib/rules/no-magic-numbers.js @@ -34,7 +34,7 @@ module.exports = { docs: { description: "Disallow magic numbers", recommended: false, - url: "https://eslint.org/docs/rules/no-magic-numbers" + url: "https://eslint.org/docs/latest/rules/no-magic-numbers" }, schema: [{ @@ -65,6 +65,10 @@ module.exports = { ignoreDefaultValues: { type: "boolean", default: false + }, + ignoreClassFieldInitialValues: { + type: "boolean", + default: false } }, additionalProperties: false @@ -82,7 +86,8 @@ module.exports = { enforceConst = !!config.enforceConst, ignore = new Set((config.ignore || []).map(normalizeIgnoreValue)), ignoreArrayIndexes = !!config.ignoreArrayIndexes, - ignoreDefaultValues = !!config.ignoreDefaultValues; + ignoreDefaultValues = !!config.ignoreDefaultValues, + ignoreClassFieldInitialValues = !!config.ignoreClassFieldInitialValues; const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"]; @@ -106,6 +111,17 @@ module.exports = { return parent.type === "AssignmentPattern" && parent.right === fullNumberNode; } + /** + * Returns whether the number is the initial value of a class field. + * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node + * @returns {boolean} true if the number is the initial value of a class field. + */ + function isClassFieldInitialValue(fullNumberNode) { + const parent = fullNumberNode.parent; + + return parent.type === "PropertyDefinition" && parent.value === fullNumberNode; + } + /** * Returns whether the given node is used as a radix within parseInt() or Number.parseInt() * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node @@ -194,6 +210,7 @@ module.exports = { if ( isIgnoredValue(value) || (ignoreDefaultValues && isDefaultValue(fullNumberNode)) || + (ignoreClassFieldInitialValues && isClassFieldInitialValue(fullNumberNode)) || isParseIntRadix(fullNumberNode) || isJSXNumber(fullNumberNode) || (ignoreArrayIndexes && isArrayIndex(fullNumberNode, value)) diff --git a/eslint/lib/rules/no-misleading-character-class.js b/eslint/lib/rules/no-misleading-character-class.js index 667d066..47ee84e 100644 --- a/eslint/lib/rules/no-misleading-character-class.js +++ b/eslint/lib/rules/no-misleading-character-class.js @@ -3,17 +3,16 @@ */ "use strict"; -const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils"); -const { RegExpValidator, RegExpParser, visitRegExpAST } = require("regexpp"); +const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils"); +const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode"); const astUtils = require("./utils/ast-utils.js"); +const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const REGEXPP_LATEST_ECMA_VERSION = 2022; - /** * Iterate character sequences of a given nodes. * @@ -109,7 +108,7 @@ module.exports = { docs: { description: "Disallow characters which are made with multiple code points in character class syntax", recommended: true, - url: "https://eslint.org/docs/rules/no-misleading-character-class" + url: "https://eslint.org/docs/latest/rules/no-misleading-character-class" }, hasSuggestions: true, @@ -126,7 +125,7 @@ module.exports = { } }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const parser = new RegExpParser(); /** @@ -185,46 +184,18 @@ module.exports = { } } - /** - * Checks if the given regular expression pattern would be valid with the `u` flag. - * @param {string} pattern The regular expression pattern to verify. - * @returns {boolean} `true` if the pattern would be valid with the `u` flag. - * `false` if the pattern would be invalid with the `u` flag or the configured - * ecmaVersion doesn't support the `u` flag. - */ - function isValidWithUnicodeFlag(pattern) { - const { ecmaVersion } = context.parserOptions; - - // ecmaVersion is unknown or it doesn't support the 'u' flag - if (typeof ecmaVersion !== "number" || ecmaVersion <= 5) { - return false; - } - - const validator = new RegExpValidator({ - ecmaVersion: Math.min(ecmaVersion + 2009, REGEXPP_LATEST_ECMA_VERSION) - }); - - try { - validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true); - } catch { - return false; - } - - return true; - } - return { "Literal[regex]"(node) { verify(node, node.regex.pattern, node.regex.flags, fixer => { - if (!isValidWithUnicodeFlag(node.regex.pattern)) { + if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)) { return null; } return fixer.insertTextAfter(node, "u"); }); }, - "Program"() { - const scope = context.getScope(); + "Program"(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); /* @@ -232,22 +203,22 @@ module.exports = { * E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`, * `const {RegExp: a} = window; new a()`, etc... */ - for (const { node } of tracker.iterateGlobalReferences({ + for (const { node: refNode } of tracker.iterateGlobalReferences({ RegExp: { [CALL]: true, [CONSTRUCT]: true } })) { - const [patternNode, flagsNode] = node.arguments; + const [patternNode, flagsNode] = refNode.arguments; const pattern = getStringIfConstant(patternNode, scope); const flags = getStringIfConstant(flagsNode, scope); if (typeof pattern === "string") { - verify(node, pattern, flags || "", fixer => { + verify(refNode, pattern, flags || "", fixer => { - if (!isValidWithUnicodeFlag(pattern)) { + if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)) { return null; } - if (node.arguments.length === 1) { - const penultimateToken = sourceCode.getLastToken(node, { skip: 1 }); // skip closing parenthesis + if (refNode.arguments.length === 1) { + const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis return fixer.insertTextAfter( penultimateToken, diff --git a/eslint/lib/rules/no-mixed-operators.js b/eslint/lib/rules/no-mixed-operators.js index cb6e936..724abe0 100644 --- a/eslint/lib/rules/no-mixed-operators.js +++ b/eslint/lib/rules/no-mixed-operators.js @@ -90,7 +90,7 @@ module.exports = { docs: { description: "Disallow mixed binary operators", recommended: false, - url: "https://eslint.org/docs/rules/no-mixed-operators" + url: "https://eslint.org/docs/latest/rules/no-mixed-operators" }, schema: [ @@ -122,7 +122,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const options = normalizeOptions(context.options[0]); /** diff --git a/eslint/lib/rules/no-mixed-requires.js b/eslint/lib/rules/no-mixed-requires.js index 4e97057..9e7b803 100644 --- a/eslint/lib/rules/no-mixed-requires.js +++ b/eslint/lib/rules/no-mixed-requires.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Disallow `require` calls to be mixed with regular variable declarations", recommended: false, - url: "https://eslint.org/docs/rules/no-mixed-requires" + url: "https://eslint.org/docs/latest/rules/no-mixed-requires" }, schema: [ diff --git a/eslint/lib/rules/no-mixed-spaces-and-tabs.js b/eslint/lib/rules/no-mixed-spaces-and-tabs.js index b2d5a04..a18e4f3 100644 --- a/eslint/lib/rules/no-mixed-spaces-and-tabs.js +++ b/eslint/lib/rules/no-mixed-spaces-and-tabs.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Disallow mixed spaces and tabs for indentation", recommended: true, - url: "https://eslint.org/docs/rules/no-mixed-spaces-and-tabs" + url: "https://eslint.org/docs/latest/rules/no-mixed-spaces-and-tabs" }, schema: [ @@ -31,7 +31,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; let smartTabs; diff --git a/eslint/lib/rules/no-multi-assign.js b/eslint/lib/rules/no-multi-assign.js index 392b33f..a7a50c1 100644 --- a/eslint/lib/rules/no-multi-assign.js +++ b/eslint/lib/rules/no-multi-assign.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Disallow use of chained assignment expressions", recommended: false, - url: "https://eslint.org/docs/rules/no-multi-assign" + url: "https://eslint.org/docs/latest/rules/no-multi-assign" }, schema: [{ diff --git a/eslint/lib/rules/no-multi-spaces.js b/eslint/lib/rules/no-multi-spaces.js index d8d3c65..62074e6 100644 --- a/eslint/lib/rules/no-multi-spaces.js +++ b/eslint/lib/rules/no-multi-spaces.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Disallow multiple spaces", recommended: false, - url: "https://eslint.org/docs/rules/no-multi-spaces" + url: "https://eslint.org/docs/latest/rules/no-multi-spaces" }, fixable: "whitespace", @@ -52,7 +52,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const options = context.options[0] || {}; const ignoreEOLComments = options.ignoreEOLComments; const exceptions = Object.assign({ Property: true }, options.exceptions); diff --git a/eslint/lib/rules/no-multi-str.js b/eslint/lib/rules/no-multi-str.js index c4400f4..8011729 100644 --- a/eslint/lib/rules/no-multi-str.js +++ b/eslint/lib/rules/no-multi-str.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Disallow multiline strings", recommended: false, - url: "https://eslint.org/docs/rules/no-multi-str" + url: "https://eslint.org/docs/latest/rules/no-multi-str" }, schema: [], diff --git a/eslint/lib/rules/no-multiple-empty-lines.js b/eslint/lib/rules/no-multiple-empty-lines.js index e8b0f98..2c0fbf2 100644 --- a/eslint/lib/rules/no-multiple-empty-lines.js +++ b/eslint/lib/rules/no-multiple-empty-lines.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow multiple empty lines", recommended: false, - url: "https://eslint.org/docs/rules/no-multiple-empty-lines" + url: "https://eslint.org/docs/latest/rules/no-multiple-empty-lines" }, fixable: "whitespace", @@ -64,7 +64,7 @@ module.exports = { maxBOF = typeof context.options[0].maxBOF !== "undefined" ? context.options[0].maxBOF : max; } - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; // Swallow the final newline, as some editors add it automatically and we don't want it to cause an issue const allLines = sourceCode.lines[sourceCode.lines.length - 1] === "" ? sourceCode.lines.slice(0, -1) : sourceCode.lines; diff --git a/eslint/lib/rules/no-native-reassign.js b/eslint/lib/rules/no-native-reassign.js index 634fea9..e3fed44 100644 --- a/eslint/lib/rules/no-native-reassign.js +++ b/eslint/lib/rules/no-native-reassign.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Disallow assignments to native objects or read-only global variables", recommended: false, - url: "https://eslint.org/docs/rules/no-native-reassign" + url: "https://eslint.org/docs/latest/rules/no-native-reassign" }, deprecated: true, @@ -47,6 +47,7 @@ module.exports = { create(context) { const config = context.options[0]; const exceptions = (config && config.exceptions) || []; + const sourceCode = context.sourceCode; /** * Reports write references. @@ -87,8 +88,8 @@ module.exports = { } return { - Program() { - const globalScope = context.getScope(); + Program(node) { + const globalScope = sourceCode.getScope(node); globalScope.variables.forEach(checkVariable); } diff --git a/eslint/lib/rules/no-negated-condition.js b/eslint/lib/rules/no-negated-condition.js index 3876177..3cb7590 100644 --- a/eslint/lib/rules/no-negated-condition.js +++ b/eslint/lib/rules/no-negated-condition.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Disallow negated conditions", recommended: false, - url: "https://eslint.org/docs/rules/no-negated-condition" + url: "https://eslint.org/docs/latest/rules/no-negated-condition" }, schema: [], diff --git a/eslint/lib/rules/no-negated-in-lhs.js b/eslint/lib/rules/no-negated-in-lhs.js index 975a8d7..7a50be7 100644 --- a/eslint/lib/rules/no-negated-in-lhs.js +++ b/eslint/lib/rules/no-negated-in-lhs.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Disallow negating the left operand in `in` expressions", recommended: false, - url: "https://eslint.org/docs/rules/no-negated-in-lhs" + url: "https://eslint.org/docs/latest/rules/no-negated-in-lhs" }, replacedBy: ["no-unsafe-negation"], diff --git a/eslint/lib/rules/no-nested-ternary.js b/eslint/lib/rules/no-nested-ternary.js index fe97823..faf8041 100644 --- a/eslint/lib/rules/no-nested-ternary.js +++ b/eslint/lib/rules/no-nested-ternary.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow nested ternary expressions", recommended: false, - url: "https://eslint.org/docs/rules/no-nested-ternary" + url: "https://eslint.org/docs/latest/rules/no-nested-ternary" }, schema: [], diff --git a/eslint/lib/rules/no-new-func.js b/eslint/lib/rules/no-new-func.js index 4759f38..d58b2d7 100644 --- a/eslint/lib/rules/no-new-func.js +++ b/eslint/lib/rules/no-new-func.js @@ -29,7 +29,7 @@ module.exports = { docs: { description: "Disallow `new` operators with the `Function` object", recommended: false, - url: "https://eslint.org/docs/rules/no-new-func" + url: "https://eslint.org/docs/latest/rules/no-new-func" }, schema: [], @@ -40,27 +40,28 @@ module.exports = { }, create(context) { + const sourceCode = context.sourceCode; return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); const variable = globalScope.set.get("Function"); if (variable && variable.defs.length === 0) { variable.references.forEach(ref => { - const node = ref.identifier; - const { parent } = node; + const idNode = ref.identifier; + const { parent } = idNode; let evalNode; if (parent) { - if (node === parent.callee && ( + if (idNode === parent.callee && ( parent.type === "NewExpression" || parent.type === "CallExpression" )) { evalNode = parent; } else if ( parent.type === "MemberExpression" && - node === parent.object && + idNode === parent.object && callMethods.has(astUtils.getStaticPropertyName(parent)) ) { const maybeCallee = parent.parent.type === "ChainExpression" ? parent.parent : parent; diff --git a/eslint/lib/rules/no-new-native-nonconstructor.js b/eslint/lib/rules/no-new-native-nonconstructor.js new file mode 100644 index 0000000..ee70d28 --- /dev/null +++ b/eslint/lib/rules/no-new-native-nonconstructor.js @@ -0,0 +1,66 @@ +/** + * @fileoverview Rule to disallow use of the new operator with global non-constructor functions + * @author Sosuke Suzuki + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const nonConstructorGlobalFunctionNames = ["Symbol", "BigInt"]; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +/** @type {import('../shared/types').Rule} */ +module.exports = { + meta: { + type: "problem", + + docs: { + description: "Disallow `new` operators with global non-constructor functions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-new-native-nonconstructor" + }, + + schema: [], + + messages: { + noNewNonconstructor: "`{{name}}` cannot be called as a constructor." + } + }, + + create(context) { + + const sourceCode = context.sourceCode; + + return { + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); + + for (const nonConstructorName of nonConstructorGlobalFunctionNames) { + const variable = globalScope.set.get(nonConstructorName); + + if (variable && variable.defs.length === 0) { + variable.references.forEach(ref => { + const idNode = ref.identifier; + const parent = idNode.parent; + + if (parent && parent.type === "NewExpression" && parent.callee === idNode) { + context.report({ + node: idNode, + messageId: "noNewNonconstructor", + data: { name: nonConstructorName } + }); + } + }); + } + } + } + }; + + } +}; diff --git a/eslint/lib/rules/no-new-object.js b/eslint/lib/rules/no-new-object.js index 4dbe8db..08a482b 100644 --- a/eslint/lib/rules/no-new-object.js +++ b/eslint/lib/rules/no-new-object.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Disallow `Object` constructors", recommended: false, - url: "https://eslint.org/docs/rules/no-new-object" + url: "https://eslint.org/docs/latest/rules/no-new-object" }, schema: [], @@ -34,10 +34,13 @@ module.exports = { }, create(context) { + + const sourceCode = context.sourceCode; + return { NewExpression(node) { const variable = astUtils.getVariableByName( - context.getScope(), + sourceCode.getScope(node), node.callee.name ); diff --git a/eslint/lib/rules/no-new-require.js b/eslint/lib/rules/no-new-require.js index 63ca057..6abfc17 100644 --- a/eslint/lib/rules/no-new-require.js +++ b/eslint/lib/rules/no-new-require.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Disallow `new` operators with calls to `require`", recommended: false, - url: "https://eslint.org/docs/rules/no-new-require" + url: "https://eslint.org/docs/latest/rules/no-new-require" }, schema: [], diff --git a/eslint/lib/rules/no-new-symbol.js b/eslint/lib/rules/no-new-symbol.js index 534201c..9983022 100644 --- a/eslint/lib/rules/no-new-symbol.js +++ b/eslint/lib/rules/no-new-symbol.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow `new` operators with the `Symbol` object", recommended: true, - url: "https://eslint.org/docs/rules/no-new-symbol" + url: "https://eslint.org/docs/latest/rules/no-new-symbol" }, schema: [], @@ -29,19 +29,21 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); const variable = globalScope.set.get("Symbol"); if (variable && variable.defs.length === 0) { variable.references.forEach(ref => { - const node = ref.identifier; - const parent = node.parent; + const idNode = ref.identifier; + const parent = idNode.parent; - if (parent && parent.type === "NewExpression" && parent.callee === node) { + if (parent && parent.type === "NewExpression" && parent.callee === idNode) { context.report({ - node, + node: idNode, messageId: "noNewSymbol" }); } diff --git a/eslint/lib/rules/no-new-wrappers.js b/eslint/lib/rules/no-new-wrappers.js index ff44efc..9a12e1a 100644 --- a/eslint/lib/rules/no-new-wrappers.js +++ b/eslint/lib/rules/no-new-wrappers.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow `new` operators with the `String`, `Number`, and `Boolean` objects", recommended: false, - url: "https://eslint.org/docs/rules/no-new-wrappers" + url: "https://eslint.org/docs/latest/rules/no-new-wrappers" }, schema: [], diff --git a/eslint/lib/rules/no-new.js b/eslint/lib/rules/no-new.js index c434505..9e20bad 100644 --- a/eslint/lib/rules/no-new.js +++ b/eslint/lib/rules/no-new.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Disallow `new` operators outside of assignments or comparisons", recommended: false, - url: "https://eslint.org/docs/rules/no-new" + url: "https://eslint.org/docs/latest/rules/no-new" }, schema: [], diff --git a/eslint/lib/rules/no-nonoctal-decimal-escape.js b/eslint/lib/rules/no-nonoctal-decimal-escape.js index 63e2264..5939390 100644 --- a/eslint/lib/rules/no-nonoctal-decimal-escape.js +++ b/eslint/lib/rules/no-nonoctal-decimal-escape.js @@ -32,7 +32,7 @@ module.exports = { docs: { description: "Disallow `\\8` and `\\9` escape sequences in string literals", recommended: true, - url: "https://eslint.org/docs/rules/no-nonoctal-decimal-escape" + url: "https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape" }, hasSuggestions: true, @@ -49,7 +49,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Creates a new Suggestion object. diff --git a/eslint/lib/rules/no-obj-calls.js b/eslint/lib/rules/no-obj-calls.js index 86355d8..ee767ea 100644 --- a/eslint/lib/rules/no-obj-calls.js +++ b/eslint/lib/rules/no-obj-calls.js @@ -9,14 +9,14 @@ // Requirements //------------------------------------------------------------------------------ -const { CALL, CONSTRUCT, ReferenceTracker } = require("eslint-utils"); +const { CALL, CONSTRUCT, ReferenceTracker } = require("@eslint-community/eslint-utils"); const getPropertyName = require("./utils/ast-utils").getStaticPropertyName; //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect"]; +const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect", "Intl"]; /** * Returns the name of the node to report @@ -45,7 +45,7 @@ module.exports = { docs: { description: "Disallow calling global object properties as functions", recommended: true, - url: "https://eslint.org/docs/rules/no-obj-calls" + url: "https://eslint.org/docs/latest/rules/no-obj-calls" }, schema: [], @@ -58,9 +58,11 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const traceMap = {}; @@ -71,12 +73,12 @@ module.exports = { }; } - for (const { node, path } of tracker.iterateGlobalReferences(traceMap)) { - const name = getReportNodeName(node.callee); + for (const { node: refNode, path } of tracker.iterateGlobalReferences(traceMap)) { + const name = getReportNodeName(refNode.callee); const ref = path[0]; const messageId = name === ref ? "unexpectedCall" : "unexpectedRefCall"; - context.report({ node, messageId, data: { name, ref } }); + context.report({ node: refNode, messageId, data: { name, ref } }); } } }; diff --git a/eslint/lib/rules/no-octal-escape.js b/eslint/lib/rules/no-octal-escape.js index 81a8a74..6924d54 100644 --- a/eslint/lib/rules/no-octal-escape.js +++ b/eslint/lib/rules/no-octal-escape.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow octal escape sequences in string literals", recommended: false, - url: "https://eslint.org/docs/rules/no-octal-escape" + url: "https://eslint.org/docs/latest/rules/no-octal-escape" }, schema: [], diff --git a/eslint/lib/rules/no-octal.js b/eslint/lib/rules/no-octal.js index eec5691..dc02769 100644 --- a/eslint/lib/rules/no-octal.js +++ b/eslint/lib/rules/no-octal.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow octal literals", recommended: true, - url: "https://eslint.org/docs/rules/no-octal" + url: "https://eslint.org/docs/latest/rules/no-octal" }, schema: [], diff --git a/eslint/lib/rules/no-param-reassign.js b/eslint/lib/rules/no-param-reassign.js index f89435c..607cafd 100644 --- a/eslint/lib/rules/no-param-reassign.js +++ b/eslint/lib/rules/no-param-reassign.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Disallow reassigning `function` parameters", recommended: false, - url: "https://eslint.org/docs/rules/no-param-reassign" + url: "https://eslint.org/docs/latest/rules/no-param-reassign" }, schema: [ @@ -70,6 +70,7 @@ module.exports = { const props = context.options[0] && context.options[0].props; const ignoredPropertyAssignmentsFor = context.options[0] && context.options[0].ignorePropertyModificationsFor || []; const ignoredPropertyAssignmentsForRegex = context.options[0] && context.options[0].ignorePropertyModificationsForRegex || []; + const sourceCode = context.sourceCode; /** * Checks whether or not the reference modifies properties of its variable. @@ -214,7 +215,7 @@ module.exports = { * @returns {void} */ function checkForFunction(node) { - context.getDeclaredVariables(node).forEach(checkVariable); + sourceCode.getDeclaredVariables(node).forEach(checkVariable); } return { diff --git a/eslint/lib/rules/no-path-concat.js b/eslint/lib/rules/no-path-concat.js index 8502c51..2e4a3a2 100644 --- a/eslint/lib/rules/no-path-concat.js +++ b/eslint/lib/rules/no-path-concat.js @@ -21,7 +21,7 @@ module.exports = { docs: { description: "Disallow string concatenation with `__dirname` and `__filename`", recommended: false, - url: "https://eslint.org/docs/rules/no-path-concat" + url: "https://eslint.org/docs/latest/rules/no-path-concat" }, schema: [], diff --git a/eslint/lib/rules/no-plusplus.js b/eslint/lib/rules/no-plusplus.js index cda6b05..22a6fd0 100644 --- a/eslint/lib/rules/no-plusplus.js +++ b/eslint/lib/rules/no-plusplus.js @@ -53,7 +53,7 @@ module.exports = { docs: { description: "Disallow the unary operators `++` and `--`", recommended: false, - url: "https://eslint.org/docs/rules/no-plusplus" + url: "https://eslint.org/docs/latest/rules/no-plusplus" }, schema: [ diff --git a/eslint/lib/rules/no-process-env.js b/eslint/lib/rules/no-process-env.js index 5db7c94..8dac648 100644 --- a/eslint/lib/rules/no-process-env.js +++ b/eslint/lib/rules/no-process-env.js @@ -21,7 +21,7 @@ module.exports = { docs: { description: "Disallow the use of `process.env`", recommended: false, - url: "https://eslint.org/docs/rules/no-process-env" + url: "https://eslint.org/docs/latest/rules/no-process-env" }, schema: [], diff --git a/eslint/lib/rules/no-process-exit.js b/eslint/lib/rules/no-process-exit.js index ca3ecfe..fa398a7 100644 --- a/eslint/lib/rules/no-process-exit.js +++ b/eslint/lib/rules/no-process-exit.js @@ -21,7 +21,7 @@ module.exports = { docs: { description: "Disallow the use of `process.exit()`", recommended: false, - url: "https://eslint.org/docs/rules/no-process-exit" + url: "https://eslint.org/docs/latest/rules/no-process-exit" }, schema: [], diff --git a/eslint/lib/rules/no-promise-executor-return.js b/eslint/lib/rules/no-promise-executor-return.js index caa195f..d46a730 100644 --- a/eslint/lib/rules/no-promise-executor-return.js +++ b/eslint/lib/rules/no-promise-executor-return.js @@ -9,7 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -const { findVariable } = require("eslint-utils"); +const { findVariable } = require("@eslint-community/eslint-utils"); //------------------------------------------------------------------------------ // Helpers @@ -71,7 +71,7 @@ module.exports = { docs: { description: "Disallow returning values from Promise executor functions", recommended: false, - url: "https://eslint.org/docs/rules/no-promise-executor-return" + url: "https://eslint.org/docs/latest/rules/no-promise-executor-return" }, schema: [], @@ -84,6 +84,7 @@ module.exports = { create(context) { let funcInfo = null; + const sourceCode = context.sourceCode; /** * Reports the given node. @@ -99,7 +100,7 @@ module.exports = { onCodePathStart(_, node) { funcInfo = { upper: funcInfo, - shouldCheck: functionTypesToCheck.has(node.type) && isPromiseExecutor(node, context.getScope()) + shouldCheck: functionTypesToCheck.has(node.type) && isPromiseExecutor(node, sourceCode.getScope(node)) }; if (funcInfo.shouldCheck && node.type === "ArrowFunctionExpression" && node.expression) { diff --git a/eslint/lib/rules/no-proto.js b/eslint/lib/rules/no-proto.js index 771d206..28320d5 100644 --- a/eslint/lib/rules/no-proto.js +++ b/eslint/lib/rules/no-proto.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Disallow the use of the `__proto__` property", recommended: false, - url: "https://eslint.org/docs/rules/no-proto" + url: "https://eslint.org/docs/latest/rules/no-proto" }, schema: [], diff --git a/eslint/lib/rules/no-prototype-builtins.js b/eslint/lib/rules/no-prototype-builtins.js index ea27633..a7a57bc 100644 --- a/eslint/lib/rules/no-prototype-builtins.js +++ b/eslint/lib/rules/no-prototype-builtins.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Disallow calling some `Object.prototype` methods directly on objects", recommended: true, - url: "https://eslint.org/docs/rules/no-prototype-builtins" + url: "https://eslint.org/docs/latest/rules/no-prototype-builtins" }, schema: [], diff --git a/eslint/lib/rules/no-redeclare.js b/eslint/lib/rules/no-redeclare.js index 59749cb..8a4877e 100644 --- a/eslint/lib/rules/no-redeclare.js +++ b/eslint/lib/rules/no-redeclare.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Disallow variable redeclaration", recommended: true, - url: "https://eslint.org/docs/rules/no-redeclare" + url: "https://eslint.org/docs/latest/rules/no-redeclare" }, messages: { @@ -50,7 +50,7 @@ module.exports = { context.options[0].builtinGlobals ) }; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Iterate declarations of a given variable. @@ -129,7 +129,7 @@ module.exports = { * @private */ function checkForBlock(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); /* * In ES5, some node type such as `BlockStatement` doesn't have that scope. @@ -141,8 +141,8 @@ module.exports = { } return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); findVariablesInScope(scope); diff --git a/eslint/lib/rules/no-regex-spaces.js b/eslint/lib/rules/no-regex-spaces.js index 6d74aab..e7fae6d 100644 --- a/eslint/lib/rules/no-regex-spaces.js +++ b/eslint/lib/rules/no-regex-spaces.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const regexpp = require("regexpp"); +const regexpp = require("@eslint-community/regexpp"); //------------------------------------------------------------------------------ // Helpers @@ -41,7 +41,7 @@ module.exports = { docs: { description: "Disallow multiple spaces in regular expressions", recommended: true, - url: "https://eslint.org/docs/rules/no-regex-spaces" + url: "https://eslint.org/docs/latest/rules/no-regex-spaces" }, schema: [], @@ -54,6 +54,8 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + /** * Validate regular expression * @param {ASTNode} nodeToReport Node to report. @@ -149,7 +151,7 @@ module.exports = { * @private */ function checkFunction(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const regExpVar = astUtils.getVariableByName(scope, "RegExp"); const shadowed = regExpVar && regExpVar.defs.length > 0; const patternNode = node.arguments[0]; diff --git a/eslint/lib/rules/no-restricted-exports.js b/eslint/lib/rules/no-restricted-exports.js index d99e892..a1d54b0 100644 --- a/eslint/lib/rules/no-restricted-exports.js +++ b/eslint/lib/rules/no-restricted-exports.js @@ -23,31 +23,83 @@ module.exports = { docs: { description: "Disallow specified names in exports", recommended: false, - url: "https://eslint.org/docs/rules/no-restricted-exports" + url: "https://eslint.org/docs/latest/rules/no-restricted-exports" }, schema: [{ - type: "object", - properties: { - restrictedNamedExports: { - type: "array", - items: { - type: "string" + anyOf: [ + { + type: "object", + properties: { + restrictedNamedExports: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } }, - uniqueItems: true + additionalProperties: false + }, + { + type: "object", + properties: { + restrictedNamedExports: { + type: "array", + items: { + type: "string", + pattern: "^(?!default$)" + }, + uniqueItems: true + }, + restrictDefaultExports: { + type: "object", + properties: { + + // Allow/Disallow `export default foo; export default 42; export default function foo() {}` format + direct: { + type: "boolean" + }, + + // Allow/Disallow `export { foo as default };` declarations + named: { + type: "boolean" + }, + + // Allow/Disallow `export { default } from "mod"; export { default as default } from "mod";` declarations + defaultFrom: { + type: "boolean" + }, + + // Allow/Disallow `export { foo as default } from "mod";` declarations + namedFrom: { + type: "boolean" + }, + + // Allow/Disallow `export * as default from "mod"`; declarations + namespaceFrom: { + type: "boolean" + } + }, + additionalProperties: false + } + }, + additionalProperties: false } - }, - additionalProperties: false + ] }], messages: { - restrictedNamed: "'{{name}}' is restricted from being used as an exported name." + restrictedNamed: "'{{name}}' is restricted from being used as an exported name.", + restrictedDefault: "Exporting 'default' is restricted." } }, create(context) { const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports); + const restrictDefaultExports = context.options[0] && context.options[0].restrictDefaultExports; + const sourceCode = context.sourceCode; /** * Checks and reports given exported name. @@ -63,6 +115,42 @@ module.exports = { messageId: "restrictedNamed", data: { name } }); + return; + } + + if (name === "default") { + if (node.parent.type === "ExportAllDeclaration") { + if (restrictDefaultExports && restrictDefaultExports.namespaceFrom) { + context.report({ + node, + messageId: "restrictedDefault" + }); + } + + } else { // ExportSpecifier + const isSourceSpecified = !!node.parent.parent.source; + const specifierLocalName = astUtils.getModuleExportName(node.parent.local); + + if (!isSourceSpecified && restrictDefaultExports && restrictDefaultExports.named) { + context.report({ + node, + messageId: "restrictedDefault" + }); + return; + } + + if (isSourceSpecified && restrictDefaultExports) { + if ( + (specifierLocalName === "default" && restrictDefaultExports.defaultFrom) || + (specifierLocalName !== "default" && restrictDefaultExports.namedFrom) + ) { + context.report({ + node, + messageId: "restrictedDefault" + }); + } + } + } } } @@ -73,6 +161,15 @@ module.exports = { } }, + ExportDefaultDeclaration(node) { + if (restrictDefaultExports && restrictDefaultExports.direct) { + context.report({ + node, + messageId: "restrictedDefault" + }); + } + }, + ExportNamedDeclaration(node) { const declaration = node.declaration; @@ -80,7 +177,7 @@ module.exports = { if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") { checkExportedName(declaration.id); } else if (declaration.type === "VariableDeclaration") { - context.getDeclaredVariables(declaration) + sourceCode.getDeclaredVariables(declaration) .map(v => v.defs.find(d => d.parent === declaration)) .map(d => d.name) // Identifier nodes .forEach(checkExportedName); diff --git a/eslint/lib/rules/no-restricted-globals.js b/eslint/lib/rules/no-restricted-globals.js index b666238..919a8ee 100644 --- a/eslint/lib/rules/no-restricted-globals.js +++ b/eslint/lib/rules/no-restricted-globals.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Disallow specified global variables", recommended: false, - url: "https://eslint.org/docs/rules/no-restricted-globals" + url: "https://eslint.org/docs/latest/rules/no-restricted-globals" }, schema: { @@ -50,6 +50,8 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + // If no globals are restricted, we don't need to do anything if (context.options.length === 0) { return {}; @@ -99,8 +101,8 @@ module.exports = { } return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); // Report variables declared elsewhere (ex: variables defined as "global" by eslint) scope.variables.forEach(variable => { diff --git a/eslint/lib/rules/no-restricted-imports.js b/eslint/lib/rules/no-restricted-imports.js index f483867..6abfcac 100644 --- a/eslint/lib/rules/no-restricted-imports.js +++ b/eslint/lib/rules/no-restricted-imports.js @@ -98,7 +98,7 @@ module.exports = { docs: { description: "Disallow specified modules when loaded by `import`", recommended: false, - url: "https://eslint.org/docs/rules/no-restricted-imports" + url: "https://eslint.org/docs/latest/rules/no-restricted-imports" }, messages: { @@ -147,7 +147,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const options = Array.isArray(context.options) ? context.options : []; const isPathAndPatternsObject = typeof options[0] === "object" && diff --git a/eslint/lib/rules/no-restricted-modules.js b/eslint/lib/rules/no-restricted-modules.js index c37694f..d79bdbe 100644 --- a/eslint/lib/rules/no-restricted-modules.js +++ b/eslint/lib/rules/no-restricted-modules.js @@ -51,7 +51,7 @@ module.exports = { docs: { description: "Disallow specified modules when loaded by `require`", recommended: false, - url: "https://eslint.org/docs/rules/no-restricted-modules" + url: "https://eslint.org/docs/latest/rules/no-restricted-modules" }, schema: { diff --git a/eslint/lib/rules/no-restricted-properties.js b/eslint/lib/rules/no-restricted-properties.js index 7c03498..b076632 100644 --- a/eslint/lib/rules/no-restricted-properties.js +++ b/eslint/lib/rules/no-restricted-properties.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Disallow certain properties on certain objects", recommended: false, - url: "https://eslint.org/docs/rules/no-restricted-properties" + url: "https://eslint.org/docs/latest/rules/no-restricted-properties" }, schema: { diff --git a/eslint/lib/rules/no-restricted-syntax.js b/eslint/lib/rules/no-restricted-syntax.js index 76369cf..930882c 100644 --- a/eslint/lib/rules/no-restricted-syntax.js +++ b/eslint/lib/rules/no-restricted-syntax.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Disallow specified syntax", recommended: false, - url: "https://eslint.org/docs/rules/no-restricted-syntax" + url: "https://eslint.org/docs/latest/rules/no-restricted-syntax" }, schema: { diff --git a/eslint/lib/rules/no-return-assign.js b/eslint/lib/rules/no-return-assign.js index ccaf2c1..73caf0e 100644 --- a/eslint/lib/rules/no-return-assign.js +++ b/eslint/lib/rules/no-return-assign.js @@ -28,7 +28,7 @@ module.exports = { docs: { description: "Disallow assignment operators in `return` statements", recommended: false, - url: "https://eslint.org/docs/rules/no-return-assign" + url: "https://eslint.org/docs/latest/rules/no-return-assign" }, schema: [ @@ -45,7 +45,7 @@ module.exports = { create(context) { const always = (context.options[0] || "except-parens") !== "except-parens"; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { AssignmentExpression(node) { diff --git a/eslint/lib/rules/no-return-await.js b/eslint/lib/rules/no-return-await.js index 3007c8c..b5abf14 100644 --- a/eslint/lib/rules/no-return-await.js +++ b/eslint/lib/rules/no-return-await.js @@ -13,6 +13,7 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + hasSuggestions: true, type: "suggestion", docs: { @@ -20,7 +21,7 @@ module.exports = { recommended: false, - url: "https://eslint.org/docs/rules/no-return-await" + url: "https://eslint.org/docs/latest/rules/no-return-await" }, fixable: null, @@ -29,6 +30,7 @@ module.exports = { ], messages: { + removeAwait: "Remove redundant `await`.", redundantUseOfAwait: "Redundant use of `await` on a return value." } }, @@ -42,9 +44,34 @@ module.exports = { */ function reportUnnecessaryAwait(node) { context.report({ - node: context.getSourceCode().getFirstToken(node), + node: context.sourceCode.getFirstToken(node), loc: node.loc, - messageId: "redundantUseOfAwait" + messageId: "redundantUseOfAwait", + suggest: [ + { + messageId: "removeAwait", + fix(fixer) { + const sourceCode = context.sourceCode; + const [awaitToken, tokenAfterAwait] = sourceCode.getFirstTokens(node, 2); + + const areAwaitAndAwaitedExpressionOnTheSameLine = awaitToken.loc.start.line === tokenAfterAwait.loc.start.line; + + if (!areAwaitAndAwaitedExpressionOnTheSameLine) { + return null; + } + + const [startOfAwait, endOfAwait] = awaitToken.range; + + const characterAfterAwait = sourceCode.text[endOfAwait]; + const trimLength = characterAfterAwait === " " ? 1 : 0; + + const range = [startOfAwait, endOfAwait + trimLength]; + + return fixer.removeRange(range); + } + } + ] + }); } diff --git a/eslint/lib/rules/no-script-url.js b/eslint/lib/rules/no-script-url.js index 4147900..1d16bde 100644 --- a/eslint/lib/rules/no-script-url.js +++ b/eslint/lib/rules/no-script-url.js @@ -20,7 +20,7 @@ module.exports = { docs: { description: "Disallow `javascript:` urls", recommended: false, - url: "https://eslint.org/docs/rules/no-script-url" + url: "https://eslint.org/docs/latest/rules/no-script-url" }, schema: [], diff --git a/eslint/lib/rules/no-self-assign.js b/eslint/lib/rules/no-self-assign.js index 348ee8d..33ac8fb 100644 --- a/eslint/lib/rules/no-self-assign.js +++ b/eslint/lib/rules/no-self-assign.js @@ -132,7 +132,7 @@ module.exports = { docs: { description: "Disallow assignments where both sides are exactly the same", recommended: true, - url: "https://eslint.org/docs/rules/no-self-assign" + url: "https://eslint.org/docs/latest/rules/no-self-assign" }, schema: [ @@ -154,7 +154,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const [{ props = true } = {}] = context.options; /** diff --git a/eslint/lib/rules/no-self-compare.js b/eslint/lib/rules/no-self-compare.js index dab0db4..3b076eb 100644 --- a/eslint/lib/rules/no-self-compare.js +++ b/eslint/lib/rules/no-self-compare.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Disallow comparisons where both sides are exactly the same", recommended: false, - url: "https://eslint.org/docs/rules/no-self-compare" + url: "https://eslint.org/docs/latest/rules/no-self-compare" }, schema: [], @@ -29,7 +29,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Determines whether two nodes are composed of the same tokens. diff --git a/eslint/lib/rules/no-sequences.js b/eslint/lib/rules/no-sequences.js index 2c0c27c..cd21fc7 100644 --- a/eslint/lib/rules/no-sequences.js +++ b/eslint/lib/rules/no-sequences.js @@ -31,7 +31,7 @@ module.exports = { docs: { description: "Disallow comma operators", recommended: false, - url: "https://eslint.org/docs/rules/no-sequences" + url: "https://eslint.org/docs/latest/rules/no-sequences" }, schema: [{ @@ -51,7 +51,7 @@ module.exports = { create(context) { const options = Object.assign({}, DEFAULT_OPTIONS, context.options[0]); - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Parts of the grammar that are required to have parens. diff --git a/eslint/lib/rules/no-setter-return.js b/eslint/lib/rules/no-setter-return.js index 25e8f14..a5abaaa 100644 --- a/eslint/lib/rules/no-setter-return.js +++ b/eslint/lib/rules/no-setter-return.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const { findVariable } = require("eslint-utils"); +const { findVariable } = require("@eslint-community/eslint-utils"); //------------------------------------------------------------------------------ // Helpers @@ -144,7 +144,7 @@ module.exports = { docs: { description: "Disallow returning values from setters", recommended: true, - url: "https://eslint.org/docs/rules/no-setter-return" + url: "https://eslint.org/docs/latest/rules/no-setter-return" }, schema: [], @@ -156,6 +156,7 @@ module.exports = { create(context) { let funcInfo = null; + const sourceCode = context.sourceCode; /** * Creates and pushes to the stack a function info object for the given function node. @@ -163,7 +164,7 @@ module.exports = { * @returns {void} */ function enterFunction(node) { - const outerScope = getOuterScope(context.getScope()); + const outerScope = getOuterScope(sourceCode.getScope(node)); funcInfo = { upper: funcInfo, diff --git a/eslint/lib/rules/no-shadow-restricted-names.js b/eslint/lib/rules/no-shadow-restricted-names.js index a7d6d00..29560ff 100644 --- a/eslint/lib/rules/no-shadow-restricted-names.js +++ b/eslint/lib/rules/no-shadow-restricted-names.js @@ -29,7 +29,7 @@ module.exports = { docs: { description: "Disallow identifiers from shadowing restricted names", recommended: true, - url: "https://eslint.org/docs/rules/no-shadow-restricted-names" + url: "https://eslint.org/docs/latest/rules/no-shadow-restricted-names" }, schema: [], @@ -43,10 +43,11 @@ module.exports = { const RESTRICTED = new Set(["undefined", "NaN", "Infinity", "arguments", "eval"]); + const sourceCode = context.sourceCode; return { "VariableDeclaration, :function, CatchClause"(node) { - for (const variable of context.getDeclaredVariables(node)) { + for (const variable of sourceCode.getDeclaredVariables(node)) { if (variable.defs.length > 0 && RESTRICTED.has(variable.name) && !safelyShadowsUndefined(variable)) { context.report({ node: variable.defs[0].name, diff --git a/eslint/lib/rules/no-shadow.js b/eslint/lib/rules/no-shadow.js index 3af9354..3e4d998 100644 --- a/eslint/lib/rules/no-shadow.js +++ b/eslint/lib/rules/no-shadow.js @@ -32,7 +32,7 @@ module.exports = { docs: { description: "Disallow variable declarations from shadowing variables declared in the outer scope", recommended: false, - url: "https://eslint.org/docs/rules/no-shadow" + url: "https://eslint.org/docs/latest/rules/no-shadow" }, schema: [ @@ -67,6 +67,7 @@ module.exports = { allow: (context.options[0] && context.options[0].allow) || [], ignoreOnInitialization: context.options[0] && context.options[0].ignoreOnInitialization }; + const sourceCode = context.sourceCode; /** * Checks whether or not a given location is inside of the range of a given node. @@ -318,8 +319,8 @@ module.exports = { } return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); const stack = globalScope.childScopes.slice(); while (stack.length) { diff --git a/eslint/lib/rules/no-spaced-func.js b/eslint/lib/rules/no-spaced-func.js index 97e2da0..d79c184 100644 --- a/eslint/lib/rules/no-spaced-func.js +++ b/eslint/lib/rules/no-spaced-func.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Disallow spacing between function identifiers and their applications (deprecated)", recommended: false, - url: "https://eslint.org/docs/rules/no-spaced-func" + url: "https://eslint.org/docs/latest/rules/no-spaced-func" }, deprecated: true, @@ -35,7 +35,7 @@ module.exports = { create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Check if open space is present in a function name diff --git a/eslint/lib/rules/no-sparse-arrays.js b/eslint/lib/rules/no-sparse-arrays.js index 0e95fe4..c65b0ab 100644 --- a/eslint/lib/rules/no-sparse-arrays.js +++ b/eslint/lib/rules/no-sparse-arrays.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Disallow sparse arrays", recommended: true, - url: "https://eslint.org/docs/rules/no-sparse-arrays" + url: "https://eslint.org/docs/latest/rules/no-sparse-arrays" }, schema: [], diff --git a/eslint/lib/rules/no-sync.js b/eslint/lib/rules/no-sync.js index 71360c6..8f79a36 100644 --- a/eslint/lib/rules/no-sync.js +++ b/eslint/lib/rules/no-sync.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Disallow synchronous methods", recommended: false, - url: "https://eslint.org/docs/rules/no-sync" + url: "https://eslint.org/docs/latest/rules/no-sync" }, schema: [ diff --git a/eslint/lib/rules/no-tabs.js b/eslint/lib/rules/no-tabs.js index 1b4834e..b33690c 100644 --- a/eslint/lib/rules/no-tabs.js +++ b/eslint/lib/rules/no-tabs.js @@ -24,7 +24,7 @@ module.exports = { docs: { description: "Disallow all tabs", recommended: false, - url: "https://eslint.org/docs/rules/no-tabs" + url: "https://eslint.org/docs/latest/rules/no-tabs" }, schema: [{ type: "object", @@ -43,7 +43,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const allowIndentationTabs = context.options && context.options[0] && context.options[0].allowIndentationTabs; return { diff --git a/eslint/lib/rules/no-template-curly-in-string.js b/eslint/lib/rules/no-template-curly-in-string.js index 4f4e9ee..92b4c1c 100644 --- a/eslint/lib/rules/no-template-curly-in-string.js +++ b/eslint/lib/rules/no-template-curly-in-string.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Disallow template literal placeholder syntax in regular strings", recommended: false, - url: "https://eslint.org/docs/rules/no-template-curly-in-string" + url: "https://eslint.org/docs/latest/rules/no-template-curly-in-string" }, schema: [], diff --git a/eslint/lib/rules/no-ternary.js b/eslint/lib/rules/no-ternary.js index a185808..4d43c7e 100644 --- a/eslint/lib/rules/no-ternary.js +++ b/eslint/lib/rules/no-ternary.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow ternary operators", recommended: false, - url: "https://eslint.org/docs/rules/no-ternary" + url: "https://eslint.org/docs/latest/rules/no-ternary" }, schema: [], diff --git a/eslint/lib/rules/no-this-before-super.js b/eslint/lib/rules/no-this-before-super.js index b4e48e8..139bb66 100644 --- a/eslint/lib/rules/no-this-before-super.js +++ b/eslint/lib/rules/no-this-before-super.js @@ -42,7 +42,7 @@ module.exports = { docs: { description: "Disallow `this`/`super` before calling `super()` in constructors", recommended: true, - url: "https://eslint.org/docs/rules/no-this-before-super" + url: "https://eslint.org/docs/latest/rules/no-this-before-super" }, schema: [], diff --git a/eslint/lib/rules/no-throw-literal.js b/eslint/lib/rules/no-throw-literal.js index 3656c83..07a0df6 100644 --- a/eslint/lib/rules/no-throw-literal.js +++ b/eslint/lib/rules/no-throw-literal.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Disallow throwing literals as exceptions", recommended: false, - url: "https://eslint.org/docs/rules/no-throw-literal" + url: "https://eslint.org/docs/latest/rules/no-throw-literal" }, schema: [], diff --git a/eslint/lib/rules/no-trailing-spaces.js b/eslint/lib/rules/no-trailing-spaces.js index a02a880..1674de5 100644 --- a/eslint/lib/rules/no-trailing-spaces.js +++ b/eslint/lib/rules/no-trailing-spaces.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Disallow trailing whitespace at the end of lines", recommended: false, - url: "https://eslint.org/docs/rules/no-trailing-spaces" + url: "https://eslint.org/docs/latest/rules/no-trailing-spaces" }, fixable: "whitespace", @@ -50,7 +50,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u3000]", SKIP_BLANK = `^${BLANK_CLASS}*$`, diff --git a/eslint/lib/rules/no-undef-init.js b/eslint/lib/rules/no-undef-init.js index 2cb1c3f..be19d6f 100644 --- a/eslint/lib/rules/no-undef-init.js +++ b/eslint/lib/rules/no-undef-init.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Disallow initializing variables to `undefined`", recommended: false, - url: "https://eslint.org/docs/rules/no-undef-init" + url: "https://eslint.org/docs/latest/rules/no-undef-init" }, schema: [], @@ -32,14 +32,14 @@ module.exports = { create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { VariableDeclarator(node) { const name = sourceCode.getText(node.id), init = node.init && node.init.name, - scope = context.getScope(), + scope = sourceCode.getScope(node), undefinedVar = astUtils.getVariableByName(scope, "undefined"), shadowed = undefinedVar && undefinedVar.defs.length > 0, lastToken = sourceCode.getLastToken(node); diff --git a/eslint/lib/rules/no-undef.js b/eslint/lib/rules/no-undef.js index e920ce6..fe47028 100644 --- a/eslint/lib/rules/no-undef.js +++ b/eslint/lib/rules/no-undef.js @@ -31,7 +31,7 @@ module.exports = { docs: { description: "Disallow the use of undeclared variables unless mentioned in `/*global */` comments", recommended: true, - url: "https://eslint.org/docs/rules/no-undef" + url: "https://eslint.org/docs/latest/rules/no-undef" }, schema: [ @@ -54,10 +54,11 @@ module.exports = { create(context) { const options = context.options[0]; const considerTypeOf = options && options.typeof === true || false; + const sourceCode = context.sourceCode; return { - "Program:exit"(/* node */) { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); globalScope.through.forEach(ref => { const identifier = ref.identifier; diff --git a/eslint/lib/rules/no-undefined.js b/eslint/lib/rules/no-undefined.js index e006320..8f47ca1 100644 --- a/eslint/lib/rules/no-undefined.js +++ b/eslint/lib/rules/no-undefined.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Disallow the use of `undefined` as an identifier", recommended: false, - url: "https://eslint.org/docs/rules/no-undefined" + url: "https://eslint.org/docs/latest/rules/no-undefined" }, schema: [], @@ -28,6 +28,8 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + /** * Report an invalid "undefined" identifier node. * @param {ASTNode} node The node to report. @@ -66,8 +68,8 @@ module.exports = { } return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); const stack = [globalScope]; diff --git a/eslint/lib/rules/no-underscore-dangle.js b/eslint/lib/rules/no-underscore-dangle.js index eb3e404..a0e05c6 100644 --- a/eslint/lib/rules/no-underscore-dangle.js +++ b/eslint/lib/rules/no-underscore-dangle.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow dangling underscores in identifiers", recommended: false, - url: "https://eslint.org/docs/rules/no-underscore-dangle" + url: "https://eslint.org/docs/latest/rules/no-underscore-dangle" }, schema: [ @@ -53,6 +53,14 @@ module.exports = { enforceInClassFields: { type: "boolean", default: false + }, + allowInArrayDestructuring: { + type: "boolean", + default: true + }, + allowInObjectDestructuring: { + type: "boolean", + default: true } }, additionalProperties: false @@ -74,6 +82,9 @@ module.exports = { const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false; const enforceInClassFields = typeof options.enforceInClassFields !== "undefined" ? options.enforceInClassFields : false; const allowFunctionParams = typeof options.allowFunctionParams !== "undefined" ? options.allowFunctionParams : true; + const allowInArrayDestructuring = typeof options.allowInArrayDestructuring !== "undefined" ? options.allowInArrayDestructuring : true; + const allowInObjectDestructuring = typeof options.allowInObjectDestructuring !== "undefined" ? options.allowInObjectDestructuring : true; + const sourceCode = context.sourceCode; //------------------------------------------------------------------------- // Helpers @@ -195,6 +206,7 @@ module.exports = { checkForDanglingUnderscoreInFunctionParameters(node); } + /** * Check if variable expression has a dangling underscore * @param {ASTNode} node node to evaluate @@ -202,18 +214,32 @@ module.exports = { * @private */ function checkForDanglingUnderscoreInVariableExpression(node) { - const identifier = node.id.name; + sourceCode.getDeclaredVariables(node).forEach(variable => { + const definition = variable.defs.find(def => def.node === node); + const identifierNode = definition.name; + const identifier = identifierNode.name; + let parent = identifierNode.parent; - if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && - !isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) { - context.report({ - node, - messageId: "unexpectedUnderscore", - data: { - identifier - } - }); - } + while (!["VariableDeclarator", "ArrayPattern", "ObjectPattern"].includes(parent.type)) { + parent = parent.parent; + } + + if ( + hasDanglingUnderscore(identifier) && + !isSpecialCaseIdentifierInVariableExpression(identifier) && + !isAllowed(identifier) && + !(allowInArrayDestructuring && parent.type === "ArrayPattern") && + !(allowInObjectDestructuring && parent.type === "ObjectPattern") + ) { + context.report({ + node, + messageId: "unexpectedUnderscore", + data: { + identifier + } + }); + } + }); } /** diff --git a/eslint/lib/rules/no-unexpected-multiline.js b/eslint/lib/rules/no-unexpected-multiline.js index 2ca6731..810c08b 100644 --- a/eslint/lib/rules/no-unexpected-multiline.js +++ b/eslint/lib/rules/no-unexpected-multiline.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Disallow confusing multiline expressions", recommended: true, - url: "https://eslint.org/docs/rules/no-unexpected-multiline" + url: "https://eslint.org/docs/latest/rules/no-unexpected-multiline" }, schema: [], @@ -38,7 +38,7 @@ module.exports = { const REGEX_FLAG_MATCHER = /^[gimsuy]+$/u; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Check to see if there is a newline between the node and the following open bracket diff --git a/eslint/lib/rules/no-unmodified-loop-condition.js b/eslint/lib/rules/no-unmodified-loop-condition.js index 12f61e9..768a155 100644 --- a/eslint/lib/rules/no-unmodified-loop-condition.js +++ b/eslint/lib/rules/no-unmodified-loop-condition.js @@ -164,7 +164,7 @@ module.exports = { docs: { description: "Disallow unmodified loop conditions", recommended: false, - url: "https://eslint.org/docs/rules/no-unmodified-loop-condition" + url: "https://eslint.org/docs/latest/rules/no-unmodified-loop-condition" }, schema: [], @@ -175,7 +175,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; let groupMap = null; /** @@ -340,8 +340,8 @@ module.exports = { } return { - "Program:exit"() { - const queue = [context.getScope()]; + "Program:exit"(node) { + const queue = [sourceCode.getScope(node)]; groupMap = new Map(); diff --git a/eslint/lib/rules/no-unneeded-ternary.js b/eslint/lib/rules/no-unneeded-ternary.js index c193282..ab1bdc5 100644 --- a/eslint/lib/rules/no-unneeded-ternary.js +++ b/eslint/lib/rules/no-unneeded-ternary.js @@ -31,7 +31,7 @@ module.exports = { docs: { description: "Disallow ternary operators when simpler alternatives exist", recommended: false, - url: "https://eslint.org/docs/rules/no-unneeded-ternary" + url: "https://eslint.org/docs/latest/rules/no-unneeded-ternary" }, schema: [ @@ -58,7 +58,7 @@ module.exports = { create(context) { const options = context.options[0] || {}; const defaultAssignment = options.defaultAssignment !== false; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Test if the node is a boolean literal @@ -144,7 +144,7 @@ module.exports = { context.report({ node, messageId: "unnecessaryConditionalAssignment", - fix: fixer => { + fix(fixer) { const shouldParenthesizeAlternate = ( astUtils.getPrecedence(node.alternate) < OR_PRECEDENCE || diff --git a/eslint/lib/rules/no-unreachable-loop.js b/eslint/lib/rules/no-unreachable-loop.js index c42c922..1df764e 100644 --- a/eslint/lib/rules/no-unreachable-loop.js +++ b/eslint/lib/rules/no-unreachable-loop.js @@ -61,7 +61,7 @@ module.exports = { docs: { description: "Disallow loops with a body that allows only one iteration", recommended: false, - url: "https://eslint.org/docs/rules/no-unreachable-loop" + url: "https://eslint.org/docs/latest/rules/no-unreachable-loop" }, schema: [{ diff --git a/eslint/lib/rules/no-unreachable.js b/eslint/lib/rules/no-unreachable.js index dea8681..6216a73 100644 --- a/eslint/lib/rules/no-unreachable.js +++ b/eslint/lib/rules/no-unreachable.js @@ -113,7 +113,7 @@ module.exports = { docs: { description: "Disallow unreachable code after `return`, `throw`, `continue`, and `break` statements", recommended: true, - url: "https://eslint.org/docs/rules/no-unreachable" + url: "https://eslint.org/docs/latest/rules/no-unreachable" }, schema: [], @@ -130,7 +130,7 @@ module.exports = { let constructorInfo = null; /** @type {ConsecutiveRange} */ - const range = new ConsecutiveRange(context.getSourceCode()); + const range = new ConsecutiveRange(context.sourceCode); /** * Reports a given node if it's unreachable. diff --git a/eslint/lib/rules/no-unsafe-finally.js b/eslint/lib/rules/no-unsafe-finally.js index 80adb0f..ebd2432 100644 --- a/eslint/lib/rules/no-unsafe-finally.js +++ b/eslint/lib/rules/no-unsafe-finally.js @@ -26,7 +26,7 @@ module.exports = { docs: { description: "Disallow control flow statements in `finally` blocks", recommended: true, - url: "https://eslint.org/docs/rules/no-unsafe-finally" + url: "https://eslint.org/docs/latest/rules/no-unsafe-finally" }, schema: [], diff --git a/eslint/lib/rules/no-unsafe-negation.js b/eslint/lib/rules/no-unsafe-negation.js index 5dd150f..cabd7e2 100644 --- a/eslint/lib/rules/no-unsafe-negation.js +++ b/eslint/lib/rules/no-unsafe-negation.js @@ -54,7 +54,7 @@ module.exports = { docs: { description: "Disallow negating the left operand of relational operators", recommended: true, - url: "https://eslint.org/docs/rules/no-unsafe-negation" + url: "https://eslint.org/docs/latest/rules/no-unsafe-negation" }, hasSuggestions: true, @@ -82,7 +82,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const options = context.options[0] || {}; const enforceForOrderingRelations = options.enforceForOrderingRelations === true; diff --git a/eslint/lib/rules/no-unsafe-optional-chaining.js b/eslint/lib/rules/no-unsafe-optional-chaining.js index 9913907..fe2bead 100644 --- a/eslint/lib/rules/no-unsafe-optional-chaining.js +++ b/eslint/lib/rules/no-unsafe-optional-chaining.js @@ -26,7 +26,7 @@ module.exports = { docs: { description: "Disallow use of optional chaining in contexts where the `undefined` value is not allowed", recommended: true, - url: "https://eslint.org/docs/rules/no-unsafe-optional-chaining" + url: "https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining" }, schema: [{ type: "object", diff --git a/eslint/lib/rules/no-unused-expressions.js b/eslint/lib/rules/no-unused-expressions.js index d34d584..8337c19 100644 --- a/eslint/lib/rules/no-unused-expressions.js +++ b/eslint/lib/rules/no-unused-expressions.js @@ -32,7 +32,7 @@ module.exports = { docs: { description: "Disallow unused expressions", recommended: false, - url: "https://eslint.org/docs/rules/no-unused-expressions" + url: "https://eslint.org/docs/latest/rules/no-unused-expressions" }, schema: [ @@ -109,12 +109,11 @@ module.exports = { /** * 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 */ - function isDirective(node, ancestors) { - const parent = ancestors[ancestors.length - 1], - grandparent = ancestors[ancestors.length - 2]; + function isDirective(node) { + const parent = node.parent, + grandparent = parent.parent; /** * https://tc39.es/ecma262/#directive-prologue @@ -180,7 +179,7 @@ module.exports = { return { ExpressionStatement(node) { - if (Checker.isDisallowed(node.expression) && !isDirective(node, context.getAncestors())) { + if (Checker.isDisallowed(node.expression) && !isDirective(node)) { context.report({ node, messageId: "unusedExpression" }); } } diff --git a/eslint/lib/rules/no-unused-labels.js b/eslint/lib/rules/no-unused-labels.js index 305226a..9aa1067 100644 --- a/eslint/lib/rules/no-unused-labels.js +++ b/eslint/lib/rules/no-unused-labels.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow unused labels", recommended: true, - url: "https://eslint.org/docs/rules/no-unused-labels" + url: "https://eslint.org/docs/latest/rules/no-unused-labels" }, schema: [], @@ -30,7 +30,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; let scopeInfo = null; /** diff --git a/eslint/lib/rules/no-unused-private-class-members.js b/eslint/lib/rules/no-unused-private-class-members.js index e62a9ed..037be7d 100644 --- a/eslint/lib/rules/no-unused-private-class-members.js +++ b/eslint/lib/rules/no-unused-private-class-members.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow unused private class members", recommended: false, - url: "https://eslint.org/docs/rules/no-unused-private-class-members" + url: "https://eslint.org/docs/latest/rules/no-unused-private-class-members" }, schema: [], diff --git a/eslint/lib/rules/no-unused-vars.js b/eslint/lib/rules/no-unused-vars.js index 778889a..be8af43 100644 --- a/eslint/lib/rules/no-unused-vars.js +++ b/eslint/lib/rules/no-unused-vars.js @@ -35,7 +35,7 @@ module.exports = { docs: { description: "Disallow unused variables", recommended: true, - url: "https://eslint.org/docs/rules/no-unused-vars" + url: "https://eslint.org/docs/latest/rules/no-unused-vars" }, schema: [ @@ -84,7 +84,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const REST_PROPERTY_TYPE = /^(?:RestElement|(?:Experimental)?RestProperty)$/u; @@ -555,7 +555,7 @@ module.exports = { */ function isAfterLastUsedArg(variable) { const def = variable.defs[0]; - const params = context.getDeclaredVariables(def.node); + const params = sourceCode.getDeclaredVariables(def.node); const posteriorParams = params.slice(params.indexOf(variable) + 1); // If any used parameters occur after this parameter, do not report. @@ -673,7 +673,7 @@ module.exports = { return { "Program:exit"(programNode) { - const unusedVars = collectUnusedVariables(context.getScope(), []); + const unusedVars = collectUnusedVariables(sourceCode.getScope(programNode), []); for (let i = 0, l = unusedVars.length; i < l; ++i) { const unusedVar = unusedVars[i]; diff --git a/eslint/lib/rules/no-use-before-define.js b/eslint/lib/rules/no-use-before-define.js index 592c083..9d6b043 100644 --- a/eslint/lib/rules/no-use-before-define.js +++ b/eslint/lib/rules/no-use-before-define.js @@ -68,7 +68,7 @@ function isInClassStaticInitializerRange(node, location) { } /** - * Checks whether a given scope is the scope of a a class static initializer. + * Checks whether a given scope is the scope of 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. @@ -228,7 +228,7 @@ module.exports = { docs: { description: "Disallow the use of variables before they are defined", recommended: false, - url: "https://eslint.org/docs/rules/no-use-before-define" + url: "https://eslint.org/docs/latest/rules/no-use-before-define" }, schema: [ @@ -258,6 +258,7 @@ module.exports = { create(context) { const options = parseOptions(context.options[0]); + const sourceCode = context.sourceCode; /** * Determines whether a given reference should be checked. @@ -339,8 +340,8 @@ module.exports = { } return { - Program() { - checkReferencesInScope(context.getScope()); + Program(node) { + checkReferencesInScope(sourceCode.getScope(node)); } }; } diff --git a/eslint/lib/rules/no-useless-backreference.js b/eslint/lib/rules/no-useless-backreference.js index f23535b..c99ac41 100644 --- a/eslint/lib/rules/no-useless-backreference.js +++ b/eslint/lib/rules/no-useless-backreference.js @@ -9,8 +9,8 @@ // Requirements //------------------------------------------------------------------------------ -const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils"); -const { RegExpParser, visitRegExpAST } = require("regexpp"); +const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils"); +const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); //------------------------------------------------------------------------------ // Helpers @@ -66,7 +66,7 @@ module.exports = { docs: { description: "Disallow useless backreferences in regular expressions", recommended: true, - url: "https://eslint.org/docs/rules/no-useless-backreference" + url: "https://eslint.org/docs/latest/rules/no-useless-backreference" }, schema: [], @@ -82,6 +82,8 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + /** * Checks and reports useless backreferences in the given regular expression. * @param {ASTNode} node Node that represents regular expression. A regex literal or RegExp constructor call. @@ -167,8 +169,8 @@ module.exports = { checkRegex(node, pattern, flags); }, - Program() { - const scope = context.getScope(), + Program(node) { + const scope = sourceCode.getScope(node), tracker = new ReferenceTracker(scope), traceMap = { RegExp: { @@ -177,13 +179,13 @@ module.exports = { } }; - for (const { node } of tracker.iterateGlobalReferences(traceMap)) { - const [patternNode, flagsNode] = node.arguments, + for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { + const [patternNode, flagsNode] = refNode.arguments, pattern = getStringIfConstant(patternNode, scope), flags = getStringIfConstant(flagsNode, scope); if (typeof pattern === "string") { - checkRegex(node, pattern, flags || ""); + checkRegex(refNode, pattern, flags || ""); } } } diff --git a/eslint/lib/rules/no-useless-call.js b/eslint/lib/rules/no-useless-call.js index 2d3ae4e..dea2b47 100644 --- a/eslint/lib/rules/no-useless-call.js +++ b/eslint/lib/rules/no-useless-call.js @@ -57,7 +57,7 @@ module.exports = { docs: { description: "Disallow unnecessary calls to `.call()` and `.apply()`", recommended: false, - url: "https://eslint.org/docs/rules/no-useless-call" + url: "https://eslint.org/docs/latest/rules/no-useless-call" }, schema: [], @@ -68,7 +68,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { CallExpression(node) { diff --git a/eslint/lib/rules/no-useless-catch.js b/eslint/lib/rules/no-useless-catch.js index 36c356e..e02013d 100644 --- a/eslint/lib/rules/no-useless-catch.js +++ b/eslint/lib/rules/no-useless-catch.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow unnecessary `catch` clauses", recommended: true, - url: "https://eslint.org/docs/rules/no-useless-catch" + url: "https://eslint.org/docs/latest/rules/no-useless-catch" }, schema: [], diff --git a/eslint/lib/rules/no-useless-computed-key.js b/eslint/lib/rules/no-useless-computed-key.js index f7f1217..f2d9f33 100644 --- a/eslint/lib/rules/no-useless-computed-key.js +++ b/eslint/lib/rules/no-useless-computed-key.js @@ -93,7 +93,7 @@ module.exports = { docs: { description: "Disallow unnecessary computed property keys in objects and classes", recommended: false, - url: "https://eslint.org/docs/rules/no-useless-computed-key" + url: "https://eslint.org/docs/latest/rules/no-useless-computed-key" }, schema: [{ @@ -113,7 +113,7 @@ module.exports = { } }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const enforceForClassMembers = context.options[0] && context.options[0].enforceForClassMembers; /** diff --git a/eslint/lib/rules/no-useless-concat.js b/eslint/lib/rules/no-useless-concat.js index 26c5206..c566c62 100644 --- a/eslint/lib/rules/no-useless-concat.js +++ b/eslint/lib/rules/no-useless-concat.js @@ -72,7 +72,7 @@ module.exports = { docs: { description: "Disallow unnecessary concatenation of literals or template literals", recommended: false, - url: "https://eslint.org/docs/rules/no-useless-concat" + url: "https://eslint.org/docs/latest/rules/no-useless-concat" }, schema: [], @@ -83,7 +83,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { BinaryExpression(node) { diff --git a/eslint/lib/rules/no-useless-constructor.js b/eslint/lib/rules/no-useless-constructor.js index 38c3bc3..2b9c18e 100644 --- a/eslint/lib/rules/no-useless-constructor.js +++ b/eslint/lib/rules/no-useless-constructor.js @@ -140,7 +140,7 @@ module.exports = { docs: { description: "Disallow unnecessary constructors", recommended: false, - url: "https://eslint.org/docs/rules/no-useless-constructor" + url: "https://eslint.org/docs/latest/rules/no-useless-constructor" }, schema: [], diff --git a/eslint/lib/rules/no-useless-escape.js b/eslint/lib/rules/no-useless-escape.js index 2046a14..8304d91 100644 --- a/eslint/lib/rules/no-useless-escape.js +++ b/eslint/lib/rules/no-useless-escape.js @@ -86,7 +86,7 @@ module.exports = { docs: { description: "Disallow unnecessary escape characters", recommended: true, - url: "https://eslint.org/docs/rules/no-useless-escape" + url: "https://eslint.org/docs/latest/rules/no-useless-escape" }, hasSuggestions: true, @@ -101,7 +101,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Reports a node diff --git a/eslint/lib/rules/no-useless-rename.js b/eslint/lib/rules/no-useless-rename.js index 908605f..0c818fb 100644 --- a/eslint/lib/rules/no-useless-rename.js +++ b/eslint/lib/rules/no-useless-rename.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Disallow renaming import, export, and destructured assignments to the same name", recommended: false, - url: "https://eslint.org/docs/rules/no-useless-rename" + url: "https://eslint.org/docs/latest/rules/no-useless-rename" }, fixable: "code", @@ -46,7 +46,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(), + const sourceCode = context.sourceCode, options = context.options[0] || {}, ignoreDestructuring = options.ignoreDestructuring === true, ignoreImport = options.ignoreImport === true, diff --git a/eslint/lib/rules/no-useless-return.js b/eslint/lib/rules/no-useless-return.js index be8d4df..db1ccae 100644 --- a/eslint/lib/rules/no-useless-return.js +++ b/eslint/lib/rules/no-useless-return.js @@ -69,7 +69,7 @@ module.exports = { docs: { description: "Disallow redundant return statements", recommended: false, - url: "https://eslint.org/docs/rules/no-useless-return" + url: "https://eslint.org/docs/latest/rules/no-useless-return" }, fixable: "code", @@ -83,7 +83,7 @@ module.exports = { create(context) { const segmentInfoMap = new WeakMap(); const usedUnreachableSegments = new WeakSet(); - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; let scopeInfo = null; /** @@ -197,7 +197,7 @@ module.exports = { return { - // Makes and pushs a new scope information. + // Makes and pushes a new scope information. onCodePathStart(codePath) { scopeInfo = { upper: scopeInfo, diff --git a/eslint/lib/rules/no-var.js b/eslint/lib/rules/no-var.js index cfb64ef..d45a91a 100644 --- a/eslint/lib/rules/no-var.js +++ b/eslint/lib/rules/no-var.js @@ -159,7 +159,7 @@ function hasReferenceInTDZ(node) { return !reference.init && ( start < idStart || (defaultValue !== null && start >= defaultStart && end <= defaultEnd) || - (start >= initStart && end <= initEnd) + (!astUtils.isFunction(node) && start >= initStart && end <= initEnd) ); }); }; @@ -187,7 +187,7 @@ module.exports = { docs: { description: "Require `let` or `const` instead of `var`", recommended: false, - url: "https://eslint.org/docs/rules/no-var" + url: "https://eslint.org/docs/latest/rules/no-var" }, schema: [], @@ -199,7 +199,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Checks whether the variables which are defined by the given declarator node have their references in TDZ. @@ -210,7 +210,7 @@ module.exports = { if (!declarator.init) { return false; } - const variables = context.getDeclaredVariables(declarator); + const variables = sourceCode.getDeclaredVariables(declarator); return variables.some(hasReferenceInTDZ(declarator.init)); } @@ -268,7 +268,7 @@ module.exports = { * @returns {boolean} `true` if it can fix the node. */ function canFix(node) { - const variables = context.getDeclaredVariables(node); + const variables = sourceCode.getDeclaredVariables(node); const scopeNode = getScopeNode(node); if (node.parent.type === "SwitchCase" || diff --git a/eslint/lib/rules/no-void.js b/eslint/lib/rules/no-void.js index 15c4730..9546d7a 100644 --- a/eslint/lib/rules/no-void.js +++ b/eslint/lib/rules/no-void.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Disallow `void` operators", recommended: false, - url: "https://eslint.org/docs/rules/no-void" + url: "https://eslint.org/docs/latest/rules/no-void" }, messages: { diff --git a/eslint/lib/rules/no-warning-comments.js b/eslint/lib/rules/no-warning-comments.js index 9754f50..c415bee 100644 --- a/eslint/lib/rules/no-warning-comments.js +++ b/eslint/lib/rules/no-warning-comments.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Disallow specified warning terms in comments", recommended: false, - url: "https://eslint.org/docs/rules/no-warning-comments" + url: "https://eslint.org/docs/latest/rules/no-warning-comments" }, schema: [ @@ -58,7 +58,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(), + const sourceCode = context.sourceCode, configuration = context.options[0] || {}, warningTerms = configuration.terms || ["todo", "fixme", "xxx"], location = configuration.location || "start", diff --git a/eslint/lib/rules/no-whitespace-before-property.js b/eslint/lib/rules/no-whitespace-before-property.js index 6732381..1153314 100644 --- a/eslint/lib/rules/no-whitespace-before-property.js +++ b/eslint/lib/rules/no-whitespace-before-property.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Disallow whitespace before properties", recommended: false, - url: "https://eslint.org/docs/rules/no-whitespace-before-property" + url: "https://eslint.org/docs/latest/rules/no-whitespace-before-property" }, fixable: "whitespace", @@ -34,7 +34,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers diff --git a/eslint/lib/rules/no-with.js b/eslint/lib/rules/no-with.js index 33de68d..0fb2c45 100644 --- a/eslint/lib/rules/no-with.js +++ b/eslint/lib/rules/no-with.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Disallow `with` statements", recommended: true, - url: "https://eslint.org/docs/rules/no-with" + url: "https://eslint.org/docs/latest/rules/no-with" }, schema: [], diff --git a/eslint/lib/rules/nonblock-statement-body-position.js b/eslint/lib/rules/nonblock-statement-body-position.js index cefecf3..1ea2770 100644 --- a/eslint/lib/rules/nonblock-statement-body-position.js +++ b/eslint/lib/rules/nonblock-statement-body-position.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Enforce the location of single-line statements", recommended: false, - url: "https://eslint.org/docs/rules/nonblock-statement-body-position" + url: "https://eslint.org/docs/latest/rules/nonblock-statement-body-position" }, fixable: "whitespace", @@ -49,7 +49,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //---------------------------------------------------------------------- // Helpers diff --git a/eslint/lib/rules/object-curly-newline.js b/eslint/lib/rules/object-curly-newline.js index 2f80049..caf1982 100644 --- a/eslint/lib/rules/object-curly-newline.js +++ b/eslint/lib/rules/object-curly-newline.js @@ -152,7 +152,7 @@ module.exports = { docs: { description: "Enforce consistent line breaks after opening and before closing braces", recommended: false, - url: "https://eslint.org/docs/rules/object-curly-newline" + url: "https://eslint.org/docs/latest/rules/object-curly-newline" }, fixable: "whitespace", @@ -185,7 +185,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const normalizedOptions = normalizeOptions(context.options[0]); /** diff --git a/eslint/lib/rules/object-curly-spacing.js b/eslint/lib/rules/object-curly-spacing.js index d6a8e59..41ca428 100644 --- a/eslint/lib/rules/object-curly-spacing.js +++ b/eslint/lib/rules/object-curly-spacing.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Enforce consistent spacing inside braces", recommended: false, - url: "https://eslint.org/docs/rules/object-curly-spacing" + url: "https://eslint.org/docs/latest/rules/object-curly-spacing" }, fixable: "whitespace", @@ -51,7 +51,7 @@ module.exports = { create(context) { const spaced = context.options[0] === "always", - sourceCode = context.getSourceCode(); + sourceCode = context.sourceCode; /** * Determines whether an option is set, relative to the spacing option. @@ -81,7 +81,7 @@ module.exports = { * @returns {void} */ function reportNoBeginningSpace(node, token) { - const nextToken = context.getSourceCode().getTokenAfter(token, { includeComments: true }); + const nextToken = context.sourceCode.getTokenAfter(token, { includeComments: true }); context.report({ node, @@ -103,7 +103,7 @@ module.exports = { * @returns {void} */ function reportNoEndingSpace(node, token) { - const previousToken = context.getSourceCode().getTokenBefore(token, { includeComments: true }); + const previousToken = context.sourceCode.getTokenBefore(token, { includeComments: true }); context.report({ node, diff --git a/eslint/lib/rules/object-property-newline.js b/eslint/lib/rules/object-property-newline.js index bc079a1..deca9b5 100644 --- a/eslint/lib/rules/object-property-newline.js +++ b/eslint/lib/rules/object-property-newline.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Enforce placing object properties on separate lines", recommended: false, - url: "https://eslint.org/docs/rules/object-property-newline" + url: "https://eslint.org/docs/latest/rules/object-property-newline" }, schema: [ @@ -53,7 +53,7 @@ module.exports = { ? "propertiesOnNewlineAll" : "propertiesOnNewline"; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { ObjectExpression(node) { diff --git a/eslint/lib/rules/object-shorthand.js b/eslint/lib/rules/object-shorthand.js index b755aea..e4cb3a4 100644 --- a/eslint/lib/rules/object-shorthand.js +++ b/eslint/lib/rules/object-shorthand.js @@ -30,7 +30,7 @@ module.exports = { docs: { description: "Require or disallow method and property shorthand syntax for object literals", recommended: false, - url: "https://eslint.org/docs/rules/object-shorthand" + url: "https://eslint.org/docs/latest/rules/object-shorthand" }, fixable: "code", @@ -123,7 +123,7 @@ module.exports = { : null; const AVOID_QUOTES = PARAMS.avoidQuotes; const AVOID_EXPLICIT_RETURN_ARROWS = !!PARAMS.avoidExplicitReturnArrows; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers @@ -354,11 +354,12 @@ module.exports = { /** * Enters a function. This creates a new lexical identifier scope, so a new Set of arrow functions is pushed onto the stack. * Also, this marks all `arguments` identifiers so that they can be detected later. + * @param {ASTNode} node The node representing the function. * @returns {void} */ - function enterFunction() { + function enterFunction(node) { lexicalScopeStack.unshift(new Set()); - context.getScope().variables.filter(variable => variable.name === "arguments").forEach(variable => { + sourceCode.getScope(node).variables.filter(variable => variable.name === "arguments").forEach(variable => { variable.references.map(ref => ref.identifier).forEach(identifier => argumentsIdentifiers.add(identifier)); }); } diff --git a/eslint/lib/rules/one-var-declaration-per-line.js b/eslint/lib/rules/one-var-declaration-per-line.js index 65be092..b1e045b 100644 --- a/eslint/lib/rules/one-var-declaration-per-line.js +++ b/eslint/lib/rules/one-var-declaration-per-line.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Require or disallow newlines around variable declarations", recommended: false, - url: "https://eslint.org/docs/rules/one-var-declaration-per-line" + url: "https://eslint.org/docs/latest/rules/one-var-declaration-per-line" }, schema: [ diff --git a/eslint/lib/rules/one-var.js b/eslint/lib/rules/one-var.js index a8e2a1d..abb1525 100644 --- a/eslint/lib/rules/one-var.js +++ b/eslint/lib/rules/one-var.js @@ -36,7 +36,7 @@ module.exports = { docs: { description: "Enforce variables to be declared either together or separately in functions", recommended: false, - url: "https://eslint.org/docs/rules/one-var" + url: "https://eslint.org/docs/latest/rules/one-var" }, fixable: "code", @@ -121,7 +121,7 @@ module.exports = { } } - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers diff --git a/eslint/lib/rules/operator-assignment.js b/eslint/lib/rules/operator-assignment.js index ed9cb96..f71d73b 100644 --- a/eslint/lib/rules/operator-assignment.js +++ b/eslint/lib/rules/operator-assignment.js @@ -65,7 +65,7 @@ module.exports = { docs: { description: "Require or disallow assignment operator shorthand where possible", recommended: false, - url: "https://eslint.org/docs/rules/operator-assignment" + url: "https://eslint.org/docs/latest/rules/operator-assignment" }, schema: [ @@ -83,7 +83,7 @@ module.exports = { create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Returns the operator token of an AssignmentExpression or BinaryExpression diff --git a/eslint/lib/rules/operator-linebreak.js b/eslint/lib/rules/operator-linebreak.js index 03b603e..2b609f6 100644 --- a/eslint/lib/rules/operator-linebreak.js +++ b/eslint/lib/rules/operator-linebreak.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Enforce consistent linebreak style for operators", recommended: false, - url: "https://eslint.org/docs/rules/operator-linebreak" + url: "https://eslint.org/docs/latest/rules/operator-linebreak" }, schema: [ @@ -69,7 +69,7 @@ module.exports = { styleOverrides[":"] = "before"; } - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers diff --git a/eslint/lib/rules/padded-blocks.js b/eslint/lib/rules/padded-blocks.js index bc19428..c6d1372 100644 --- a/eslint/lib/rules/padded-blocks.js +++ b/eslint/lib/rules/padded-blocks.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Require or disallow padding within blocks", recommended: false, - url: "https://eslint.org/docs/rules/padded-blocks" + url: "https://eslint.org/docs/latest/rules/padded-blocks" }, fixable: "whitespace", @@ -96,7 +96,7 @@ module.exports = { options.allowSingleLineBlocks = exceptOptions.allowSingleLineBlocks === true; } - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Gets the open brace token from a given node. diff --git a/eslint/lib/rules/padding-line-between-statements.js b/eslint/lib/rules/padding-line-between-statements.js index 9d730bf..be7e2e4 100644 --- a/eslint/lib/rules/padding-line-between-statements.js +++ b/eslint/lib/rules/padding-line-between-statements.js @@ -253,7 +253,7 @@ function verifyForNever(context, _, nextNode, paddingLines) { const nextToken = paddingLines[0][1]; const start = prevToken.range[1]; const end = nextToken.range[0]; - const text = context.getSourceCode().text + const text = context.sourceCode.text .slice(start, end) .replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines); @@ -284,7 +284,7 @@ function verifyForAlways(context, prevNode, nextNode, paddingLines) { node: nextNode, messageId: "expectedBlankLine", fix(fixer) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; let prevToken = getActualLastToken(sourceCode, prevNode); const nextToken = sourceCode.getFirstTokenBetween( prevToken, @@ -433,7 +433,7 @@ module.exports = { docs: { description: "Require or disallow padding lines between statements", recommended: false, - url: "https://eslint.org/docs/rules/padding-line-between-statements" + url: "https://eslint.org/docs/latest/rules/padding-line-between-statements" }, fixable: "whitespace", @@ -475,7 +475,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const configureList = context.options || []; let scopeInfo = null; diff --git a/eslint/lib/rules/prefer-arrow-callback.js b/eslint/lib/rules/prefer-arrow-callback.js index 8af7161..d22e508 100644 --- a/eslint/lib/rules/prefer-arrow-callback.js +++ b/eslint/lib/rules/prefer-arrow-callback.js @@ -153,7 +153,7 @@ module.exports = { docs: { description: "Require using arrow functions for callbacks", recommended: false, - url: "https://eslint.org/docs/rules/prefer-arrow-callback" + url: "https://eslint.org/docs/latest/rules/prefer-arrow-callback" }, schema: [ @@ -185,7 +185,7 @@ module.exports = { const allowUnboundThis = options.allowUnboundThis !== false; // default to true const allowNamedFunctions = options.allowNamedFunctions; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /* * {Array<{this: boolean, super: boolean, meta: boolean}>} @@ -263,14 +263,14 @@ module.exports = { } // Skip recursive functions. - const nameVar = context.getDeclaredVariables(node)[0]; + const nameVar = sourceCode.getDeclaredVariables(node)[0]; if (isFunctionName(nameVar) && nameVar.references.length > 0) { return; } // Skip if it's using arguments. - const variable = getVariableOfArguments(context.getScope()); + const variable = getVariableOfArguments(sourceCode.getScope(node)); if (variable && variable.references.length > 0) { return; @@ -335,6 +335,7 @@ module.exports = { // Convert the function expression to an arrow function. const functionToken = sourceCode.getFirstToken(node, node.async ? 1 : 0); const leftParenToken = sourceCode.getTokenAfter(functionToken, astUtils.isOpeningParenToken); + const tokenBeforeBody = sourceCode.getTokenBefore(node.body); if (sourceCode.commentsExistBetween(functionToken, leftParenToken)) { @@ -348,7 +349,7 @@ module.exports = { // Remove extra tokens and spaces. yield fixer.removeRange([functionToken.range[0], leftParenToken.range[0]]); } - yield fixer.insertTextBefore(node.body, "=> "); + yield fixer.insertTextAfter(tokenBeforeBody, " =>"); // Get the node that will become the new arrow function. let replacedNode = callbackInfo.isLexicalThis ? node.parent.parent : node; diff --git a/eslint/lib/rules/prefer-const.js b/eslint/lib/rules/prefer-const.js index e3d2db7..b43975e 100644 --- a/eslint/lib/rules/prefer-const.js +++ b/eslint/lib/rules/prefer-const.js @@ -334,7 +334,7 @@ module.exports = { docs: { description: "Require `const` declarations for variables that are never reassigned after declared", recommended: false, - url: "https://eslint.org/docs/rules/prefer-const" + url: "https://eslint.org/docs/latest/rules/prefer-const" }, fixable: "code", @@ -356,7 +356,7 @@ module.exports = { create(context) { const options = context.options[0] || {}; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const shouldMatchAnyDestructuredVariable = options.destructuring !== "all"; const ignoreReadBeforeAssign = options.ignoreReadBeforeAssign === true; const variables = []; @@ -493,7 +493,7 @@ module.exports = { VariableDeclaration(node) { if (node.kind === "let" && !isInitOfForStatement(node)) { - variables.push(...context.getDeclaredVariables(node)); + variables.push(...sourceCode.getDeclaredVariables(node)); } } }; diff --git a/eslint/lib/rules/prefer-destructuring.js b/eslint/lib/rules/prefer-destructuring.js index fdf46f6..c6075c5 100644 --- a/eslint/lib/rules/prefer-destructuring.js +++ b/eslint/lib/rules/prefer-destructuring.js @@ -28,7 +28,7 @@ module.exports = { docs: { description: "Require destructuring from arrays and/or objects", recommended: false, - url: "https://eslint.org/docs/rules/prefer-destructuring" + url: "https://eslint.org/docs/latest/rules/prefer-destructuring" }, fixable: "code", @@ -190,7 +190,7 @@ module.exports = { */ function fixIntoObjectDestructuring(fixer, node) { const rightNode = node.init; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; // Don't fix if that would remove any comments. Only comments inside `rightNode.object` can be preserved. if (sourceCode.getCommentsInside(node).length > sourceCode.getCommentsInside(rightNode.object).length) { diff --git a/eslint/lib/rules/prefer-exponentiation-operator.js b/eslint/lib/rules/prefer-exponentiation-operator.js index fec5319..dd4ba2c 100644 --- a/eslint/lib/rules/prefer-exponentiation-operator.js +++ b/eslint/lib/rules/prefer-exponentiation-operator.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const { CALL, ReferenceTracker } = require("eslint-utils"); +const { CALL, ReferenceTracker } = require("@eslint-community/eslint-utils"); //------------------------------------------------------------------------------ // Helpers @@ -92,7 +92,7 @@ module.exports = { docs: { description: "Disallow the use of `Math.pow` in favor of the `**` operator", recommended: false, - url: "https://eslint.org/docs/rules/prefer-exponentiation-operator" + url: "https://eslint.org/docs/latest/rules/prefer-exponentiation-operator" }, schema: [], @@ -104,7 +104,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Reports the given node. @@ -172,8 +172,8 @@ module.exports = { } return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const trackMap = { Math: { @@ -181,8 +181,8 @@ module.exports = { } }; - for (const { node } of tracker.iterateGlobalReferences(trackMap)) { - report(node); + for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { + report(refNode); } } }; diff --git a/eslint/lib/rules/prefer-named-capture-group.js b/eslint/lib/rules/prefer-named-capture-group.js index 1a13ffa..8fb68de 100644 --- a/eslint/lib/rules/prefer-named-capture-group.js +++ b/eslint/lib/rules/prefer-named-capture-group.js @@ -14,8 +14,8 @@ const { CONSTRUCT, ReferenceTracker, getStringIfConstant -} = require("eslint-utils"); -const regexpp = require("regexpp"); +} = require("@eslint-community/eslint-utils"); +const regexpp = require("@eslint-community/regexpp"); //------------------------------------------------------------------------------ // Helpers @@ -23,6 +23,61 @@ const regexpp = require("regexpp"); const parser = new regexpp.RegExpParser(); +/** + * Creates fixer suggestions for the regex, if statically determinable. + * @param {number} groupStart Starting index of the regex group. + * @param {string} pattern The regular expression pattern to be checked. + * @param {string} rawText Source text of the regexNode. + * @param {ASTNode} regexNode AST node which contains the regular expression. + * @returns {Array} Fixer suggestions for the regex, if statically determinable. + */ +function suggestIfPossible(groupStart, pattern, rawText, regexNode) { + switch (regexNode.type) { + case "Literal": + if (typeof regexNode.value === "string" && rawText.includes("\\")) { + return null; + } + break; + case "TemplateLiteral": + if (regexNode.expressions.length || rawText.slice(1, -1) !== pattern) { + return null; + } + break; + default: + return null; + } + + const start = regexNode.range[0] + groupStart + 2; + + return [ + { + fix(fixer) { + const existingTemps = pattern.match(/temp\d+/gu) || []; + const highestTempCount = existingTemps.reduce( + (previous, next) => + Math.max(previous, Number(next.slice("temp".length))), + 0 + ); + + return fixer.insertTextBeforeRange( + [start, start], + `?` + ); + }, + messageId: "addGroupName" + }, + { + fix(fixer) { + return fixer.insertTextBeforeRange( + [start, start], + "?:" + ); + }, + messageId: "addNonCapture" + } + ]; +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -35,26 +90,32 @@ module.exports = { docs: { description: "Enforce using named capture group in regular expression", recommended: false, - url: "https://eslint.org/docs/rules/prefer-named-capture-group" + url: "https://eslint.org/docs/latest/rules/prefer-named-capture-group" }, + hasSuggestions: true, + schema: [], messages: { + addGroupName: "Add name to capture group.", + addNonCapture: "Convert group to non-capturing.", required: "Capture group '{{group}}' should be converted to a named or non-capturing group." } }, create(context) { + const sourceCode = context.sourceCode; /** * Function to check regular expression. - * @param {string} pattern The regular expression pattern to be check. - * @param {ASTNode} node AST node which contains regular expression. + * @param {string} pattern The regular expression pattern to be checked. + * @param {ASTNode} node AST node which contains the regular expression or a call/new expression. + * @param {ASTNode} regexNode AST node which contains the regular expression. * @param {boolean} uFlag Flag indicates whether unicode mode is enabled or not. * @returns {void} */ - function checkRegex(pattern, node, uFlag) { + function checkRegex(pattern, node, regexNode, uFlag) { let ast; try { @@ -68,12 +129,16 @@ module.exports = { regexpp.visitRegExpAST(ast, { onCapturingGroupEnter(group) { if (!group.name) { + const rawText = sourceCode.getText(regexNode); + const suggest = suggestIfPossible(group.start, pattern, rawText, regexNode); + context.report({ node, messageId: "required", data: { group: group.raw - } + }, + suggest }); } } @@ -83,11 +148,11 @@ module.exports = { return { Literal(node) { if (node.regex) { - checkRegex(node.regex.pattern, node, node.regex.flags.includes("u")); + checkRegex(node.regex.pattern, node, node, node.regex.flags.includes("u")); } }, - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const traceMap = { RegExp: { @@ -96,12 +161,12 @@ module.exports = { } }; - for (const { node } of tracker.iterateGlobalReferences(traceMap)) { - const regex = getStringIfConstant(node.arguments[0]); - const flags = getStringIfConstant(node.arguments[1]); + for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { + const regex = getStringIfConstant(refNode.arguments[0]); + const flags = getStringIfConstant(refNode.arguments[1]); if (regex) { - checkRegex(regex, node, flags && flags.includes("u")); + checkRegex(regex, refNode, refNode.arguments[0], flags && flags.includes("u")); } } } diff --git a/eslint/lib/rules/prefer-numeric-literals.js b/eslint/lib/rules/prefer-numeric-literals.js index 5f70158..118d6dc 100644 --- a/eslint/lib/rules/prefer-numeric-literals.js +++ b/eslint/lib/rules/prefer-numeric-literals.js @@ -47,7 +47,7 @@ module.exports = { docs: { description: "Disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals", recommended: false, - url: "https://eslint.org/docs/rules/prefer-numeric-literals" + url: "https://eslint.org/docs/latest/rules/prefer-numeric-literals" }, schema: [], @@ -60,7 +60,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //---------------------------------------------------------------------- // Public diff --git a/eslint/lib/rules/prefer-object-has-own.js b/eslint/lib/rules/prefer-object-has-own.js index 023d0a6..97ea64f 100644 --- a/eslint/lib/rules/prefer-object-has-own.js +++ b/eslint/lib/rules/prefer-object-has-own.js @@ -52,7 +52,7 @@ module.exports = { description: "Disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`", recommended: false, - url: "https://eslint.org/docs/rules/prefer-object-has-own" + url: "https://eslint.org/docs/latest/rules/prefer-object-has-own" }, schema: [], messages: { @@ -61,6 +61,9 @@ module.exports = { fixable: "code" }, create(context) { + + const sourceCode = context.sourceCode; + return { CallExpression(node) { if (!(node.callee.type === "MemberExpression" && node.callee.object.type === "MemberExpression")) { @@ -72,7 +75,7 @@ module.exports = { const isObject = hasLeftHandObject(node.callee.object); // check `Object` scope - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const variable = astUtils.getVariableByName(scope, "Object"); if ( @@ -85,7 +88,6 @@ module.exports = { node, messageId: "useHasOwn", fix(fixer) { - const sourceCode = context.getSourceCode(); if (sourceCode.getCommentsInside(node.callee).length > 0) { return null; diff --git a/eslint/lib/rules/prefer-object-spread.js b/eslint/lib/rules/prefer-object-spread.js index 0819200..60b0c31 100644 --- a/eslint/lib/rules/prefer-object-spread.js +++ b/eslint/lib/rules/prefer-object-spread.js @@ -1,12 +1,11 @@ /** * @fileoverview Prefers object spread property over Object.assign * @author Sharmila Jesupaul - * See LICENSE file in root directory for full license. */ "use strict"; -const { CALL, ReferenceTracker } = require("eslint-utils"); +const { CALL, ReferenceTracker } = require("@eslint-community/eslint-utils"); const { isCommaToken, isOpeningParenToken, @@ -247,9 +246,9 @@ module.exports = { docs: { description: - "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead.", + "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead", recommended: false, - url: "https://eslint.org/docs/rules/prefer-object-spread" + url: "https://eslint.org/docs/latest/rules/prefer-object-spread" }, schema: [], @@ -262,11 +261,11 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const trackMap = { Object: { @@ -275,22 +274,22 @@ module.exports = { }; // Iterate all calls of `Object.assign` (only of the global variable `Object`). - for (const { node } of tracker.iterateGlobalReferences(trackMap)) { + for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { if ( - node.arguments.length >= 1 && - node.arguments[0].type === "ObjectExpression" && - !hasArraySpread(node) && + refNode.arguments.length >= 1 && + refNode.arguments[0].type === "ObjectExpression" && + !hasArraySpread(refNode) && !( - node.arguments.length > 1 && - hasArgumentsWithAccessors(node) + refNode.arguments.length > 1 && + hasArgumentsWithAccessors(refNode) ) ) { - const messageId = node.arguments.length === 1 + const messageId = refNode.arguments.length === 1 ? "useLiteralMessage" : "useSpreadMessage"; - const fix = defineFixer(node, sourceCode); + const fix = defineFixer(refNode, sourceCode); - context.report({ node, messageId, fix }); + context.report({ node: refNode, messageId, fix }); } } } diff --git a/eslint/lib/rules/prefer-promise-reject-errors.js b/eslint/lib/rules/prefer-promise-reject-errors.js index bd7bdcb..e990265 100644 --- a/eslint/lib/rules/prefer-promise-reject-errors.js +++ b/eslint/lib/rules/prefer-promise-reject-errors.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Require using Error objects as Promise rejection reasons", recommended: false, - url: "https://eslint.org/docs/rules/prefer-promise-reject-errors" + url: "https://eslint.org/docs/latest/rules/prefer-promise-reject-errors" }, fixable: null, @@ -41,6 +41,7 @@ module.exports = { create(context) { const ALLOW_EMPTY_REJECT = context.options.length && context.options[0].allowEmptyReject; + const sourceCode = context.sourceCode; //---------------------------------------------------------------------- // Helpers @@ -100,7 +101,7 @@ module.exports = { node.arguments.length && astUtils.isFunction(node.arguments[0]) && node.arguments[0].params.length > 1 && node.arguments[0].params[1].type === "Identifier" ) { - context.getDeclaredVariables(node.arguments[0]) + sourceCode.getDeclaredVariables(node.arguments[0]) /* * Find the first variable that matches the second parameter's name. diff --git a/eslint/lib/rules/prefer-reflect.js b/eslint/lib/rules/prefer-reflect.js index 68ffa88..d579b48 100644 --- a/eslint/lib/rules/prefer-reflect.js +++ b/eslint/lib/rules/prefer-reflect.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Require `Reflect` methods where applicable", recommended: false, - url: "https://eslint.org/docs/rules/prefer-reflect" + url: "https://eslint.org/docs/latest/rules/prefer-reflect" }, deprecated: true, diff --git a/eslint/lib/rules/prefer-regex-literals.js b/eslint/lib/rules/prefer-regex-literals.js index f30eddb..39e2950 100644 --- a/eslint/lib/rules/prefer-regex-literals.js +++ b/eslint/lib/rules/prefer-regex-literals.js @@ -10,16 +10,15 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("eslint-utils"); -const { RegExpValidator, visitRegExpAST, RegExpParser } = require("regexpp"); +const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("@eslint-community/eslint-utils"); +const { RegExpValidator, visitRegExpAST, RegExpParser } = require("@eslint-community/regexpp"); const { canTokensBeAdjacent } = require("./utils/ast-utils"); +const { REGEXPP_LATEST_ECMA_VERSION } = require("./utils/regular-expressions"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const REGEXPP_LATEST_ECMA_VERSION = 2022; - /** * Determines whether the given node is a string literal. * @param {ASTNode} node Node to check. @@ -125,7 +124,7 @@ module.exports = { docs: { description: "Disallow use of the `RegExp` constructor in favor of regular expression literals", recommended: false, - url: "https://eslint.org/docs/rules/prefer-regex-literals" + url: "https://eslint.org/docs/latest/rules/prefer-regex-literals" }, hasSuggestions: true, @@ -146,6 +145,8 @@ module.exports = { messages: { unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor.", replaceWithLiteral: "Replace with an equivalent regular expression literal.", + replaceWithLiteralAndFlags: "Replace with an equivalent regular expression literal with flags '{{ flags }}'.", + replaceWithIntendedLiteralAndFlags: "Replace with a regular expression literal with flags '{{ flags }}'.", unexpectedRedundantRegExp: "Regular expression literal is unnecessarily wrapped within a 'RegExp' constructor.", unexpectedRedundantRegExpWithFlags: "Use regular expression literal with flags instead of the 'RegExp' constructor." } @@ -153,7 +154,7 @@ module.exports = { create(context) { const [{ disallowRedundantWrapping = false } = {}] = context.options; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Determines whether the given identifier node is a reference to a global variable. @@ -161,7 +162,7 @@ module.exports = { * @returns {boolean} True if the identifier is a reference to a global variable. */ function isGlobalReference(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const variable = findVariable(scope, node); return variable !== null && variable.scope.type === "global" && variable.defs.length === 0; @@ -248,16 +249,18 @@ module.exports = { /** * Returns a ecmaVersion compatible for regexpp. - * @param {any} ecmaVersion The ecmaVersion to convert. + * @param {number} ecmaVersion The ecmaVersion to convert. * @returns {import("regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp. */ function getRegexppEcmaVersion(ecmaVersion) { - if (typeof ecmaVersion !== "number" || ecmaVersion <= 5) { + if (ecmaVersion <= 5) { return 5; } - return Math.min(ecmaVersion + 2009, REGEXPP_LATEST_ECMA_VERSION); + return Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION); } + const regexppEcmaVersion = getRegexppEcmaVersion(context.languageOptions.ecmaVersion); + /** * Makes a character escaped or else returns null. * @param {string} character The character to escape. @@ -293,9 +296,86 @@ module.exports = { } } + /** + * Checks whether the given regex and flags are valid for the ecma version or not. + * @param {string} pattern The regex pattern to check. + * @param {string | undefined} flags The regex flags to check. + * @returns {boolean} True if the given regex pattern and flags are valid for the ecma version. + */ + function isValidRegexForEcmaVersion(pattern, flags) { + const validator = new RegExpValidator({ ecmaVersion: regexppEcmaVersion }); + + try { + validator.validatePattern(pattern, 0, pattern.length, flags ? flags.includes("u") : false); + if (flags) { + validator.validateFlags(flags); + } + return true; + } catch { + return false; + } + } + + /** + * Checks whether two given regex flags contain the same flags or not. + * @param {string} flagsA The regex flags. + * @param {string} flagsB The regex flags. + * @returns {boolean} True if two regex flags contain same flags. + */ + function areFlagsEqual(flagsA, flagsB) { + return [...flagsA].sort().join("") === [...flagsB].sort().join(""); + } + + + /** + * Merges two regex flags. + * @param {string} flagsA The regex flags. + * @param {string} flagsB The regex flags. + * @returns {string} The merged regex flags. + */ + function mergeRegexFlags(flagsA, flagsB) { + const flagsSet = new Set([ + ...flagsA, + ...flagsB + ]); + + return [...flagsSet].join(""); + } + + /** + * Checks whether a give node can be fixed to the given regex pattern and flags. + * @param {ASTNode} node The node to check. + * @param {string} pattern The regex pattern to check. + * @param {string} flags The regex flags + * @returns {boolean} True if a node can be fixed to the given regex pattern and flags. + */ + function canFixTo(node, pattern, flags) { + const tokenBefore = sourceCode.getTokenBefore(node); + + return sourceCode.getCommentsInside(node).length === 0 && + (!tokenBefore || validPrecedingTokens.has(tokenBefore.value)) && + isValidRegexForEcmaVersion(pattern, flags); + } + + /** + * Returns a safe output code considering the before and after tokens. + * @param {ASTNode} node The regex node. + * @param {string} newRegExpValue The new regex expression value. + * @returns {string} The output code. + */ + function getSafeOutput(node, newRegExpValue) { + const tokenBefore = sourceCode.getTokenBefore(node); + const tokenAfter = sourceCode.getTokenAfter(node); + + return (tokenBefore && !canTokensBeAdjacent(tokenBefore, newRegExpValue) && tokenBefore.range[1] === node.range[0] ? " " : "") + + newRegExpValue + + (tokenAfter && !canTokensBeAdjacent(newRegExpValue, tokenAfter) && node.range[1] === tokenAfter.range[0] ? " " : ""); + + } + return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const traceMap = { RegExp: { @@ -304,37 +384,82 @@ module.exports = { } }; - for (const { node } of tracker.iterateGlobalReferences(traceMap)) { - if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(node)) { - if (node.arguments.length === 2) { - context.report({ node, messageId: "unexpectedRedundantRegExpWithFlags" }); + for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { + if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(refNode)) { + const regexNode = refNode.arguments[0]; + + if (refNode.arguments.length === 2) { + const suggests = []; + + const argFlags = getStringValue(refNode.arguments[1]) || ""; + + if (canFixTo(refNode, regexNode.regex.pattern, argFlags)) { + suggests.push({ + messageId: "replaceWithLiteralAndFlags", + pattern: regexNode.regex.pattern, + flags: argFlags + }); + } + + const literalFlags = regexNode.regex.flags || ""; + const mergedFlags = mergeRegexFlags(literalFlags, argFlags); + + if ( + !areFlagsEqual(mergedFlags, argFlags) && + canFixTo(refNode, regexNode.regex.pattern, mergedFlags) + ) { + suggests.push({ + messageId: "replaceWithIntendedLiteralAndFlags", + pattern: regexNode.regex.pattern, + flags: mergedFlags + }); + } + + context.report({ + node: refNode, + messageId: "unexpectedRedundantRegExpWithFlags", + suggest: suggests.map(({ flags, pattern, messageId }) => ({ + messageId, + data: { + flags + }, + fix(fixer) { + return fixer.replaceText(refNode, getSafeOutput(refNode, `/${pattern}/${flags}`)); + } + })) + }); } else { - context.report({ node, messageId: "unexpectedRedundantRegExp" }); + const outputs = []; + + if (canFixTo(refNode, regexNode.regex.pattern, regexNode.regex.flags)) { + outputs.push(sourceCode.getText(regexNode)); + } + + + context.report({ + node: refNode, + messageId: "unexpectedRedundantRegExp", + suggest: outputs.map(output => ({ + messageId: "replaceWithLiteral", + fix(fixer) { + return fixer.replaceText( + refNode, + getSafeOutput(refNode, output) + ); + } + })) + }); } - } else if (hasOnlyStaticStringArguments(node)) { - let regexContent = getStringValue(node.arguments[0]); + } else if (hasOnlyStaticStringArguments(refNode)) { + let regexContent = getStringValue(refNode.arguments[0]); let noFix = false; let flags; - if (node.arguments[1]) { - flags = getStringValue(node.arguments[1]); + if (refNode.arguments[1]) { + flags = getStringValue(refNode.arguments[1]); } - const regexppEcmaVersion = getRegexppEcmaVersion(context.parserOptions.ecmaVersion); - const RegExpValidatorInstance = new RegExpValidator({ ecmaVersion: regexppEcmaVersion }); - - try { - RegExpValidatorInstance.validatePattern(regexContent, 0, regexContent.length, flags ? flags.includes("u") : false); - if (flags) { - RegExpValidatorInstance.validateFlags(flags); - } - } catch { - noFix = true; - } - - const tokenBefore = sourceCode.getTokenBefore(node); - - if (tokenBefore && !validPrecedingTokens.has(tokenBefore.value)) { + if (!canFixTo(refNode, regexContent, flags)) { noFix = true; } @@ -342,10 +467,6 @@ module.exports = { noFix = true; } - if (sourceCode.getCommentsInside(node).length > 0) { - noFix = true; - } - if (regexContent && !noFix) { let charIncrease = 0; @@ -372,19 +493,12 @@ module.exports = { const newRegExpValue = `/${regexContent || "(?:)"}/${flags || ""}`; context.report({ - node, + node: refNode, messageId: "unexpectedRegExp", suggest: noFix ? [] : [{ messageId: "replaceWithLiteral", fix(fixer) { - const tokenAfter = sourceCode.getTokenAfter(node); - - return fixer.replaceText( - node, - (tokenBefore && !canTokensBeAdjacent(tokenBefore, newRegExpValue) && tokenBefore.range[1] === node.range[0] ? " " : "") + - newRegExpValue + - (tokenAfter && !canTokensBeAdjacent(newRegExpValue, tokenAfter) && node.range[1] === tokenAfter.range[0] ? " " : "") - ); + return fixer.replaceText(refNode, getSafeOutput(refNode, newRegExpValue)); } }] }); diff --git a/eslint/lib/rules/prefer-rest-params.js b/eslint/lib/rules/prefer-rest-params.js index 14b9ae5..b7e35b4 100644 --- a/eslint/lib/rules/prefer-rest-params.js +++ b/eslint/lib/rules/prefer-rest-params.js @@ -67,7 +67,7 @@ module.exports = { docs: { description: "Require rest parameters instead of `arguments`", recommended: false, - url: "https://eslint.org/docs/rules/prefer-rest-params" + url: "https://eslint.org/docs/latest/rules/prefer-rest-params" }, schema: [], @@ -79,6 +79,8 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + /** * Reports a given reference. * @param {eslint-scope.Reference} reference A reference to report. @@ -94,10 +96,11 @@ module.exports = { /** * Reports references of the implicit `arguments` variable if exist. + * @param {ASTNode} node The node representing the function. * @returns {void} */ - function checkForArguments() { - const argumentsVar = getVariableOfArguments(context.getScope()); + function checkForArguments(node) { + const argumentsVar = getVariableOfArguments(sourceCode.getScope(node)); if (argumentsVar) { argumentsVar diff --git a/eslint/lib/rules/prefer-spread.js b/eslint/lib/rules/prefer-spread.js index c8909fc..7013c1d 100644 --- a/eslint/lib/rules/prefer-spread.js +++ b/eslint/lib/rules/prefer-spread.js @@ -51,7 +51,7 @@ module.exports = { docs: { description: "Require spread operators instead of `.apply()`", recommended: false, - url: "https://eslint.org/docs/rules/prefer-spread" + url: "https://eslint.org/docs/latest/rules/prefer-spread" }, schema: [], @@ -63,7 +63,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { CallExpression(node) { diff --git a/eslint/lib/rules/prefer-template.js b/eslint/lib/rules/prefer-template.js index 167c187..a2c8c72 100644 --- a/eslint/lib/rules/prefer-template.js +++ b/eslint/lib/rules/prefer-template.js @@ -130,7 +130,7 @@ module.exports = { docs: { description: "Require template literals instead of string concatenation", recommended: false, - url: "https://eslint.org/docs/rules/prefer-template" + url: "https://eslint.org/docs/latest/rules/prefer-template" }, schema: [], @@ -142,7 +142,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; let done = Object.create(null); /** diff --git a/eslint/lib/rules/quote-props.js b/eslint/lib/rules/quote-props.js index db94239..8abab15 100644 --- a/eslint/lib/rules/quote-props.js +++ b/eslint/lib/rules/quote-props.js @@ -24,7 +24,7 @@ module.exports = { docs: { description: "Require quotes around object literal property names", recommended: false, - url: "https://eslint.org/docs/rules/quote-props" + url: "https://eslint.org/docs/latest/rules/quote-props" }, schema: { @@ -86,7 +86,7 @@ module.exports = { CHECK_UNNECESSARY = !context.options[1] || context.options[1].unnecessary !== false, NUMBERS = context.options[1] && context.options[1].numbers, - sourceCode = context.getSourceCode(); + sourceCode = context.sourceCode; /** diff --git a/eslint/lib/rules/quotes.js b/eslint/lib/rules/quotes.js index ab7b38b..9efb980 100644 --- a/eslint/lib/rules/quotes.js +++ b/eslint/lib/rules/quotes.js @@ -82,7 +82,7 @@ module.exports = { docs: { description: "Enforce the consistent use of either backticks, double, or single quotes", recommended: false, - url: "https://eslint.org/docs/rules/quotes" + url: "https://eslint.org/docs/latest/rules/quotes" }, fixable: "code", @@ -123,7 +123,7 @@ module.exports = { settings = QUOTE_SETTINGS[quoteOption || "double"], options = context.options[1], allowTemplateLiterals = options && options.allowTemplateLiterals === true, - sourceCode = context.getSourceCode(); + sourceCode = context.sourceCode; let avoidEscape = options && options.avoidEscape === true; // deprecated diff --git a/eslint/lib/rules/radix.js b/eslint/lib/rules/radix.js index 0618d98..7df97d9 100644 --- a/eslint/lib/rules/radix.js +++ b/eslint/lib/rules/radix.js @@ -82,7 +82,7 @@ module.exports = { docs: { description: "Enforce the consistent use of the radix argument when using `parseInt()`", recommended: false, - url: "https://eslint.org/docs/rules/radix" + url: "https://eslint.org/docs/latest/rules/radix" }, hasSuggestions: true, @@ -104,6 +104,7 @@ module.exports = { create(context) { const mode = context.options[0] || MODE_ALWAYS; + const sourceCode = context.sourceCode; /** * Checks the arguments of a given CallExpression node and reports it if it @@ -131,7 +132,6 @@ module.exports = { { messageId: "addRadixParameter10", fix(fixer) { - const sourceCode = context.getSourceCode(); const tokens = sourceCode.getTokens(node); const lastToken = tokens[tokens.length - 1]; // Parenthesis. const secondToLastToken = tokens[tokens.length - 2]; // May or may not be a comma. @@ -162,18 +162,18 @@ module.exports = { } return { - "Program:exit"() { - const scope = context.getScope(); + "Program:exit"(node) { + const scope = sourceCode.getScope(node); let variable; // Check `parseInt()` variable = astUtils.getVariableByName(scope, "parseInt"); if (variable && !isShadowed(variable)) { variable.references.forEach(reference => { - const node = reference.identifier; + const idNode = reference.identifier; - if (astUtils.isCallee(node)) { - checkArguments(node.parent); + if (astUtils.isCallee(idNode)) { + checkArguments(idNode.parent); } }); } @@ -182,12 +182,12 @@ module.exports = { variable = astUtils.getVariableByName(scope, "Number"); if (variable && !isShadowed(variable)) { variable.references.forEach(reference => { - const node = reference.identifier.parent; - const maybeCallee = node.parent.type === "ChainExpression" - ? node.parent - : node; + const parentNode = reference.identifier.parent; + const maybeCallee = parentNode.parent.type === "ChainExpression" + ? parentNode.parent + : parentNode; - if (isParseIntMethod(node) && astUtils.isCallee(maybeCallee)) { + if (isParseIntMethod(parentNode) && astUtils.isCallee(maybeCallee)) { checkArguments(maybeCallee.parent); } }); diff --git a/eslint/lib/rules/require-atomic-updates.js b/eslint/lib/rules/require-atomic-updates.js index 7a5f822..ba369a2 100644 --- a/eslint/lib/rules/require-atomic-updates.js +++ b/eslint/lib/rules/require-atomic-updates.js @@ -173,7 +173,7 @@ module.exports = { docs: { description: "Disallow assignments that can lead to race conditions due to usage of `await` or `yield`", recommended: false, - url: "https://eslint.org/docs/rules/require-atomic-updates" + url: "https://eslint.org/docs/latest/rules/require-atomic-updates" }, fixable: null, @@ -198,14 +198,14 @@ module.exports = { create(context) { const allowProperties = !!context.options[0] && context.options[0].allowProperties; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const assignmentReferences = new Map(); const segmentInfo = new SegmentInfo(); let stack = null; return { - onCodePathStart(codePath) { - const scope = context.getScope(); + onCodePathStart(codePath, node) { + const scope = sourceCode.getScope(node); const shouldVerify = scope.type === "function" && (scope.block.async || scope.block.generator); diff --git a/eslint/lib/rules/require-await.js b/eslint/lib/rules/require-await.js index 1add255..952dde5 100644 --- a/eslint/lib/rules/require-await.js +++ b/eslint/lib/rules/require-await.js @@ -36,7 +36,7 @@ module.exports = { docs: { description: "Disallow async functions which have no `await` expression", recommended: false, - url: "https://eslint.org/docs/rules/require-await" + url: "https://eslint.org/docs/latest/rules/require-await" }, schema: [], @@ -47,7 +47,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; let scopeInfo = null; /** diff --git a/eslint/lib/rules/require-jsdoc.js b/eslint/lib/rules/require-jsdoc.js index 755f6df..b6fedf2 100644 --- a/eslint/lib/rules/require-jsdoc.js +++ b/eslint/lib/rules/require-jsdoc.js @@ -13,7 +13,7 @@ module.exports = { docs: { description: "Require JSDoc comments", recommended: false, - url: "https://eslint.org/docs/rules/require-jsdoc" + url: "https://eslint.org/docs/latest/rules/require-jsdoc" }, schema: [ @@ -61,7 +61,7 @@ module.exports = { }, create(context) { - const source = context.getSourceCode(); + const source = context.sourceCode; const DEFAULT_OPTIONS = { FunctionDeclaration: true, MethodDefinition: false, diff --git a/eslint/lib/rules/require-unicode-regexp.js b/eslint/lib/rules/require-unicode-regexp.js index 4236af6..f748753 100644 --- a/eslint/lib/rules/require-unicode-regexp.js +++ b/eslint/lib/rules/require-unicode-regexp.js @@ -14,7 +14,9 @@ const { CONSTRUCT, ReferenceTracker, getStringIfConstant -} = require("eslint-utils"); +} = require("@eslint-community/eslint-utils"); +const astUtils = require("./utils/ast-utils.js"); +const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); //------------------------------------------------------------------------------ // Rule Definition @@ -28,10 +30,13 @@ module.exports = { docs: { description: "Enforce the use of `u` flag on RegExp", recommended: false, - url: "https://eslint.org/docs/rules/require-unicode-regexp" + url: "https://eslint.org/docs/latest/rules/require-unicode-regexp" }, + hasSuggestions: true, + messages: { + addUFlag: "Add the 'u' flag.", requireUFlag: "Use the 'u' flag." }, @@ -39,28 +44,83 @@ module.exports = { }, create(context) { + + const sourceCode = context.sourceCode; + return { "Literal[regex]"(node) { const flags = node.regex.flags || ""; if (!flags.includes("u")) { - context.report({ node, messageId: "requireUFlag" }); + context.report({ + messageId: "requireUFlag", + node, + suggest: isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern) + ? [ + { + fix(fixer) { + return fixer.insertTextAfter(node, "u"); + }, + messageId: "addUFlag" + } + ] + : null + }); } }, - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const trackMap = { RegExp: { [CALL]: true, [CONSTRUCT]: true } }; - for (const { node } of tracker.iterateGlobalReferences(trackMap)) { - const flagsNode = node.arguments[1]; + for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { + const [patternNode, flagsNode] = refNode.arguments; + + if (patternNode && patternNode.type === "SpreadElement") { + continue; + } + const pattern = getStringIfConstant(patternNode, scope); const flags = getStringIfConstant(flagsNode, scope); if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) { - context.report({ node, messageId: "requireUFlag" }); + context.report({ + messageId: "requireUFlag", + node: refNode, + suggest: typeof pattern === "string" && isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern) + ? [ + { + fix(fixer) { + if (flagsNode) { + if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") { + const flagsNodeText = sourceCode.getText(flagsNode); + + return fixer.replaceText(flagsNode, [ + flagsNodeText.slice(0, flagsNodeText.length - 1), + flagsNodeText.slice(flagsNodeText.length - 1) + ].join("u")); + } + + // We intentionally don't suggest concatenating + "u" to non-literals + return null; + } + + const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis + + return fixer.insertTextAfter( + penultimateToken, + astUtils.isCommaToken(penultimateToken) + ? ' "u",' + : ', "u"' + ); + }, + messageId: "addUFlag" + } + ] + : null + }); } } } diff --git a/eslint/lib/rules/require-yield.js b/eslint/lib/rules/require-yield.js index b3f1341..f801af0 100644 --- a/eslint/lib/rules/require-yield.js +++ b/eslint/lib/rules/require-yield.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Require generator functions to contain `yield`", recommended: true, - url: "https://eslint.org/docs/rules/require-yield" + url: "https://eslint.org/docs/latest/rules/require-yield" }, schema: [], diff --git a/eslint/lib/rules/rest-spread-spacing.js b/eslint/lib/rules/rest-spread-spacing.js index 17f9aa0..7791238 100644 --- a/eslint/lib/rules/rest-spread-spacing.js +++ b/eslint/lib/rules/rest-spread-spacing.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Enforce spacing between rest and spread operators and their expressions", recommended: false, - url: "https://eslint.org/docs/rules/rest-spread-spacing" + url: "https://eslint.org/docs/latest/rules/rest-spread-spacing" }, fixable: "whitespace", @@ -35,7 +35,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(), + const sourceCode = context.sourceCode, alwaysSpace = context.options[0] === "always"; //-------------------------------------------------------------------------- diff --git a/eslint/lib/rules/semi-spacing.js b/eslint/lib/rules/semi-spacing.js index 875cb62..770f62d 100644 --- a/eslint/lib/rules/semi-spacing.js +++ b/eslint/lib/rules/semi-spacing.js @@ -19,7 +19,7 @@ module.exports = { docs: { description: "Enforce consistent spacing before and after semicolons", recommended: false, - url: "https://eslint.org/docs/rules/semi-spacing" + url: "https://eslint.org/docs/latest/rules/semi-spacing" }, fixable: "whitespace", @@ -52,7 +52,7 @@ module.exports = { create(context) { const config = context.options[0], - sourceCode = context.getSourceCode(); + sourceCode = context.sourceCode; let requireSpaceBefore = false, requireSpaceAfter = true; diff --git a/eslint/lib/rules/semi-style.js b/eslint/lib/rules/semi-style.js index 424858b..67ed1e4 100644 --- a/eslint/lib/rules/semi-style.js +++ b/eslint/lib/rules/semi-style.js @@ -75,7 +75,7 @@ module.exports = { docs: { description: "Enforce location of semicolons", recommended: false, - url: "https://eslint.org/docs/rules/semi-style" + url: "https://eslint.org/docs/latest/rules/semi-style" }, schema: [{ enum: ["last", "first"] }], @@ -87,7 +87,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const option = context.options[0] || "last"; /** diff --git a/eslint/lib/rules/semi.js b/eslint/lib/rules/semi.js index 1e49273..6a47353 100644 --- a/eslint/lib/rules/semi.js +++ b/eslint/lib/rules/semi.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Require or disallow semicolons instead of ASI", recommended: false, - url: "https://eslint.org/docs/rules/semi" + url: "https://eslint.org/docs/latest/rules/semi" }, fixable: "code", @@ -58,7 +58,8 @@ module.exports = { { type: "object", properties: { - omitLastInOneLineBlock: { type: "boolean" } + omitLastInOneLineBlock: { type: "boolean" }, + omitLastInOneLineClassBody: { type: "boolean" } }, additionalProperties: false } @@ -83,8 +84,9 @@ module.exports = { const options = context.options[1]; const never = context.options[0] === "never"; const exceptOneLine = Boolean(options && options.omitLastInOneLineBlock); + const exceptOneLineClassBody = Boolean(options && options.omitLastInOneLineClassBody); const beforeStatementContinuationChars = options && options.beforeStatementContinuationChars || "any"; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers @@ -334,6 +336,27 @@ module.exports = { return false; } + /** + * Checks a node to see if it's the last item in a one-liner `ClassBody` node. + * ClassBody is a one-liner if its braces (and consequently everything between them) are on the same line. + * @param {ASTNode} node The node to check. + * @returns {boolean} whether the node is the last item in a one-liner ClassBody. + */ + function isLastInOneLinerClassBody(node) { + const parent = node.parent; + const nextToken = sourceCode.getTokenAfter(node); + + if (!nextToken || nextToken.value !== "}") { + return false; + } + + if (parent.type === "ClassBody") { + return parent.loc.start.line === parent.loc.end.line; + } + + return false; + } + /** * Checks a node to see if it's followed by a semicolon. * @param {ASTNode} node The node to check. @@ -354,10 +377,12 @@ module.exports = { } } else { const oneLinerBlock = (exceptOneLine && isLastInOneLinerBlock(node)); + const oneLinerClassBody = (exceptOneLineClassBody && isLastInOneLinerClassBody(node)); + const oneLinerBlockOrClassBody = oneLinerBlock || oneLinerClassBody; - if (isSemi && oneLinerBlock) { + if (isSemi && oneLinerBlockOrClassBody) { report(node, true); - } else if (!isSemi && !oneLinerBlock) { + } else if (!isSemi && !oneLinerBlockOrClassBody) { report(node); } } diff --git a/eslint/lib/rules/sort-imports.js b/eslint/lib/rules/sort-imports.js index bfb0765..04814ed 100644 --- a/eslint/lib/rules/sort-imports.js +++ b/eslint/lib/rules/sort-imports.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Enforce sorted import declarations within modules", recommended: false, - url: "https://eslint.org/docs/rules/sort-imports" + url: "https://eslint.org/docs/latest/rules/sort-imports" }, schema: [ @@ -71,7 +71,7 @@ module.exports = { ignoreMemberSort = configuration.ignoreMemberSort || false, memberSyntaxSortOrder = configuration.memberSyntaxSortOrder || ["none", "all", "multiple", "single"], allowSeparatedGroups = configuration.allowSeparatedGroups || false, - sourceCode = context.getSourceCode(); + sourceCode = context.sourceCode; let previousDeclaration = null; /** diff --git a/eslint/lib/rules/sort-keys.js b/eslint/lib/rules/sort-keys.js index 1523ab7..088b589 100644 --- a/eslint/lib/rules/sort-keys.js +++ b/eslint/lib/rules/sort-keys.js @@ -83,7 +83,7 @@ module.exports = { docs: { description: "Require object keys to be sorted", recommended: false, - url: "https://eslint.org/docs/rules/sort-keys" + url: "https://eslint.org/docs/latest/rules/sort-keys" }, schema: [ @@ -135,7 +135,7 @@ module.exports = { // The stack to save the previous property's name for each object literals. let stack = null; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { ObjectExpression(node) { diff --git a/eslint/lib/rules/sort-vars.js b/eslint/lib/rules/sort-vars.js index ec0718e..8fd723f 100644 --- a/eslint/lib/rules/sort-vars.js +++ b/eslint/lib/rules/sort-vars.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Require variables within the same declaration block to be sorted", recommended: false, - url: "https://eslint.org/docs/rules/sort-vars" + url: "https://eslint.org/docs/latest/rules/sort-vars" }, schema: [ @@ -44,7 +44,7 @@ module.exports = { const configuration = context.options[0] || {}, ignoreCase = configuration.ignoreCase || false, - sourceCode = context.getSourceCode(); + sourceCode = context.sourceCode; return { VariableDeclaration(node) { diff --git a/eslint/lib/rules/space-before-blocks.js b/eslint/lib/rules/space-before-blocks.js index ffd33dd..a580a4f 100644 --- a/eslint/lib/rules/space-before-blocks.js +++ b/eslint/lib/rules/space-before-blocks.js @@ -42,7 +42,7 @@ module.exports = { docs: { description: "Enforce consistent spacing before blocks", recommended: false, - url: "https://eslint.org/docs/rules/space-before-blocks" + url: "https://eslint.org/docs/latest/rules/space-before-blocks" }, fixable: "whitespace", @@ -80,7 +80,7 @@ module.exports = { create(context) { const config = context.options[0], - sourceCode = context.getSourceCode(); + sourceCode = context.sourceCode; let alwaysFunctions = true, alwaysKeywords = true, alwaysClasses = true, diff --git a/eslint/lib/rules/space-before-function-paren.js b/eslint/lib/rules/space-before-function-paren.js index b56ac3c..c5faa8c 100644 --- a/eslint/lib/rules/space-before-function-paren.js +++ b/eslint/lib/rules/space-before-function-paren.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Enforce consistent spacing before `function` definition opening parenthesis", recommended: false, - url: "https://eslint.org/docs/rules/space-before-function-paren" + url: "https://eslint.org/docs/latest/rules/space-before-function-paren" }, fixable: "whitespace", @@ -59,7 +59,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const baseConfig = typeof context.options[0] === "string" ? context.options[0] : "always"; const overrideConfig = typeof context.options[0] === "object" ? context.options[0] : {}; diff --git a/eslint/lib/rules/space-in-parens.js b/eslint/lib/rules/space-in-parens.js index 42d9bb5..c6c06d2 100644 --- a/eslint/lib/rules/space-in-parens.js +++ b/eslint/lib/rules/space-in-parens.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Enforce consistent spacing inside parentheses", recommended: false, - url: "https://eslint.org/docs/rules/space-in-parens" + url: "https://eslint.org/docs/latest/rules/space-in-parens" }, fixable: "whitespace", @@ -102,7 +102,7 @@ module.exports = { //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Determines if a token is one of the exceptions for the opener paren diff --git a/eslint/lib/rules/space-infix-ops.js b/eslint/lib/rules/space-infix-ops.js index 141c269..81a95f8 100644 --- a/eslint/lib/rules/space-infix-ops.js +++ b/eslint/lib/rules/space-infix-ops.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Require spacing around infix operators", recommended: false, - url: "https://eslint.org/docs/rules/space-infix-ops" + url: "https://eslint.org/docs/latest/rules/space-infix-ops" }, fixable: "whitespace", @@ -43,7 +43,7 @@ module.exports = { create(context) { const int32Hint = context.options[0] ? context.options[0].int32Hint === true : false; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Returns the first token which violates the rule diff --git a/eslint/lib/rules/space-unary-ops.js b/eslint/lib/rules/space-unary-ops.js index 1d9141d..381381d 100644 --- a/eslint/lib/rules/space-unary-ops.js +++ b/eslint/lib/rules/space-unary-ops.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Enforce consistent spacing before or after unary operators", recommended: false, - url: "https://eslint.org/docs/rules/space-unary-ops" + url: "https://eslint.org/docs/latest/rules/space-unary-ops" }, fixable: "whitespace", @@ -62,7 +62,7 @@ module.exports = { create(context) { const options = context.options[0] || { words: true, nonwords: false }; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers diff --git a/eslint/lib/rules/spaced-comment.js b/eslint/lib/rules/spaced-comment.js index 6aedeae..2eb7f0e 100644 --- a/eslint/lib/rules/spaced-comment.js +++ b/eslint/lib/rules/spaced-comment.js @@ -154,7 +154,7 @@ module.exports = { docs: { description: "Enforce consistent spacing after the `//` or `/*` in a comment", recommended: false, - url: "https://eslint.org/docs/rules/spaced-comment" + url: "https://eslint.org/docs/latest/rules/spaced-comment" }, fixable: "whitespace", @@ -235,7 +235,7 @@ module.exports = { create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; // Unless the first option is never, require a space const requireSpace = context.options[0] !== "never"; diff --git a/eslint/lib/rules/strict.js b/eslint/lib/rules/strict.js index e677c95..f9dd750 100644 --- a/eslint/lib/rules/strict.js +++ b/eslint/lib/rules/strict.js @@ -71,7 +71,7 @@ module.exports = { docs: { description: "Require or disallow strict mode directives", recommended: false, - url: "https://eslint.org/docs/rules/strict" + url: "https://eslint.org/docs/latest/rules/strict" }, schema: [ @@ -105,7 +105,7 @@ module.exports = { if (ecmaFeatures.impliedStrict) { mode = "implied"; } else if (mode === "safe") { - mode = ecmaFeatures.globalReturn ? "global" : "function"; + mode = ecmaFeatures.globalReturn || context.languageOptions.sourceType === "commonjs" ? "global" : "function"; } /** diff --git a/eslint/lib/rules/switch-colon-spacing.js b/eslint/lib/rules/switch-colon-spacing.js index c1df496..45e4018 100644 --- a/eslint/lib/rules/switch-colon-spacing.js +++ b/eslint/lib/rules/switch-colon-spacing.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Enforce spacing around colons of switch statements", recommended: false, - url: "https://eslint.org/docs/rules/switch-colon-spacing" + url: "https://eslint.org/docs/latest/rules/switch-colon-spacing" }, schema: [ @@ -46,7 +46,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const options = context.options[0] || {}; const beforeSpacing = options.before === true; // false by default const afterSpacing = options.after !== false; // true by default diff --git a/eslint/lib/rules/symbol-description.js b/eslint/lib/rules/symbol-description.js index 1c8a364..4528f09 100644 --- a/eslint/lib/rules/symbol-description.js +++ b/eslint/lib/rules/symbol-description.js @@ -24,7 +24,7 @@ module.exports = { docs: { description: "Require symbol descriptions", recommended: false, - url: "https://eslint.org/docs/rules/symbol-description" + url: "https://eslint.org/docs/latest/rules/symbol-description" }, fixable: null, schema: [], @@ -35,6 +35,8 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + /** * Reports if node does not conform the rule in case rule is set to * report missing description @@ -51,16 +53,16 @@ module.exports = { } return { - "Program:exit"() { - const scope = context.getScope(); + "Program:exit"(node) { + const scope = sourceCode.getScope(node); const variable = astUtils.getVariableByName(scope, "Symbol"); if (variable && variable.defs.length === 0) { variable.references.forEach(reference => { - const node = reference.identifier; + const idNode = reference.identifier; - if (astUtils.isCallee(node)) { - checkArgument(node.parent); + if (astUtils.isCallee(idNode)) { + checkArgument(idNode.parent); } }); } diff --git a/eslint/lib/rules/template-curly-spacing.js b/eslint/lib/rules/template-curly-spacing.js index 35d4bba..cc9bbe3 100644 --- a/eslint/lib/rules/template-curly-spacing.js +++ b/eslint/lib/rules/template-curly-spacing.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Require or disallow spacing around embedded expressions of template strings", recommended: false, - url: "https://eslint.org/docs/rules/template-curly-spacing" + url: "https://eslint.org/docs/latest/rules/template-curly-spacing" }, fixable: "whitespace", @@ -40,7 +40,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const always = context.options[0] === "always"; /** diff --git a/eslint/lib/rules/template-tag-spacing.js b/eslint/lib/rules/template-tag-spacing.js index 3140fa0..9bfdfc2 100644 --- a/eslint/lib/rules/template-tag-spacing.js +++ b/eslint/lib/rules/template-tag-spacing.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Require or disallow spacing between template tags and their literals", recommended: false, - url: "https://eslint.org/docs/rules/template-tag-spacing" + url: "https://eslint.org/docs/latest/rules/template-tag-spacing" }, fixable: "whitespace", @@ -33,7 +33,7 @@ module.exports = { create(context) { const never = context.options[0] !== "always"; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Check if a space is present between a template tag and its literal diff --git a/eslint/lib/rules/unicode-bom.js b/eslint/lib/rules/unicode-bom.js index 482d3bb..09971d2 100644 --- a/eslint/lib/rules/unicode-bom.js +++ b/eslint/lib/rules/unicode-bom.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Require or disallow Unicode byte order mark (BOM)", recommended: false, - url: "https://eslint.org/docs/rules/unicode-bom" + url: "https://eslint.org/docs/latest/rules/unicode-bom" }, fixable: "whitespace", @@ -42,7 +42,7 @@ module.exports = { Program: function checkUnicodeBOM(node) { - const sourceCode = context.getSourceCode(), + const sourceCode = context.sourceCode, location = { column: 0, line: 1 }, requireBOM = context.options[0] || "never"; diff --git a/eslint/lib/rules/use-isnan.js b/eslint/lib/rules/use-isnan.js index 219d695..21dc395 100644 --- a/eslint/lib/rules/use-isnan.js +++ b/eslint/lib/rules/use-isnan.js @@ -39,7 +39,7 @@ module.exports = { docs: { description: "Require calls to `isNaN()` when checking for `NaN`", recommended: true, - url: "https://eslint.org/docs/rules/use-isnan" + url: "https://eslint.org/docs/latest/rules/use-isnan" }, schema: [ diff --git a/eslint/lib/rules/utils/regular-expressions.js b/eslint/lib/rules/utils/regular-expressions.js new file mode 100644 index 0000000..234a1cb --- /dev/null +++ b/eslint/lib/rules/utils/regular-expressions.js @@ -0,0 +1,42 @@ +/** + * @fileoverview Common utils for regular expressions. + * @author Josh Goldberg + * @author Toru Nagashima + */ + +"use strict"; + +const { RegExpValidator } = require("@eslint-community/regexpp"); + +const REGEXPP_LATEST_ECMA_VERSION = 2022; + +/** + * Checks if the given regular expression pattern would be valid with the `u` flag. + * @param {number} ecmaVersion ECMAScript version to parse in. + * @param {string} pattern The regular expression pattern to verify. + * @returns {boolean} `true` if the pattern would be valid with the `u` flag. + * `false` if the pattern would be invalid with the `u` flag or the configured + * ecmaVersion doesn't support the `u` flag. + */ +function isValidWithUnicodeFlag(ecmaVersion, pattern) { + if (ecmaVersion <= 5) { // ecmaVersion <= 5 doesn't support the 'u' flag + return false; + } + + const validator = new RegExpValidator({ + ecmaVersion: Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION) + }); + + try { + validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true); + } catch { + return false; + } + + return true; +} + +module.exports = { + isValidWithUnicodeFlag, + REGEXPP_LATEST_ECMA_VERSION +}; diff --git a/eslint/lib/rules/valid-jsdoc.js b/eslint/lib/rules/valid-jsdoc.js index 25be39f..919975f 100644 --- a/eslint/lib/rules/valid-jsdoc.js +++ b/eslint/lib/rules/valid-jsdoc.js @@ -23,7 +23,7 @@ module.exports = { docs: { description: "Enforce valid JSDoc comments", recommended: false, - url: "https://eslint.org/docs/rules/valid-jsdoc" + url: "https://eslint.org/docs/latest/rules/valid-jsdoc" }, schema: [ @@ -96,7 +96,7 @@ module.exports = { const options = context.options[0] || {}, prefer = options.prefer || {}, - sourceCode = context.getSourceCode(), + sourceCode = context.sourceCode, // these both default to true, so you have to explicitly make them false requireReturn = options.requireReturn !== false, diff --git a/eslint/lib/rules/valid-typeof.js b/eslint/lib/rules/valid-typeof.js index 908d572..82af130 100644 --- a/eslint/lib/rules/valid-typeof.js +++ b/eslint/lib/rules/valid-typeof.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Enforce comparing `typeof` expressions against valid strings", recommended: true, - url: "https://eslint.org/docs/rules/valid-typeof" + url: "https://eslint.org/docs/latest/rules/valid-typeof" }, hasSuggestions: true, @@ -44,7 +44,7 @@ module.exports = { const VALID_TYPES = new Set(["symbol", "undefined", "object", "boolean", "number", "string", "function", "bigint"]), OPERATORS = new Set(["==", "===", "!=", "!=="]); - + const sourceCode = context.sourceCode; const requireStringLiterals = context.options[0] && context.options[0].requireStringLiterals; let globalScope; @@ -77,13 +77,13 @@ module.exports = { return { - Program() { - globalScope = context.getScope(); + Program(node) { + globalScope = sourceCode.getScope(node); }, UnaryExpression(node) { if (isTypeofExpression(node)) { - const parent = context.getAncestors().pop(); + const { parent } = node; if (parent.type === "BinaryExpression" && OPERATORS.has(parent.operator)) { const sibling = parent.left === node ? parent.right : parent.left; diff --git a/eslint/lib/rules/vars-on-top.js b/eslint/lib/rules/vars-on-top.js index 99e2b4a..81f5d62 100644 --- a/eslint/lib/rules/vars-on-top.js +++ b/eslint/lib/rules/vars-on-top.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Require `var` declarations be placed at the top of their containing scope", recommended: false, - url: "https://eslint.org/docs/rules/vars-on-top" + url: "https://eslint.org/docs/latest/rules/vars-on-top" }, schema: [], diff --git a/eslint/lib/rules/wrap-iife.js b/eslint/lib/rules/wrap-iife.js index 4c2c927..4c448fa 100644 --- a/eslint/lib/rules/wrap-iife.js +++ b/eslint/lib/rules/wrap-iife.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const eslintUtils = require("eslint-utils"); +const eslintUtils = require("@eslint-community/eslint-utils"); //---------------------------------------------------------------------- // Helpers @@ -45,7 +45,7 @@ module.exports = { docs: { description: "Require parentheses around immediate `function` invocations", recommended: false, - url: "https://eslint.org/docs/rules/wrap-iife" + url: "https://eslint.org/docs/latest/rules/wrap-iife" }, schema: [ @@ -77,7 +77,7 @@ module.exports = { const style = context.options[0] || "outside"; const includeFunctionPrototypeMethods = context.options[1] && context.options[1].functionPrototypeMethods; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Check if the node is wrapped in any (). All parens count: grouping parens and parens for constructs such as if() diff --git a/eslint/lib/rules/wrap-regex.js b/eslint/lib/rules/wrap-regex.js index b24d360..8166c25 100644 --- a/eslint/lib/rules/wrap-regex.js +++ b/eslint/lib/rules/wrap-regex.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Require parenthesis around regex literals", recommended: false, - url: "https://eslint.org/docs/rules/wrap-regex" + url: "https://eslint.org/docs/latest/rules/wrap-regex" }, schema: [], @@ -29,7 +29,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { @@ -40,10 +40,9 @@ module.exports = { if (nodeType === "RegularExpression") { const beforeToken = sourceCode.getTokenBefore(node); const afterToken = sourceCode.getTokenAfter(node); - const ancestors = context.getAncestors(); - const grandparent = ancestors[ancestors.length - 1]; + const { parent } = node; - if (grandparent.type === "MemberExpression" && grandparent.object === node && + if (parent.type === "MemberExpression" && parent.object === node && !(beforeToken && beforeToken.value === "(" && afterToken && afterToken.value === ")")) { context.report({ node, diff --git a/eslint/lib/rules/yield-star-spacing.js b/eslint/lib/rules/yield-star-spacing.js index c2e07aa..9f9d918 100644 --- a/eslint/lib/rules/yield-star-spacing.js +++ b/eslint/lib/rules/yield-star-spacing.js @@ -17,7 +17,7 @@ module.exports = { docs: { description: "Require or disallow spacing around the `*` in `yield*` expressions", recommended: false, - url: "https://eslint.org/docs/rules/yield-star-spacing" + url: "https://eslint.org/docs/latest/rules/yield-star-spacing" }, fixable: "whitespace", @@ -48,7 +48,7 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const mode = (function(option) { if (!option || typeof option === "string") { diff --git a/eslint/lib/rules/yoda.js b/eslint/lib/rules/yoda.js index eb9a32a..60a6ad2 100644 --- a/eslint/lib/rules/yoda.js +++ b/eslint/lib/rules/yoda.js @@ -123,7 +123,7 @@ module.exports = { docs: { description: 'Require or disallow "Yoda" conditions', recommended: false, - url: "https://eslint.org/docs/rules/yoda" + url: "https://eslint.org/docs/latest/rules/yoda" }, schema: [ @@ -162,7 +162,7 @@ module.exports = { const onlyEquality = context.options[1] && context.options[1].onlyEquality; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; /** * Determines whether node represents a range test. @@ -343,7 +343,7 @@ module.exports = { ) && !(!isEqualityOperator(node.operator) && onlyEquality) && isComparisonOperator(node.operator) && - !(exceptRange && isRangeTest(context.getAncestors().pop())) + !(exceptRange && isRangeTest(node.parent)) ) { context.report({ node, diff --git a/eslint/lib/shared/directives.js b/eslint/lib/shared/directives.js new file mode 100644 index 0000000..ff67b00 --- /dev/null +++ b/eslint/lib/shared/directives.js @@ -0,0 +1,15 @@ +/** + * @fileoverview Common utils for directives. + * + * This file contains only shared items for directives. + * If you make a utility for rules, please see `../rules/utils/ast-utils.js`. + * + * @author gfyoung + */ +"use strict"; + +const directivesPattern = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u; + +module.exports = { + directivesPattern +}; diff --git a/eslint/lib/shared/runtime-info.js b/eslint/lib/shared/runtime-info.js index 56c0898..b99ad10 100644 --- a/eslint/lib/shared/runtime-info.js +++ b/eslint/lib/shared/runtime-info.js @@ -97,7 +97,7 @@ function environment() { */ function getNpmPackageVersion(pkg, { global = false } = {}) { const npmBinArgs = ["bin", "-g"]; - const npmLsArgs = ["ls", "--depth=0", "--json", "eslint"]; + const npmLsArgs = ["ls", "--depth=0", "--json", pkg]; if (global) { npmLsArgs.push("-g"); diff --git a/eslint/lib/shared/string-utils.js b/eslint/lib/shared/string-utils.js index e4a55d7..ed0781e 100644 --- a/eslint/lib/shared/string-utils.js +++ b/eslint/lib/shared/string-utils.js @@ -5,6 +5,26 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const Graphemer = require("graphemer").default; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +// eslint-disable-next-line no-control-regex -- intentionally including control characters +const ASCII_REGEX = /^[\u0000-\u007f]*$/u; + +/** @type {Graphemer | undefined} */ +let splitter; + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + /** * Converts the first letter of a string to uppercase. * @param {string} string The string to operate on @@ -17,6 +37,24 @@ function upperCaseFirst(string) { return string[0].toUpperCase() + string.slice(1); } +/** + * Counts graphemes in a given string. + * @param {string} value A string to count graphemes. + * @returns {number} The number of graphemes in `value`. + */ +function getGraphemeCount(value) { + if (ASCII_REGEX.test(value)) { + return value.length; + } + + if (!splitter) { + splitter = new Graphemer(); + } + + return splitter.countGraphemes(value); +} + module.exports = { - upperCaseFirst + upperCaseFirst, + getGraphemeCount }; diff --git a/eslint/lib/shared/traverser.js b/eslint/lib/shared/traverser.js index be0ed59..38b4e21 100644 --- a/eslint/lib/shared/traverser.js +++ b/eslint/lib/shared/traverser.js @@ -74,7 +74,7 @@ class Traverser { } /** - * Gives a a copy of the ancestor nodes. + * Gives 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 60f9f1d..5c10462 100644 --- a/eslint/lib/shared/types.js +++ b/eslint/lib/shared/types.js @@ -96,10 +96,12 @@ module.exports = {}; * @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 {boolean} [fatal] If `true` then this is a fatal error. * @property {{range:[number,number], text:string}} [fix] Information for autofix. * @property {number|undefined} line The 1-based line number. * @property {string} message The error message. + * @property {string} [messageId] The ID of the message in the rule's meta. + * @property {(string|null)} nodeType Type of node * @property {string|null} ruleId The ID of the rule which makes this message. * @property {0|1|2} severity The severity of this message. * @property {Array<{desc?: string, messageId?: string, fix: {range: [number, number], text: string}}>} [suggestions] Information for suggestions. @@ -110,10 +112,12 @@ module.exports = {}; * @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 {boolean} [fatal] If `true` then this is a fatal error. * @property {{range:[number,number], text:string}} [fix] Information for autofix. * @property {number|undefined} line The 1-based line number. * @property {string} message The error message. + * @property {string} [messageId] The ID of the message in the rule's meta. + * @property {(string|null)} nodeType Type of node * @property {string|null} ruleId The ID of the rule which makes this message. * @property {0|1|2} severity The severity of this message. * @property {Array<{kind: string, justification: string}>} suppressions The suppression info. @@ -190,10 +194,23 @@ module.exports = {}; * @property {DeprecatedRuleInfo[]} usedDeprecatedRules The list of used deprecated rules. */ +/** + * Information provided when the maximum warning threshold is exceeded. + * @typedef {Object} MaxWarningsExceeded + * @property {number} maxWarnings Number of warnings to trigger nonzero exit code. + * @property {number} foundWarnings Number of warnings found while linting. + */ + +/** + * Metadata about results for formatters. + * @typedef {Object} ResultsMeta + * @property {MaxWarningsExceeded} [maxWarningsExceeded] Present if the maxWarnings threshold was exceeded. + */ + /** * A formatter function. * @callback FormatterFunction * @param {LintResult[]} results The list of linting results. - * @param {{cwd: string, rulesMeta: Record}} [context] A context object. + * @param {{cwd: string, maxWarningsExceeded?: MaxWarningsExceeded, rulesMeta: Record}} [context] A context object. * @returns {string | Promise} Formatted text. */ diff --git a/eslint/lib/source-code/source-code.js b/eslint/lib/source-code/source-code.js index 49b2820..07c0d29 100644 --- a/eslint/lib/source-code/source-code.js +++ b/eslint/lib/source-code/source-code.js @@ -9,11 +9,17 @@ //------------------------------------------------------------------------------ const - { isCommentToken } = require("eslint-utils"), + { isCommentToken } = require("@eslint-community/eslint-utils"), TokenStore = require("./token-store"), astUtils = require("../shared/ast-utils"), Traverser = require("../shared/traverser"); +//------------------------------------------------------------------------------ +// Type Definitions +//------------------------------------------------------------------------------ + +/** @typedef {import("eslint-scope").Variable} Variable */ + //------------------------------------------------------------------------------ // Private //------------------------------------------------------------------------------ @@ -143,6 +149,8 @@ function isSpaceBetween(sourceCode, first, second, checkInsideOfJSXText) { // Public Interface //------------------------------------------------------------------------------ +const caches = Symbol("caches"); + /** * Represents parsed source code. */ @@ -175,6 +183,13 @@ class SourceCode extends TokenStore { validate(ast); super(ast.tokens, ast.comments); + /** + * General purpose caching for the class. + */ + this[caches] = new Map([ + ["scopes", new WeakMap()] + ]); + /** * The flag to indicate that the source code has Unicode BOM. * @type {boolean} @@ -588,6 +603,127 @@ class SourceCode extends TokenStore { return positionIndex; } + + /** + * Gets the scope for the given node + * @param {ASTNode} currentNode The node to get the scope of + * @returns {eslint-scope.Scope} The scope information for this node + * @throws {TypeError} If the `currentNode` argument is missing. + */ + getScope(currentNode) { + + if (!currentNode) { + throw new TypeError("Missing required argument: node."); + } + + // check cache first + const cache = this[caches].get("scopes"); + const cachedScope = cache.get(currentNode); + + if (cachedScope) { + return cachedScope; + } + + // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope. + const inner = currentNode.type !== "Program"; + + for (let node = currentNode; node; node = node.parent) { + const scope = this.scopeManager.acquire(node, inner); + + if (scope) { + if (scope.type === "function-expression-name") { + cache.set(currentNode, scope.childScopes[0]); + return scope.childScopes[0]; + } + + cache.set(currentNode, scope); + return scope; + } + } + + cache.set(currentNode, this.scopeManager.scopes[0]); + return this.scopeManager.scopes[0]; + } + + /** + * Get the variables that `node` defines. + * This is a convenience method that passes through + * to the same method on the `scopeManager`. + * @param {ASTNode} node The node for which the variables are obtained. + * @returns {Array} An array of variable nodes representing + * the variables that `node` defines. + */ + getDeclaredVariables(node) { + return this.scopeManager.getDeclaredVariables(node); + } + + /* eslint-disable class-methods-use-this -- node is owned by SourceCode */ + /** + * Gets all the ancestors of a given node + * @param {ASTNode} node The node + * @returns {Array} All the ancestor nodes in the AST, not including the provided node, starting + * from the root node at index 0 and going inwards to the parent node. + * @throws {TypeError} When `node` is missing. + */ + getAncestors(node) { + + if (!node) { + throw new TypeError("Missing required argument: node."); + } + + const ancestorsStartingAtParent = []; + + for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) { + ancestorsStartingAtParent.push(ancestor); + } + + return ancestorsStartingAtParent.reverse(); + } + /* eslint-enable class-methods-use-this -- node is owned by SourceCode */ + + /** + * Marks a variable as used in the current scope + * @param {string} name The name of the variable to mark as used. + * @param {ASTNode} [refNode] The closest node to the variable reference. + * @returns {boolean} True if the variable was found and marked as used, false if not. + */ + markVariableAsUsed(name, refNode = this.ast) { + + const currentScope = this.getScope(refNode); + let initialScope = currentScope; + + /* + * When we are in an ESM or CommonJS module, we need to start searching + * from the top-level scope, not the global scope. For ESM the top-level + * scope is the module scope; for CommonJS the top-level scope is the + * outer function scope. + * + * Without this check, we might miss a variable declared with `var` at + * the top-level because it won't exist in the global scope. + */ + if ( + currentScope.type === "global" && + currentScope.childScopes.length > 0 && + + // top-level scopes refer to a `Program` node + currentScope.childScopes[0].block === this.ast + ) { + initialScope = currentScope.childScopes[0]; + } + + for (let scope = initialScope; scope; scope = scope.upper) { + const variable = scope.variables.find(scopeVar => scopeVar.name === name); + + if (variable) { + variable.eslintUsed = true; + return true; + } + } + + return false; + } + + } module.exports = SourceCode; diff --git a/eslint/lib/source-code/token-store/index.js b/eslint/lib/source-code/token-store/index.js index 25db8a4..46a96b2 100644 --- a/eslint/lib/source-code/token-store/index.js +++ b/eslint/lib/source-code/token-store/index.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const assert = require("assert"); -const { isCommentToken } = require("eslint-utils"); +const { isCommentToken } = require("@eslint-community/eslint-utils"); const cursors = require("./cursors"); const ForwardTokenCursor = require("./forward-token-cursor"); const PaddedTokenCursor = require("./padded-token-cursor"); diff --git a/eslint/lib/source-code/token-store/utils.js b/eslint/lib/source-code/token-store/utils.js index a2bd77d..3e01470 100644 --- a/eslint/lib/source-code/token-store/utils.js +++ b/eslint/lib/source-code/token-store/utils.js @@ -4,20 +4,6 @@ */ "use strict"; -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -/** - * Gets `token.range[0]` from the given token. - * @param {Node|Token|Comment} token The token to get. - * @returns {number} The start location. - * @private - */ -function getStartLocation(token) { - return token.range[0]; -} - //------------------------------------------------------------------------------ // Exports //------------------------------------------------------------------------------ @@ -30,9 +16,28 @@ function getStartLocation(token) { * @returns {number} The found index or `tokens.length`. */ exports.search = function search(tokens, location) { - const index = tokens.findIndex(el => location <= getStartLocation(el)); + for (let minIndex = 0, maxIndex = tokens.length - 1; minIndex <= maxIndex;) { - return index === -1 ? tokens.length : index; + /* + * Calculate the index in the middle between minIndex and maxIndex. + * `| 0` is used to round a fractional value down to the nearest integer: this is similar to + * using `Math.trunc()` or `Math.floor()`, but performance tests have shown this method to + * be faster. + */ + const index = (minIndex + maxIndex) / 2 | 0; + const token = tokens[index]; + const tokenStartLocation = token.range[0]; + + if (location <= tokenStartLocation) { + if (index === minIndex) { + return index; + } + maxIndex = index; + } else { + minIndex = index + 1; + } + } + return tokens.length; }; /** @@ -49,13 +54,18 @@ exports.getFirstIndex = function getFirstIndex(tokens, indexMap, startLoc) { } if ((startLoc - 1) in indexMap) { const index = indexMap[startLoc - 1]; - const token = (index >= 0 && index < tokens.length) ? tokens[index] : null; + const token = tokens[index]; + + // If the mapped index is out of bounds, the returned cursor index will point after the end of the tokens array. + if (!token) { + return tokens.length; + } /* * For the map of "comment's location -> token's index", it points the next token of a comment. * In that case, +1 is unnecessary. */ - if (token && token.range[0] >= startLoc) { + if (token.range[0] >= startLoc) { return index; } return index + 1; @@ -77,13 +87,18 @@ exports.getLastIndex = function getLastIndex(tokens, indexMap, endLoc) { } if ((endLoc - 1) in indexMap) { const index = indexMap[endLoc - 1]; - const token = (index >= 0 && index < tokens.length) ? tokens[index] : null; + const token = tokens[index]; + + // If the mapped index is out of bounds, the returned cursor index will point before the end of the tokens array. + if (!token) { + return tokens.length - 1; + } /* * For the map of "comment's location -> token's index", it points the next token of a comment. * In that case, -1 is necessary. */ - if (token && token.range[1] > endLoc) { + if (token.range[1] > endLoc) { return index - 1; } return index; diff --git a/eslint/lib/unsupported-api.js b/eslint/lib/unsupported-api.js index c1daf54..b688608 100644 --- a/eslint/lib/unsupported-api.js +++ b/eslint/lib/unsupported-api.js @@ -12,7 +12,7 @@ //----------------------------------------------------------------------------- const { FileEnumerator } = require("./cli-engine/file-enumerator"); -const { FlatESLint } = require("./eslint/flat-eslint"); +const { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint"); const FlatRuleTester = require("./rule-tester/flat-rule-tester"); //----------------------------------------------------------------------------- @@ -22,6 +22,7 @@ const FlatRuleTester = require("./rule-tester/flat-rule-tester"); module.exports = { builtinRules: require("./rules"), FlatESLint, + shouldUseFlatConfig, FlatRuleTester, FileEnumerator }; diff --git a/eslint/messages/invalid-rule-options.js b/eslint/messages/invalid-rule-options.js new file mode 100644 index 0000000..9a8acc9 --- /dev/null +++ b/eslint/messages/invalid-rule-options.js @@ -0,0 +1,17 @@ +"use strict"; + +const { stringifyValueForError } = require("./shared"); + +module.exports = function({ ruleId, value }) { + return ` +Configuration for rule "${ruleId}" is invalid. Each rule must have a severity ("off", 0, "warn", 1, "error", or 2) and may be followed by additional options for the rule. + +You passed '${stringifyValueForError(value, 4)}', which doesn't contain a valid severity. + +If you're attempting to configure rule options, perhaps you meant: + + "${ruleId}": ["error", ${stringifyValueForError(value, 8)}] + +See https://eslint.org/docs/latest/use/configure/rules#using-configuration-files for configuring rules. +`.trimStart(); +}; diff --git a/eslint/messages/invalid-rule-severity.js b/eslint/messages/invalid-rule-severity.js new file mode 100644 index 0000000..3f13183 --- /dev/null +++ b/eslint/messages/invalid-rule-severity.js @@ -0,0 +1,13 @@ +"use strict"; + +const { stringifyValueForError } = require("./shared"); + +module.exports = function({ ruleId, value }) { + return ` +Configuration for rule "${ruleId}" is invalid. Expected severity of "off", 0, "warn", 1, "error", or 2. + +You passed '${stringifyValueForError(value, 4)}'. + +See https://eslint.org/docs/latest/use/configure/rules#using-configuration-files for configuring rules. +`.trimStart(); +}; diff --git a/eslint/messages/no-config-found.js b/eslint/messages/no-config-found.js index 9860410..21cf549 100644 --- a/eslint/messages/no-config-found.js +++ b/eslint/messages/no-config-found.js @@ -10,6 +10,6 @@ ESLint couldn't find a configuration file. To set up a configuration file for th ESLint looked for configuration files in ${directoryPath} and its ancestors. If it found none, it then looked in your home directory. -If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://eslint.org/chat/help +If you think you already have a configuration file or if you need more help, please stop by the ESLint Discord server: https://eslint.org/chat `.trimStart(); }; diff --git a/eslint/messages/print-config-with-directory-path.js b/eslint/messages/print-config-with-directory-path.js index f65bdaa..4559c8d 100644 --- a/eslint/messages/print-config-with-directory-path.js +++ b/eslint/messages/print-config-with-directory-path.js @@ -3,6 +3,6 @@ module.exports = function() { return ` The '--print-config' CLI option requires a path to a source code file rather than a directory. -See also: https://eslint.org/docs/user-guide/command-line-interface#--print-config +See also: https://eslint.org/docs/latest/use/command-line-interface#--print-config `.trimStart(); }; diff --git a/eslint/messages/shared.js b/eslint/messages/shared.js new file mode 100644 index 0000000..8c6e9b9 --- /dev/null +++ b/eslint/messages/shared.js @@ -0,0 +1,18 @@ +/** + * @fileoverview Shared utilities for error messages. + * @author Josh Goldberg + */ + +"use strict"; + +/** + * Converts a value to a string that may be printed in errors. + * @param {any} value The invalid value. + * @param {number} indentation How many spaces to indent + * @returns {string} The value, stringified. + */ +function stringifyValueForError(value, indentation) { + return value ? JSON.stringify(value, null, 4).replace(/\n/gu, `\n${" ".repeat(indentation)}`) : `${value}`; +} + +module.exports = { stringifyValueForError }; diff --git a/eslint/package.json b/eslint/package.json index d002a60..ae6e8b3 100644 --- a/eslint/package.json +++ b/eslint/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.23.1", + "version": "8.41.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { @@ -13,22 +13,23 @@ "./use-at-your-own-risk": "./lib/unsupported-api.js" }, "scripts": { + "build:docs:update-links": "node tools/fetch-docs-links.js", + "build:site": "node Makefile.js gensite", + "build:webpack": "node Makefile.js webpack", + "build:readme": "node tools/update-readme.js", + "lint": "node Makefile.js lint", + "lint:docs:js": "node Makefile.js lintDocsJS", + "lint:fix": "node Makefile.js lint -- fix", + "lint:fix:docs:js": "node Makefile.js lintDocsJS -- fix", + "release:generate:alpha": "node Makefile.js generatePrerelease -- alpha", + "release:generate:beta": "node Makefile.js generatePrerelease -- beta", + "release:generate:latest": "node Makefile.js generateRelease", + "release:generate:rc": "node Makefile.js generatePrerelease -- rc", + "release:publish": "node Makefile.js publishRelease", "test": "node Makefile.js test", "test:cli": "mocha", - "lint": "node Makefile.js lint", - "lint:docsjs": "node Makefile.js lintDocsJS", - "fix": "node Makefile.js lint -- fix", - "fix:docsjs": "node Makefile.js lintDocsJS -- fix", - "fuzz": "node Makefile.js fuzz", - "generate-release": "node Makefile.js generateRelease", - "generate-alpharelease": "node Makefile.js generatePrerelease -- alpha", - "generate-betarelease": "node Makefile.js generatePrerelease -- beta", - "generate-rcrelease": "node Makefile.js generatePrerelease -- rc", - "publish-release": "node Makefile.js publishRelease", - "gensite": "node Makefile.js gensite", - "webpack": "node Makefile.js webpack", - "perf": "node Makefile.js perf", - "docs:update-links": "node tools/fetch-docs-links.js" + "test:fuzz": "node Makefile.js fuzz", + "test:performance": "node Makefile.js perf" }, "gitHooks": { "pre-commit": "lint-staged" @@ -36,6 +37,10 @@ "lint-staged": { "*.js": "eslint --fix", "*.md": "markdownlint --fix", + "lib/rules/*.js": [ + "node tools/update-eslint-all.js", + "git add packages/js/src/configs/eslint-all.js" + ], "docs/src/rules/*.md": [ "node tools/fetch-docs-links.js", "git add docs/src/_data/further_reading_links.json" @@ -55,34 +60,35 @@ "homepage": "https://eslint.org", "bugs": "https://github.com/eslint/eslint/issues/", "dependencies": { - "@eslint/eslintrc": "^1.3.2", - "@humanwhocodes/config-array": "^0.10.4", - "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.41.0", + "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", - "grapheme-splitter": "^1.0.4", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-sdsl": "^4.1.4", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", @@ -90,7 +96,6 @@ "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" @@ -121,7 +126,6 @@ "glob": "^7.1.6", "got": "^11.8.3", "gray-matter": "^4.0.3", - "jsdoc": "^3.5.5", "karma": "^6.1.1", "karma-chrome-launcher": "^3.1.0", "karma-mocha": "^2.0.1", diff --git a/eslint/packages/js/LICENSE b/eslint/packages/js/LICENSE new file mode 100644 index 0000000..b607bb3 --- /dev/null +++ b/eslint/packages/js/LICENSE @@ -0,0 +1,19 @@ +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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/eslint/packages/js/README.md b/eslint/packages/js/README.md new file mode 100644 index 0000000..a8121c3 --- /dev/null +++ b/eslint/packages/js/README.md @@ -0,0 +1,57 @@ +[![npm version](https://img.shields.io/npm/v/@eslint/js.svg)](https://www.npmjs.com/package/@eslint/js) + +# ESLint JavaScript Plugin + +[Website](https://eslint.org) | [Configure ESLint](https://eslint.org/docs/latest/use/configure) | [Rules](https://eslint.org/docs/rules/) | [Contributing](https://eslint.org/docs/latest/contribute) | [Twitter](https://twitter.com/geteslint) | [Chatroom](https://eslint.org/chat) + +The beginnings of separating out JavaScript-specific functionality from ESLint. + +Right now, this plugin contains two configurations: + +* `recommended` - enables the rules recommended by the ESLint team (the replacement for `"eslint:recommended"`) +* `all` - enables all ESLint rules (the replacement for `"eslint:all"`) + +## Installation + +```shell +npm install @eslint/js -D +``` + +## Usage + +Use in your `eslint.config.js` file anytime you want to extend one of the configs: + +```js +import js from "@eslint/js"; + +export default [ + + // apply recommended rules to JS files + { + files: ["**/*.js"], + rules: js.configs.recommended.rules + }, + + // apply recommended rules to JS files with an override + { + files: ["**/*.js"], + rules: { + ...js.configs.recommended.rules, + "no-unused-vars": "warn" + } + }, + + // apply all rules to JS files + { + files: ["**/*.js"], + rules: { + ...js.configs.all.rules, + "no-unused-vars": "warn" + } + } +] +``` + +## License + +MIT diff --git a/eslint/packages/js/package.json b/eslint/packages/js/package.json new file mode 100644 index 0000000..5d65d79 --- /dev/null +++ b/eslint/packages/js/package.json @@ -0,0 +1,31 @@ +{ + "name": "@eslint/js", + "version": "8.41.0", + "description": "ESLint JavaScript language implementation", + "main": "./src/index.js", + "scripts": {}, + "files": [ + "LICENSE", + "README.md", + "src" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/eslint/eslint.git", + "directory": "packages/js" + }, + "homepage": "https://eslint.org", + "bugs": "https://github.com/eslint/eslint/issues/", + "keywords": [ + "javascript", + "eslint-plugin", + "eslint" + ], + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } +} diff --git a/eslint/packages/js/src/index.js b/eslint/packages/js/src/index.js new file mode 100644 index 0000000..0d4be48 --- /dev/null +++ b/eslint/packages/js/src/index.js @@ -0,0 +1,17 @@ +/** + * @fileoverview Main package entrypoint. + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + configs: { + all: require("./configs/eslint-all"), + recommended: require("./configs/eslint-recommended") + } +}; diff --git a/eslint/templates/blogpost.md.ejs b/eslint/templates/blogpost.md.ejs index 2d73ee1..efb1161 100644 --- a/eslint/templates/blogpost.md.ejs +++ b/eslint/templates/blogpost.md.ejs @@ -50,7 +50,7 @@ npm i eslint@<%- version %> --save-dev ### Migration Guide -As there are a lot of changes, we've created a [migration guide](/docs/<%- prereleaseMajorVersion %>/user-guide/migrating-to-<%- prereleaseMajorVersion %>) describing the changes in great detail along with the steps you should take to address them. We expect that most users should be able to upgrade without any build changes, but the migration guide should be a useful resource if you encounter problems. +As there are a lot of changes, we've created a [migration guide](/docs/<%- prereleaseMajorVersion %>/use/migrating-to-<%- prereleaseMajorVersion %>) describing the changes in great detail along with the steps you should take to address them. We expect that most users should be able to upgrade without any build changes, but the migration guide should be a useful resource if you encounter problems. <% } %> diff --git a/eslint/templates/bug-report.md b/eslint/templates/bug-report.md index c8c552d..d7a7a40 100644 --- a/eslint/templates/bug-report.md +++ b/eslint/templates/bug-report.md @@ -1,10 +1,19 @@ -**Tell us about your environment:** +**Tell us about your environment (`npx eslint --env-info`):** -* **ESLint Version:** -* **Node Version:** -* **npm Version:** +* **Node version:** +* **npm version:** +* **Local ESLint version:** +* **Global ESLint version:** +* **Operating System:** -**What parser (default, `@babel/eslint-parser`, `@typescript-eslint/parser`, etc.) are you using?** +**What parser are you using (place an "X" next to just one item)?** + +[ ] `Default (Espree)` +[ ] `@typescript-eslint/parser` +[ ] `@babel/eslint-parser` +[ ] `vue-eslint-parser` +[ ] `@angular-eslint/template-parser` +[ ] `Other` **Please show your full configuration:** diff --git a/eslint/templates/formatter-examples.md.ejs b/eslint/templates/formatter-examples.md.ejs index 6dae9d7..20f46f2 100644 --- a/eslint/templates/formatter-examples.md.ejs +++ b/eslint/templates/formatter-examples.md.ejs @@ -1,17 +1,16 @@ --- -title: Formatters -layout: doc +title: Formatters Reference eleventyNavigation: key: formatters - parent: user guide - title: Formatters - order: 5 + parent: use eslint + title: Formatters Reference + order: 6 edit_link: https://github.com/eslint/eslint/edit/main/templates/formatter-examples.md.ejs --- 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 json` uses the `json` formatter. +You can specify a formatter using the `--format` or `-f` flag in the CLI. For example, `--format json` uses the `json` formatter. The built-in formatter options are: @@ -21,9 +20,9 @@ The built-in formatter options are: ## Example Source -Examples of each formatter were created from linting `fullOfProblems.js` using the `.eslintrc` configuration shown below. +Examples of each formatter were created from linting `fullOfProblems.js` using the `.eslintrc.json` configuration shown below. -### `fullOfProblems.js` +`fullOfProblems.js`: ```js function addOne(i) { @@ -35,7 +34,7 @@ function addOne(i) { }; ``` -### `.eslintrc`: +`.eslintrc.json`: ```json { @@ -50,17 +49,26 @@ function addOne(i) { } ``` -## Output Examples +Tests the formatters with the CLI: + +```shell +npx eslint --format fullOfProblems.js +``` + +## Built-In Formatter Options <% Object.keys(formatterResults).forEach(function(formatterName) { -%> ### <%= formatterName %> -<% if (formatterName !== "html") { -%> +<%= formatterResults[formatterName].description %> + +Example output: + +<% if (formatterName !== "html") { -%> ```text -<%= formatterResults[formatterName].result %> +<%- formatterResults[formatterName].result %> ``` <% } else {-%> - <% } -%> <% }) -%> diff --git a/eslint/templates/rule-change-proposal.md b/eslint/templates/rule-change-proposal.md index a4a8e17..a4d4acb 100644 --- a/eslint/templates/rule-change-proposal.md +++ b/eslint/templates/rule-change-proposal.md @@ -1,8 +1,17 @@ **What rule do you want to change?** -**Does this change cause the rule to produce more or fewer warnings?** +**What change do you want to make (place an "X" next to just one item)?** -**How will the change be implemented? (New option, new default behavior, etc.)?** +[ ] Generate more warnings +[ ] Generate fewer warnings +[ ] Implement autofix +[ ] Implement suggestions + +**How will the change be implemented (place an "X" next to just one item)?** + +[ ] A new option +[ ] A new default behavior +[ ] Other **Please provide some example code that this change will affect:** diff --git a/eslint/templates/rule-proposal.md b/eslint/templates/rule-proposal.md index 2adf931..cc1b011 100644 --- a/eslint/templates/rule-proposal.md +++ b/eslint/templates/rule-proposal.md @@ -1,13 +1,14 @@ -**Please describe what the rule should do:** +**What should the new rule do?** + +**What new ECMAScript feature does this rule relate to? Note that we only accept new core rules related to new ECMAScript features.** **What category of rule is this (place an "X" next to just one item)?** -[ ] Enforces code style -[ ] Warns about a potential error +[ ] Warns about a potential problem [ ] Suggests an alternate way of doing something -[ ] Other (please specify:) +[ ] Enforces a formatting/stylistic preference -**Provide 2-3 code examples that this rule will warn about:** +**Please provide some example JavaScript code that this rule will warn about:** ```js diff --git a/eslint/tests/bench/large.js b/eslint/tests/bench/large.js index a3db1ac..26a1f9e 100644 --- a/eslint/tests/bench/large.js +++ b/eslint/tests/bench/large.js @@ -50947,7 +50947,7 @@ exports.debuglog = function(set) { /** - * Echos the value of a value. Trys to print the value out + * Echos the value of a value. Tries to print the value out * in the best way possible given the different types. * * @param {Object} obj The object to print out. @@ -52738,10 +52738,12 @@ module.exports=require('nr+AlQ'); /*! * JSHint, by JSHint Community. * - * This file (and this file only) is licensed under the same slightly modified - * MIT license that JSLint is. It stops evil-doers everywhere: + * This file (and this file only) was licensed under the same slightly modified + * MIT license that JSLint is. After a relicensing in 2020 this is now MIT License (Expat). + * Relicensing: https://jshint.com/relicensing-2020/ + * License-Url: https://github.com/jshint/jshint/blob/main/LICENSE * - * Copyright (c) 2002 Douglas Crockford (www.JSLint.com) + * Copyright 2012 Anton Kovalyov (http://jshint.com) * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the "Software"), @@ -52753,8 +52755,6 @@ module.exports=require('nr+AlQ'); * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * - * The Software shall be used for Good, not Evil. - * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE diff --git a/eslint/tests/fixtures/cli-engine/empty/.keep b/eslint/tests/fixtures/.eslintignore_empty similarity index 100% rename from eslint/tests/fixtures/cli-engine/empty/.keep rename to eslint/tests/fixtures/.eslintignore_empty diff --git a/eslint/tests/fixtures/cli-engine/eslint.config_with_ignores2.js b/eslint/tests/fixtures/cli-engine/eslint.config_with_ignores2.js new file mode 100644 index 0000000..0450eb6 --- /dev/null +++ b/eslint/tests/fixtures/cli-engine/eslint.config_with_ignores2.js @@ -0,0 +1,3 @@ +module.exports = [{ + ignores: ["**/fixtures/**"] +}]; diff --git a/eslint/tests/fixtures/cli/ignore-pattern-relative/.eslintrc.js b/eslint/tests/fixtures/cli/ignore-pattern-relative/.eslintrc.js new file mode 100644 index 0000000..cfa7c2d --- /dev/null +++ b/eslint/tests/fixtures/cli/ignore-pattern-relative/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + root: true +}; diff --git a/eslint/tests/fixtures/cli/ignore-pattern-relative/eslint.config.js b/eslint/tests/fixtures/cli/ignore-pattern-relative/eslint.config.js new file mode 100644 index 0000000..e0a30c5 --- /dev/null +++ b/eslint/tests/fixtures/cli/ignore-pattern-relative/eslint.config.js @@ -0,0 +1 @@ +module.exports = []; diff --git a/eslint/tests/fixtures/cli/ignore-pattern-relative/subdir/subsubdir/a.js b/eslint/tests/fixtures/cli/ignore-pattern-relative/subdir/subsubdir/a.js new file mode 100644 index 0000000..e69de29 diff --git a/eslint/tests/fixtures/dot-files/.a.js b/eslint/tests/fixtures/dot-files/.a.js new file mode 100644 index 0000000..e5f581a --- /dev/null +++ b/eslint/tests/fixtures/dot-files/.a.js @@ -0,0 +1 @@ +console.log("Running"); diff --git a/eslint/tests/fixtures/dot-files/.c.js b/eslint/tests/fixtures/dot-files/.c.js new file mode 100644 index 0000000..e5f581a --- /dev/null +++ b/eslint/tests/fixtures/dot-files/.c.js @@ -0,0 +1 @@ +console.log("Running"); diff --git a/eslint/tests/fixtures/dot-files/b.js b/eslint/tests/fixtures/dot-files/b.js new file mode 100644 index 0000000..e5f581a --- /dev/null +++ b/eslint/tests/fixtures/dot-files/b.js @@ -0,0 +1 @@ +console.log("Running"); diff --git a/eslint/tests/fixtures/dot-files/eslint.config.js b/eslint/tests/fixtures/dot-files/eslint.config.js new file mode 100644 index 0000000..e0b84a7 --- /dev/null +++ b/eslint/tests/fixtures/dot-files/eslint.config.js @@ -0,0 +1,8 @@ +module.exports = [ + { + files: ["*.js"] + }, + { + ignores: ["eslint.config.js"] + } +]; diff --git a/eslint/tests/fixtures/dots-in-files/a..b.js b/eslint/tests/fixtures/dots-in-files/a..b.js new file mode 100644 index 0000000..e5f581a --- /dev/null +++ b/eslint/tests/fixtures/dots-in-files/a..b.js @@ -0,0 +1 @@ +console.log("Running"); diff --git a/eslint/tests/fixtures/dots-in-files/eslint.config.js b/eslint/tests/fixtures/dots-in-files/eslint.config.js new file mode 100644 index 0000000..8c1da94 --- /dev/null +++ b/eslint/tests/fixtures/dots-in-files/eslint.config.js @@ -0,0 +1,8 @@ +module.exports = [ + { + files: ["a..b.js"] + }, + { + ignores: ["eslint.config.js"] + } +]; diff --git a/eslint/tests/fixtures/eslint.config_with_ignores.js b/eslint/tests/fixtures/eslint.config_with_ignores.js new file mode 100644 index 0000000..4640a3b --- /dev/null +++ b/eslint/tests/fixtures/eslint.config_with_ignores.js @@ -0,0 +1,8 @@ +const eslintConfig = require("./eslint.config.js"); + +module.exports = [ + eslintConfig, + { + ignores: ["**/*.json", "**/*.js"] + } +]; diff --git a/eslint/tests/fixtures/eslint.config_with_ignores2.js b/eslint/tests/fixtures/eslint.config_with_ignores2.js new file mode 100644 index 0000000..ec15bf4 --- /dev/null +++ b/eslint/tests/fixtures/eslint.config_with_ignores2.js @@ -0,0 +1,8 @@ +const eslintConfig = require("./eslint.config.js"); + +module.exports = [ + eslintConfig, + { + ignores: ["**/undef.js", "undef2.js", "**/undef3.js"] + } +]; diff --git a/eslint/tests/fixtures/eslint.config_with_rules.js b/eslint/tests/fixtures/eslint.config_with_rules.js new file mode 100644 index 0000000..6817217 --- /dev/null +++ b/eslint/tests/fixtures/eslint.config_with_rules.js @@ -0,0 +1,5 @@ +module.exports = [{ + rules: { + quotes: ["error", "single"] + } +}]; diff --git a/eslint/tests/fixtures/example-app/app.js b/eslint/tests/fixtures/example-app/app.js new file mode 100644 index 0000000..e5f581a --- /dev/null +++ b/eslint/tests/fixtures/example-app/app.js @@ -0,0 +1 @@ +console.log("Running"); diff --git a/eslint/tests/fixtures/example-app/eslint.config.js b/eslint/tests/fixtures/example-app/eslint.config.js new file mode 100644 index 0000000..e0a30c5 --- /dev/null +++ b/eslint/tests/fixtures/example-app/eslint.config.js @@ -0,0 +1 @@ +module.exports = []; diff --git a/eslint/tests/fixtures/example-app/subdir/util.js b/eslint/tests/fixtures/example-app/subdir/util.js new file mode 100644 index 0000000..f053ebf --- /dev/null +++ b/eslint/tests/fixtures/example-app/subdir/util.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/eslint/tests/fixtures/example-app2/eslint.config.js b/eslint/tests/fixtures/example-app2/eslint.config.js new file mode 100644 index 0000000..cfef568 --- /dev/null +++ b/eslint/tests/fixtures/example-app2/eslint.config.js @@ -0,0 +1,3 @@ +module.exports = [{ + files: ["subdir*/**/*.js"] +}]; diff --git a/eslint/tests/fixtures/example-app2/subdir1/a.js b/eslint/tests/fixtures/example-app2/subdir1/a.js new file mode 100644 index 0000000..e69de29 diff --git a/eslint/tests/fixtures/example-app2/subdir2/b.js b/eslint/tests/fixtures/example-app2/subdir2/b.js new file mode 100644 index 0000000..e69de29 diff --git a/eslint/tests/fixtures/ignores-directory/eslint.config.js b/eslint/tests/fixtures/ignores-directory/eslint.config.js new file mode 100644 index 0000000..25d0cef --- /dev/null +++ b/eslint/tests/fixtures/ignores-directory/eslint.config.js @@ -0,0 +1,3 @@ +module.exports = { + ignores: ["subdir/subsubdir"] +}; diff --git a/eslint/tests/fixtures/ignores-directory/subdir/subsubdir/a.js b/eslint/tests/fixtures/ignores-directory/subdir/subsubdir/a.js new file mode 100644 index 0000000..e69de29 diff --git a/eslint/tests/fixtures/ignores-relative/a.js b/eslint/tests/fixtures/ignores-relative/a.js new file mode 100644 index 0000000..e69de29 diff --git a/eslint/tests/fixtures/ignores-relative/eslint.config.js b/eslint/tests/fixtures/ignores-relative/eslint.config.js new file mode 100644 index 0000000..41032a9 --- /dev/null +++ b/eslint/tests/fixtures/ignores-relative/eslint.config.js @@ -0,0 +1,5 @@ +module.exports = [ + { + ignores: ["a.js"] + } +]; diff --git a/eslint/tests/fixtures/ignores-relative/subdir/a.js b/eslint/tests/fixtures/ignores-relative/subdir/a.js new file mode 100644 index 0000000..e69de29 diff --git a/eslint/tests/fixtures/ignores-self/eslint.config.js b/eslint/tests/fixtures/ignores-self/eslint.config.js new file mode 100644 index 0000000..35f93ee --- /dev/null +++ b/eslint/tests/fixtures/ignores-self/eslint.config.js @@ -0,0 +1,3 @@ +module.exports = { + ignores: ["**/ignores-self/**"] +}; diff --git a/eslint/tests/fixtures/ignores-subdirectory/eslint.config.js b/eslint/tests/fixtures/ignores-subdirectory/eslint.config.js new file mode 100644 index 0000000..2e1db61 --- /dev/null +++ b/eslint/tests/fixtures/ignores-subdirectory/eslint.config.js @@ -0,0 +1,3 @@ +module.exports = { + ignores: ["subdir"] +}; diff --git a/eslint/tests/fixtures/ignores-subdirectory/subdir/subsubdir/a.js b/eslint/tests/fixtures/ignores-subdirectory/subdir/subsubdir/a.js new file mode 100644 index 0000000..e69de29 diff --git a/eslint/tests/fixtures/parsers/all-comments-parser.js b/eslint/tests/fixtures/parsers/all-comments-parser.js new file mode 100644 index 0000000..595392a --- /dev/null +++ b/eslint/tests/fixtures/parsers/all-comments-parser.js @@ -0,0 +1,28 @@ +// Similar to the default parser, but considers leading and trailing comments to be part of the root node. +// Some custom parsers like @typescript-eslint/parser behave in this way. + +const espree = require("espree"); +exports.parse = function(code, options) { + const ast = espree.parse(code, options); + + if (ast.range && ast.comments && ast.comments.length > 0) { + const firstComment = ast.comments[0]; + const lastComment = ast.comments[ast.comments.length - 1]; + + if (ast.range[0] > firstComment.range[0]) { + ast.range[0] = firstComment.range[0]; + ast.start = firstComment.start; + if (ast.loc) { + ast.loc.start = firstComment.loc.start; + } + } + if (ast.range[1] < lastComment.range[1]) { + ast.range[1] = lastComment.range[1]; + ast.end = lastComment.end; + if (ast.loc) { + ast.loc.end = lastComment.loc.end; + } + } + } + return ast; +}; diff --git a/eslint/tests/fixtures/rules/custom-rule.js b/eslint/tests/fixtures/rules/custom-rule.js index 6d8b662..8b1cce1 100644 --- a/eslint/tests/fixtures/rules/custom-rule.js +++ b/eslint/tests/fixtures/rules/custom-rule.js @@ -1,15 +1,17 @@ -module.exports = function(context) { +module.exports = { + meta: { + schema: [] + }, + create(context) { - "use strict"; + "use strict"; - return { - "Identifier": function(node) { - if (node.name === "foo") { - context.report(node, "Identifier cannot be named 'foo'."); + return { + "Identifier": function(node) { + if (node.name === "foo") { + context.report(node, "Identifier cannot be named 'foo'."); + } } - } - }; - + }; + } }; - -module.exports.schema = []; diff --git a/eslint/tests/fixtures/testers/rule-tester/no-test-filename b/eslint/tests/fixtures/testers/rule-tester/no-test-filename index b3cde25..78d8f34 100644 --- a/eslint/tests/fixtures/testers/rule-tester/no-test-filename +++ b/eslint/tests/fixtures/testers/rule-tester/no-test-filename @@ -17,7 +17,7 @@ module.exports = { create(context) { return { "Program": function(node) { - if (context.getFilename() === '') { + if (context.filename === '') { context.report(node, "Filename test was not defined."); } } diff --git a/eslint/tests/fixtures/testers/rule-tester/no-test-global.js b/eslint/tests/fixtures/testers/rule-tester/no-test-global.js index 6703cc6..b44863d 100644 --- a/eslint/tests/fixtures/testers/rule-tester/no-test-global.js +++ b/eslint/tests/fixtures/testers/rule-tester/no-test-global.js @@ -15,9 +15,12 @@ module.exports = { schema: [], }, create(context) { + + const sourceCode = context.sourceCode; + return { - "Program": function(node) { - var globals = context.getScope().variables.map(function (variable) { + "Program"(node) { + var globals = sourceCode.getScope(node).variables.map(function (variable) { return variable.name; }); diff --git a/eslint/tests/fixtures/testers/rule-tester/no-var.js b/eslint/tests/fixtures/testers/rule-tester/no-var.js index 96a410f..578b750 100644 --- a/eslint/tests/fixtures/testers/rule-tester/no-var.js +++ b/eslint/tests/fixtures/testers/rule-tester/no-var.js @@ -15,7 +15,7 @@ module.exports = { schema: [] }, create(context) { - var sourceCode = context.getSourceCode(); + var sourceCode = context.sourceCode; return { "VariableDeclaration": function(node) { diff --git a/eslint/tests/fixtures/{curly-path}/client/eslint.config.js b/eslint/tests/fixtures/{curly-path}/client/eslint.config.js new file mode 100644 index 0000000..cab59b0 --- /dev/null +++ b/eslint/tests/fixtures/{curly-path}/client/eslint.config.js @@ -0,0 +1 @@ +module.exports = { rules: { "no-console": "off" } }; diff --git a/eslint/tests/fixtures/{curly-path}/client/src/one.js b/eslint/tests/fixtures/{curly-path}/client/src/one.js new file mode 100644 index 0000000..0dab8b6 --- /dev/null +++ b/eslint/tests/fixtures/{curly-path}/client/src/one.js @@ -0,0 +1 @@ +console.log("one"); diff --git a/eslint/tests/fixtures/{curly-path}/server/eslint.config.js b/eslint/tests/fixtures/{curly-path}/server/eslint.config.js new file mode 100644 index 0000000..7e8b056 --- /dev/null +++ b/eslint/tests/fixtures/{curly-path}/server/eslint.config.js @@ -0,0 +1 @@ +module.exports = { rules: { "no-console": "warn" } }; diff --git a/eslint/tests/fixtures/{curly-path}/server/src/two.js b/eslint/tests/fixtures/{curly-path}/server/src/two.js new file mode 100644 index 0000000..ef0e38f --- /dev/null +++ b/eslint/tests/fixtures/{curly-path}/server/src/two.js @@ -0,0 +1 @@ +console.log("two"); diff --git a/eslint/tests/lib/cli-engine/cli-engine.js b/eslint/tests/lib/cli-engine/cli-engine.js index 9cd5593..2528a39 100644 --- a/eslint/tests/lib/cli-engine/cli-engine.js +++ b/eslint/tests/lib/cli-engine/cli-engine.js @@ -577,7 +577,8 @@ describe("CLIEngine", () => { severity: 2, message: "Parsing error: Unexpected token is", line: 1, - column: 19 + column: 19, + nodeType: null } ], suppressedMessages: [], @@ -622,7 +623,8 @@ describe("CLIEngine", () => { severity: 2, message: "Parsing error: Unexpected token", line: 1, - column: 10 + column: 10, + nodeType: null } ], suppressedMessages: [], @@ -713,7 +715,8 @@ describe("CLIEngine", () => { severity: 2, message: "Parsing error: Unexpected token is", line: 1, - column: 19 + column: 19, + nodeType: null } ], suppressedMessages: [], @@ -4433,7 +4436,7 @@ describe("CLIEngine", () => { const cwd = getFixturePath("ignored-paths", "configurations"); const engine = new CLIEngine({ cwd }); - // a .eslintignore in parent directories includes `*.js`, but don't load it. + // an .eslintignore in parent directories includes `*.js`, but don't load it. assert(!engine.isPathIgnored("foo.js")); assert(engine.isPathIgnored("node_modules/foo.js")); }); @@ -4593,7 +4596,7 @@ describe("CLIEngine", () => { const cwd = getFixturePath("ignored-paths", "no-ignore-file"); const engine = new CLIEngine({ ignorePath: false, cwd }); - // a .eslintignore in parent directories includes `*.js`, but don't load it. + // an .eslintignore in parent directories includes `*.js`, but don't load it. assert(!engine.isPathIgnored("foo.js")); assert(engine.isPathIgnored("node_modules/foo.js")); }); @@ -5018,7 +5021,7 @@ describe("CLIEngine", () => { it("should call fs.writeFileSync() for each result with output", () => { const fakeFS = { - writeFileSync: () => {} + writeFileSync() {} }, localCLIEngine = proxyquire("../../../lib/cli-engine/cli-engine", { fs: fakeFS @@ -5048,7 +5051,7 @@ describe("CLIEngine", () => { it("should call fs.writeFileSync() for each result with output and not at all for a result without output", () => { const fakeFS = { - writeFileSync: () => {} + writeFileSync() {} }, localCLIEngine = proxyquire("../../../lib/cli-engine/cli-engine", { fs: fakeFS diff --git a/eslint/tests/lib/cli-engine/file-enumerator.js b/eslint/tests/lib/cli-engine/file-enumerator.js index 2ccf15e..3a96cf5 100644 --- a/eslint/tests/lib/cli-engine/file-enumerator.js +++ b/eslint/tests/lib/cli-engine/file-enumerator.js @@ -13,6 +13,7 @@ const path = require("path"); const os = require("os"); const { assert } = require("chai"); const sh = require("shelljs"); +const sinon = require("sinon"); const { Legacy: { CascadingConfigArrayFactory @@ -182,6 +183,73 @@ describe("FileEnumerator", () => { }); }); + // https://github.com/eslint/eslint/issues/14742 + describe("with 5 directories ('{lib}', '{lib}/client', '{lib}/client/src', '{lib}/server', '{lib}/server/src') that contains two files '{lib}/client/src/one.js' and '{lib}/server/src/two.js'", () => { + const root = path.join(os.tmpdir(), "eslint/file-enumerator"); + const files = { + "{lib}/client/src/one.js": "console.log('one.js');", + "{lib}/server/src/two.js": "console.log('two.js');", + "{lib}/client/.eslintrc.json": JSON.stringify({ + rules: { + "no-console": "error" + }, + env: { + mocha: true + } + }), + "{lib}/server/.eslintrc.json": JSON.stringify({ + rules: { + "no-console": "off" + }, + env: { + mocha: true + } + }) + }; + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files + }); + + /** @type {FileEnumerator} */ + let enumerator; + + beforeEach(async () => { + await prepare(); + enumerator = new FileEnumerator({ + cwd: path.resolve(getPath("{lib}/server")) + }); + }); + + afterEach(cleanup); + + describe("when running eslint in the server directory", () => { + it("should use the config '{lib}/server/.eslintrc.json' for '{lib}/server/src/two.js'.", () => { + const spy = sinon.spy(fs, "readdirSync"); + + const list = [ + ...enumerator.iterateFiles(["src/**/*.{js,json}"]) + ]; + + // should enter the directory '{lib}/server/src' directly + assert.strictEqual(spy.getCall(0).firstArg, path.join(root, "{lib}/server/src")); + assert.strictEqual(list.length, 1); + assert.strictEqual(list[0].config.length, 2); + assert.strictEqual(list[0].config[0].name, "DefaultIgnorePattern"); + assert.strictEqual(list[0].config[1].filePath, getPath("{lib}/server/.eslintrc.json")); + assert.deepStrictEqual( + list.map(entry => entry.filePath), + [ + path.join(root, "{lib}/server/src/two.js") + ] + ); + + // destroy the spy + sinon.restore(); + }); + }); + }); + // This group moved from 'tests/lib/util/glob-utils.js' when refactoring to keep the cumulated test cases. describe("with 'tests/fixtures/glob-utils' files", () => { let fixtureDir; diff --git a/eslint/tests/lib/cli.js b/eslint/tests/lib/cli.js index a58a8c9..a487c95 100644 --- a/eslint/tests/lib/cli.js +++ b/eslint/tests/lib/cli.js @@ -63,7 +63,7 @@ describe("cli", () => { const localCLI = proxyquire("../../lib/cli", { "./eslint": { ESLint: fakeESLint }, - "./flat-eslint": { FlatESLint: fakeESLint, findFlatConfigFile: () => null }, + "./flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(false) }, "./shared/logging": log }); @@ -137,6 +137,50 @@ describe("cli", () => { }); + describe("flat config", () => { + const originalEnv = process.env; + const originalCwd = process.cwd; + + beforeEach(() => { + process.env = { ...originalEnv }; + }); + + afterEach(() => { + process.env = originalEnv; + process.cwd = originalCwd; + }); + + it(`should use it when an eslint.config.js is present and useFlatConfig is true:${configType}`, async () => { + process.cwd = getFixturePath; + + const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); + + // When flat config is used, we get an exit code of 2 because the --ext option is unrecognized. + assert.strictEqual(exitCode, useFlatConfig ? 2 : 0); + }); + + it(`should not use it when ESLINT_USE_FLAT_CONFIG=false even if an eslint.config.js is present:${configType}`, async () => { + process.env.ESLINT_USE_FLAT_CONFIG = "false"; + process.cwd = getFixturePath; + + const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); + + assert.strictEqual(exitCode, 0); + }); + + it(`should use it when ESLINT_USE_FLAT_CONFIG=true and useFlatConfig is true even if an eslint.config.js is not present:${configType}`, async () => { + process.env.ESLINT_USE_FLAT_CONFIG = "true"; + + // Set the CWD to outside the fixtures/ directory so that no eslint.config.js is found + process.cwd = () => getFixturePath(".."); + + const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); + + // When flat config is used, we get an exit code of 2 because the --ext option is unrecognized. + assert.strictEqual(exitCode, useFlatConfig ? 2 : 0); + }); + }); + describe("when given a config with rules with options and severity level set to error", () => { const originalCwd = process.cwd; @@ -160,17 +204,6 @@ describe("cli", () => { }); }); - describe("when given a config file and a directory of files", () => { - it(`should load and execute without error with configType:${configType}`, async () => { - const configPath = getFixturePath("configurations", "semi-error.js"); - const filePath = getFixturePath("formatters"); - const code = `--config ${configPath} ${filePath}`; - const exitStatus = await cli.execute(code, null, useFlatConfig); - - assert.strictEqual(exitStatus, 0); - }); - }); - describe("when there is a local config file", () => { it(`should load the local config file with configType:${configType}`, async () => { @@ -244,6 +277,45 @@ describe("cli", () => { }); }); + describe("when the --max-warnings option is passed", () => { + const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; + + describe("and there are too many warnings", () => { + it(`should provide \`maxWarningsExceeded\` metadata to the formatter with configType:${configType}`, async () => { + const exit = await cli.execute( + `--no-ignore -f json-with-metadata --max-warnings 1 --rule 'quotes: warn' ${flag}`, + "'hello' + 'world';", + useFlatConfig + ); + + assert.strictEqual(exit, 1); + + const { metadata } = JSON.parse(log.info.args[0][0]); + + assert.deepStrictEqual( + metadata.maxWarningsExceeded, + { maxWarnings: 1, foundWarnings: 2 } + ); + }); + }); + + describe("and warnings do not exceed the limit", () => { + it(`should omit \`maxWarningsExceeded\` metadata from the formatter with configType:${configType}`, async () => { + const exit = await cli.execute( + `--no-ignore -f json-with-metadata --max-warnings 1 --rule 'quotes: warn' ${flag}`, + "'hello world';", + useFlatConfig + ); + + assert.strictEqual(exit, 0); + + const { metadata } = JSON.parse(log.info.args[0][0]); + + assert.notProperty(metadata, "maxWarningsExceeded"); + }); + }); + }); + describe("when given an invalid built-in formatter name", () => { it(`should execute with error: with configType:${configType}`, async () => { const filePath = getFixturePath("passing.js"); @@ -421,6 +493,17 @@ describe("cli", () => { process.cwd = originalCwd; }); + describe("when given a config file and a directory of files", () => { + it(`should load and execute without error with configType:${configType}`, async () => { + const configPath = getFixturePath("configurations", "semi-error.js"); + const filePath = getFixturePath("formatters"); + const code = `--no-ignore --config ${configPath} ${filePath}`; + const exitStatus = await cli.execute(code, null, useFlatConfig); + + assert.strictEqual(exitStatus, 0); + }); + }); + describe("when executing with global flag", () => { it(`should default defined variables to read-only with configType:${configType}`, async () => { @@ -658,23 +741,27 @@ describe("cli", () => { describe("when given a directory with eslint excluded files in the directory", () => { it(`should throw an error and not process any files with configType:${configType}`, async () => { - const ignorePath = getFixturePath(".eslintignore"); + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config_with_ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; const filePath = getFixturePath("cli"); const expectedMessage = useFlatConfig - ? `All files matched by '${filePath.replace(/\\/gu, "/")}/**/*.js' are ignored.` + ? `All files matched by '${filePath.replace(/\\/gu, "/")}' are ignored.` : `All files matched by '${filePath}' are ignored.`; await stdAssert.rejects(async () => { - await cli.execute(`--ignore-path ${ignorePath} ${filePath}`, null, useFlatConfig); + await cli.execute(`${options} ${filePath}`, null, useFlatConfig); }, new Error(expectedMessage)); }); }); describe("when given a file in excluded files list", () => { it(`should not process the file with configType:${configType}`, async () => { - const ignorePath = getFixturePath(".eslintignore"); + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config_with_ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`--ignore-path ${ignorePath} ${filePath}`, null, useFlatConfig); + const exit = await cli.execute(`${options} ${filePath}`, null, useFlatConfig); // a warning about the ignored file assert.isTrue(log.info.called); @@ -682,9 +769,11 @@ describe("cli", () => { }); it(`should process the file when forced with configType:${configType}`, async () => { - const ignorePath = getFixturePath(".eslintignore"); + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config_with_ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`--ignore-path ${ignorePath} --no-ignore ${filePath}`, null, useFlatConfig); + const exit = await cli.execute(`${options} --no-ignore ${filePath}`, null, useFlatConfig); // no warnings assert.isFalse(log.info.called); @@ -695,13 +784,65 @@ describe("cli", () => { describe("when given a pattern to ignore", () => { it(`should not process any files with configType:${configType}`, async () => { const ignoredFile = getFixturePath("cli/syntax-error.js"); + const ignorePathOption = useFlatConfig + ? "" + : "--ignore-path .eslintignore_empty"; const filePath = getFixturePath("cli/passing.js"); - const exit = await cli.execute(`--ignore-pattern cli/ ${ignoredFile} ${filePath}`, null, useFlatConfig); + const ignorePattern = useFlatConfig ? "cli/**" : "cli/"; + const exit = await cli.execute( + `--ignore-pattern ${ignorePattern} ${ignorePathOption} ${ignoredFile} ${filePath}`, null, useFlatConfig + ); // warnings about the ignored files assert.isTrue(log.info.called); assert.strictEqual(exit, 0); }); + + it(`should interpret pattern that contains a slash as relative to cwd with configType:${configType}`, async () => { + process.cwd = () => getFixturePath("cli/ignore-pattern-relative/subdir"); + + /* + * The config file is in `cli/ignore-pattern-relative`, so this would fail + * if `subdir/**` ignore pattern is interpreted as relative to the config base path. + */ + const exit = await cli.execute("**/*.js --ignore-pattern subdir/**", null, useFlatConfig); + + assert.strictEqual(exit, 0); + + await stdAssert.rejects( + async () => await cli.execute("**/*.js --ignore-pattern subsubdir/*.js", null, useFlatConfig), + /All files matched by '\*\*\/\*\.js' are ignored/u + ); + }); + + it(`should interpret pattern that doesn't contain a slash as relative to cwd with configType:${configType}`, async () => { + process.cwd = () => getFixturePath("cli/ignore-pattern-relative/subdir/subsubdir"); + + await stdAssert.rejects( + async () => await cli.execute("**/*.js --ignore-pattern *.js", null, useFlatConfig), + /All files matched by '\*\*\/\*\.js' are ignored/u + ); + }); + + if (useFlatConfig) { + it("should ignore files if the pattern is a path to a directory (with trailing slash)", async () => { + const filePath = getFixturePath("cli/syntax-error.js"); + const exit = await cli.execute(`--ignore-pattern cli/ ${filePath}`, null, true); + + // parsing error causes exit code 1 + assert.isTrue(log.info.called); + assert.strictEqual(exit, 0); + }); + + it("should ignore files if the pattern is a path to a directory (without trailing slash)", async () => { + const filePath = getFixturePath("cli/syntax-error.js"); + const exit = await cli.execute(`--ignore-pattern cli ${filePath}`, null, true); + + // parsing error causes exit code 1 + assert.isTrue(log.info.called); + assert.strictEqual(exit, 0); + }); + } }); }); @@ -799,7 +940,7 @@ describe("cli", () => { localCLI = proxyquire("../../lib/cli", { "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./eslint/flat-eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(false) }, "./shared/logging": log }); @@ -818,7 +959,7 @@ describe("cli", () => { localCLI = proxyquire("../../lib/cli", { "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./eslint/flat-eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(false) }, "./shared/logging": log }); @@ -849,7 +990,7 @@ describe("cli", () => { localCLI = proxyquire("../../lib/cli", { "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./eslint/flat-eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(false) }, "./shared/logging": log }); @@ -885,7 +1026,7 @@ describe("cli", () => { localCLI = proxyquire("../../lib/cli", { "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./eslint/flat-eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(false) }, "./shared/logging": log }); @@ -922,7 +1063,7 @@ describe("cli", () => { localCLI = proxyquire("../../lib/cli", { "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./eslint/flat-eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(false) }, "./shared/logging": log }); @@ -940,7 +1081,7 @@ describe("cli", () => { localCLI = proxyquire("../../lib/cli", { "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./eslint/flat-eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(false) }, "./shared/logging": log }); @@ -971,7 +1112,7 @@ describe("cli", () => { localCLI = proxyquire("../../lib/cli", { "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./eslint/flat-eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(false) }, "./shared/logging": log }); @@ -999,7 +1140,7 @@ describe("cli", () => { localCLI = proxyquire("../../lib/cli", { "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./eslint/flat-eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(false) }, "./shared/logging": log }); @@ -1034,7 +1175,7 @@ describe("cli", () => { localCLI = proxyquire("../../lib/cli", { "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./eslint/flat-eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(false) }, "./shared/logging": log }); @@ -1071,7 +1212,7 @@ describe("cli", () => { localCLI = proxyquire("../../lib/cli", { "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./eslint/flat-eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(false) }, "./shared/logging": log }); @@ -1107,7 +1248,7 @@ describe("cli", () => { localCLI = proxyquire("../../lib/cli", { "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./eslint/flat-eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(false) }, "./shared/logging": log }); @@ -1124,7 +1265,7 @@ describe("cli", () => { localCLI = proxyquire("../../lib/cli", { "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { ESLint: fakeESLint, findFlatConfigFile: () => null }, + "./eslint/flat-eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(false) }, "./shared/logging": log }); @@ -1306,7 +1447,7 @@ describe("cli", () => { getFixturePath("globals-node.js") ]; - await cli.execute(`--no-eslintrc --config ./conf/eslint-recommended.js --no-ignore ${files.join(" ")}`); + await cli.execute(`--no-eslintrc --config ./packages/js/src/configs/eslint-recommended.js --no-ignore ${files.join(" ")}`); assert.strictEqual(log.info.args[0][0].split("\n").length, 10); }); diff --git a/eslint/tests/lib/config/flat-config-array.js b/eslint/tests/lib/config/flat-config-array.js index 675257e..65d3a72 100644 --- a/eslint/tests/lib/config/flat-config-array.js +++ b/eslint/tests/lib/config/flat-config-array.js @@ -11,9 +11,12 @@ 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"); +const { + all: allConfig, + recommended: recommendedConfig +} = require("@eslint/js").configs; const stringify = require("json-stable-stringify-without-jsonify"); +const espree = require("espree"); //----------------------------------------------------------------------------- // Helpers @@ -25,15 +28,17 @@ const baseConfig = { "@": { rules: { foo: { - schema: { - type: "array", - items: [ - { - enum: ["always", "never"] - } - ], - minItems: 0, - maxItems: 1 + meta: { + schema: { + type: "array", + items: [ + { + enum: ["always", "never"] + } + ], + minItems: 0, + maxItems: 1 + } } }, @@ -48,13 +53,15 @@ const baseConfig = { boom() {}, foo2: { - schema: { - type: "array", - items: { - type: "string" - }, - uniqueItems: true, - minItems: 1 + meta: { + schema: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true, + minItems: 1 + } } } } @@ -184,6 +191,7 @@ describe("FlatConfigArray", () => { }); describe("Serialization of configs", () => { + it("should convert config into normalized JSON object", () => { const configs = new FlatConfigArray([{ @@ -201,7 +209,7 @@ describe("FlatConfigArray", () => { languageOptions: { ecmaVersion: "latest", sourceType: "module", - parser: "@/espree", + parser: `espree@${espree.version}`, parserOptions: {} }, processor: void 0 @@ -213,7 +221,73 @@ describe("FlatConfigArray", () => { assert.strictEqual(stringify(actual), stringify(expected)); }); - it("should throw an error when config with parser object is normalized", () => { + it("should convert config with plugin name/version into normalized JSON object", () => { + + const configs = new FlatConfigArray([{ + plugins: { + a: {}, + b: { + name: "b-plugin", + version: "2.3.1" + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + const expected = { + plugins: ["@", "a", "b:b-plugin@2.3.1"], + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + parser: `espree@${espree.version}`, + parserOptions: {} + }, + processor: void 0 + }; + const actual = config.toJSON(); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(stringify(actual), stringify(expected)); + }); + + it("should convert config with plugin meta into normalized JSON object", () => { + + const configs = new FlatConfigArray([{ + plugins: { + a: {}, + b: { + meta: { + name: "b-plugin", + version: "2.3.1" + } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + const expected = { + plugins: ["@", "a", "b:b-plugin@2.3.1"], + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + parser: `espree@${espree.version}`, + parserOptions: {} + }, + processor: void 0 + }; + const actual = config.toJSON(); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(stringify(actual), stringify(expected)); + }); + + it("should throw an error when config with unnamed parser object is normalized", () => { const configs = new FlatConfigArray([{ languageOptions: { @@ -229,11 +303,176 @@ describe("FlatConfigArray", () => { assert.throws(() => { config.toJSON(); - }, /Caching is not supported/u); + }, /Could not serialize parser/u); }); - it("should throw an error when config with processor object is normalized", () => { + it("should throw an error when config with unnamed parser object with empty meta object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: {}, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Could not serialize parser/u); + + }); + + it("should throw an error when config with unnamed parser object with only meta version is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + version: "0.1.1" + }, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Could not serialize parser/u); + + }); + + it("should not throw an error when config with named parser object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + name: "custom-parser" + }, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should not throw an error when config with named and versioned parser object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + name: "custom-parser", + version: "0.1.0" + }, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should not throw an error when config with meta-named and versioned parser object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + name: "custom-parser" + }, + version: "0.1.0", + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should not throw an error when config with named and versioned parser object outside of meta object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + name: "custom-parser", + version: "0.1.0", + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should throw an error when config with unnamed processor object is normalized", () => { const configs = new FlatConfigArray([{ processor: { @@ -248,10 +487,146 @@ describe("FlatConfigArray", () => { assert.throws(() => { config.toJSON(); - }, /Caching is not supported/u); + }, /Could not serialize processor/u); }); + it("should throw an error when config with processor object with empty meta object is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + meta: {}, + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Could not serialize processor/u); + + }); + + + it("should not throw an error when config with named processor object is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + meta: { + name: "custom-processor" + }, + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: `espree@${espree.version}`, + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor" + }); + + }); + + it("should not throw an error when config with named processor object without meta is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + name: "custom-processor", + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: `espree@${espree.version}`, + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor" + }); + + }); + + it("should not throw an error when config with named and versioned processor object is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + meta: { + name: "custom-processor", + version: "1.2.3" + }, + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: `espree@${espree.version}`, + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor@1.2.3" + }); + + }); + + it("should not throw an error when config with named and versioned processor object without meta is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + name: "custom-processor", + version: "1.2.3", + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: `espree@${espree.version}`, + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor@1.2.3" + }); + + }); }); @@ -1033,21 +1408,21 @@ describe("FlatConfigArray", () => { parser: true } } - ], "Expected an object or string."); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); - it("should error when an unexpected value is found", async () => { + it("should error when a null is found", async () => { await assertInvalidConfig([ { languageOptions: { - parser: "true" + parser: null } } - ], /Expected string in the form "pluginName\/objectName"/u); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); - it("should error when a plugin parser can't be found", async () => { + it("should error when a parser is a string", async () => { await assertInvalidConfig([ { @@ -1055,7 +1430,7 @@ describe("FlatConfigArray", () => { parser: "foo/bar" } } - ], "Key \"parser\": Could not find \"bar\" in plugin \"foo\"."); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); it("should error when a value doesn't have a parse() method", async () => { @@ -1066,7 +1441,7 @@ describe("FlatConfigArray", () => { parser: {} } } - ], "Expected object to have a parse() or parseForESLint() method."); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); it("should merge two objects when second object has overrides", () => { @@ -1081,24 +1456,12 @@ describe("FlatConfigArray", () => { } }, { - plugins: { - "@foo/baz": { - parsers: { - bar: stubParser - } - } - }, languageOptions: { - parser: "@foo/baz/bar" + parser: stubParser } } ], { plugins: { - "@foo/baz": { - parsers: { - bar: stubParser - } - }, ...baseConfig.plugins }, languageOptions: { @@ -1113,27 +1476,14 @@ describe("FlatConfigArray", () => { return assertMergedResult([ { - plugins: { - foo: { - parsers: { - bar: stubParser - } - } - }, - languageOptions: { - parser: "foo/bar" + parser: stubParser } }, { } ], { plugins: { - foo: { - parsers: { - bar: stubParser - } - }, ...baseConfig.plugins }, @@ -1153,25 +1503,12 @@ describe("FlatConfigArray", () => { { }, { - plugins: { - foo: { - parsers: { - bar: stubParser - } - } - }, - languageOptions: { - parser: "foo/bar" + parser: stubParser } } ], { plugins: { - foo: { - parsers: { - bar: stubParser - } - }, ...baseConfig.plugins }, @@ -1375,7 +1712,7 @@ describe("FlatConfigArray", () => { foo: true } } - ], "Key \"rules\": Key \"foo\": Expected a string, number, or array."); + ], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2."); }); it("should error when an invalid rule severity of the right type is set", async () => { @@ -1505,20 +1842,20 @@ describe("FlatConfigArray", () => { { rules: { foo: 1, - bar: "error" + foo2: "error" } }, { rules: { foo: ["error", "never"], - bar: ["warn", "foo"] + foo2: ["warn", "foo"] } } ], { plugins: baseConfig.plugins, rules: { foo: [2, "never"], - bar: [1, "foo"] + foo2: [1, "foo"] } })); diff --git a/eslint/tests/lib/eslint/eslint.js b/eslint/tests/lib/eslint/eslint.js index 74f26cf..c05a869 100644 --- a/eslint/tests/lib/eslint/eslint.js +++ b/eslint/tests/lib/eslint/eslint.js @@ -690,7 +690,8 @@ describe("ESLint", () => { severity: 2, message: "Parsing error: Unexpected token is", line: 1, - column: 19 + column: 19, + nodeType: null } ], suppressedMessages: [], @@ -730,7 +731,8 @@ describe("ESLint", () => { severity: 2, message: "Parsing error: Unexpected token", line: 1, - column: 10 + column: 10, + nodeType: null } ], suppressedMessages: [], @@ -819,7 +821,8 @@ describe("ESLint", () => { severity: 2, message: "Parsing error: Unexpected token is", line: 1, - column: 19 + column: 19, + nodeType: null } ], suppressedMessages: [], @@ -4400,7 +4403,7 @@ describe("ESLint", () => { const cwd = getFixturePath("ignored-paths", "configurations"); const engine = new ESLint({ cwd }); - // a .eslintignore in parent directories includes `*.js`, but don't load it. + // an .eslintignore in parent directories includes `*.js`, but don't load it. assert(!await engine.isPathIgnored("foo.js")); assert(await engine.isPathIgnored("node_modules/foo.js")); }); @@ -4971,7 +4974,7 @@ describe("ESLint", () => { const rulesMeta = engine.getRulesMetaForResults([]); - assert.strictEqual(Object.keys(rulesMeta).length, 0); + assert.deepStrictEqual(rulesMeta, {}); }); it("should return one rule meta when there is a linting error", async () => { @@ -4987,6 +4990,7 @@ describe("ESLint", () => { const results = await engine.lintText("a"); const rulesMeta = engine.getRulesMetaForResults(results); + assert.strictEqual(Object.keys(rulesMeta).length, 1); assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); }); @@ -5003,6 +5007,7 @@ describe("ESLint", () => { const results = await engine.lintText("a // eslint-disable-line semi"); const rulesMeta = engine.getRulesMetaForResults(results); + assert.strictEqual(Object.keys(rulesMeta).length, 1); assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); }); @@ -5051,6 +5056,51 @@ describe("ESLint", () => { nodePlugin.rules["no-new-require"].meta ); }); + + it("should ignore messages not related to a rule", async () => { + const engine = new ESLint({ + useEslintrc: false, + overrideConfig: { + ignorePatterns: "ignored.js", + rules: { + "no-var": "warn" + } + }, + reportUnusedDisableDirectives: "warn" + }); + + { + const results = await engine.lintText("syntax error"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText("// eslint-disable-line no-var"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText("", { filePath: "ignored.js", warnIgnored: true }); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + }); + + it("should return a non-empty value if some of the messages are related to a rule", async () => { + const engine = new ESLint({ + useEslintrc: false, + overrideConfig: { rules: { "no-var": "warn" } }, + reportUnusedDisableDirectives: "warn" + }); + + const results = await engine.lintText("// eslint-disable-line no-var\nvar foo;"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); + }); }); describe("outputFixes()", () => { diff --git a/eslint/tests/lib/eslint/flat-eslint.js b/eslint/tests/lib/eslint/flat-eslint.js index c69cba4..00a906b 100644 --- a/eslint/tests/lib/eslint/flat-eslint.js +++ b/eslint/tests/lib/eslint/flat-eslint.js @@ -11,6 +11,7 @@ //------------------------------------------------------------------------------ const assert = require("assert"); +const util = require("util"); const fs = require("fs"); const fsp = fs.promises; const os = require("os"); @@ -22,8 +23,35 @@ const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); const shell = require("shelljs"); const hash = require("../../../lib/cli-engine/hash"); const { unIndent, createCustomTeardown } = require("../../_utils"); +const { shouldUseFlatConfig } = require("../../../lib/eslint/flat-eslint"); const coreRules = require("../../../lib/rules"); +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Creates a directory if it doesn't already exist. + * @param {string} dirPath The path to the directory that should exist. + * @returns {void} + */ +function ensureDirectoryExists(dirPath) { + try { + fs.statSync(dirPath); + } catch { + fs.mkdirSync(dirPath); + } +} + +/** + * Does nothing for a given time. + * @param {number} time Time in ms. + * @returns {void} + */ +async function sleep(time) { + await util.promisify(setTimeout)(time); +} + //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ @@ -128,6 +156,7 @@ describe("FlatESLint", () => { configFile: "", envs: [], globals: [], + ignorePath: ".gitignore", ignorePattern: [], parser: "", parserOptions: {}, @@ -136,7 +165,7 @@ describe("FlatESLint", () => { }), new RegExp(escapeStringRegExp([ "Invalid Options:", - "- Unknown options: cacheFile, configFile, envs, globals, ignorePattern, parser, parserOptions, rules" + "- Unknown options: cacheFile, configFile, envs, globals, ignorePath, ignorePattern, parser, parserOptions, rules" ].join("\n")), "u") ); }); @@ -154,7 +183,7 @@ describe("FlatESLint", () => { fixTypes: ["xyz"], globInputPaths: "", ignore: "", - ignorePath: "", + ignorePatterns: "", overrideConfig: "", overrideConfigFile: "", plugins: "", @@ -172,7 +201,7 @@ describe("FlatESLint", () => { "- '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.", + "- 'ignorePatterns' must be an array of non-empty strings or null.", "- 'overrideConfig' must be an object or null.", "- 'overrideConfigFile' must be a non-empty string, null, or true.", "- 'plugins' must be an object or null.", @@ -181,6 +210,34 @@ describe("FlatESLint", () => { ); }); + it("should throw readable messages if 'ignorePatterns' is not an array of non-empty strings.", () => { + const invalidIgnorePatterns = [ + () => {}, + false, + {}, + "", + "foo", + [[]], + [() => {}], + [false], + [{}], + [""], + ["foo", ""], + ["foo", "", "bar"], + ["foo", false, "bar"] + ]; + + invalidIgnorePatterns.forEach(ignorePatterns => { + assert.throws( + () => new FlatESLint({ ignorePatterns }), + new RegExp(escapeStringRegExp([ + "Invalid Options:", + "- 'ignorePatterns' must be an array of non-empty strings or null." + ].join("\n")), "u") + ); + }); + }); + it("should throw readable messages if 'plugins' option contains empty key", () => { assert.throws( () => new FlatESLint({ @@ -278,9 +335,8 @@ describe("FlatESLint", () => { it("should return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is true", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore"), cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config.js" + overrideConfigFile: "fixtures/eslint.config_with_ignores.js" }); const options = { filePath: "fixtures/passing.js", warnIgnored: true }; @@ -302,9 +358,8 @@ describe("FlatESLint", () => { it("should not return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is false", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore"), cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config.js" + overrideConfigFile: "fixtures/eslint.config_with_ignores.js" }); const options = { filePath: "fixtures/passing.js", @@ -320,9 +375,8 @@ describe("FlatESLint", () => { it("should suppress excluded file warnings by default", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore"), cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config.js" + overrideConfigFile: "fixtures/eslint.config_with_ignores.js" }); const options = { filePath: "fixtures/passing.js" }; const results = await eslint.lintText("var bar = foo;", options); @@ -333,10 +387,9 @@ describe("FlatESLint", () => { it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", async () => { eslint = new FlatESLint({ - ignorePath: "fixtures/.eslintignore", cwd: getFixturePath(".."), ignore: false, - overrideConfigFile: true, + overrideConfigFile: "fixtures/eslint.config_with_ignores.js", overrideConfig: { rules: { "no-undef": 2 @@ -453,7 +506,8 @@ describe("FlatESLint", () => { severity: 2, message: "Parsing error: Unexpected token is", line: 1, - column: 19 + column: 19, + nodeType: null } ], suppressedMessages: [], @@ -493,7 +547,8 @@ describe("FlatESLint", () => { severity: 2, message: "Parsing error: Unexpected token", line: 1, - column: 10 + column: 10, + nodeType: null } ], suppressedMessages: [], @@ -583,7 +638,8 @@ describe("FlatESLint", () => { severity: 2, message: "Parsing error: Unexpected token is", line: 1, - column: 19 + column: 19, + nodeType: null } ], suppressedMessages: [], @@ -605,7 +661,7 @@ describe("FlatESLint", () => { ignore: false }); const results = await eslint.lintText("var bar = foo;", { filePath: "node_modules/passing.js", warnIgnored: true }); - const expectedMsg = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; + const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override."; assert.strictEqual(results.length, 1); assert.strictEqual(results[0].filePath, getFixturePath("node_modules/passing.js")); @@ -751,7 +807,7 @@ describe("FlatESLint", () => { overrideConfigFile: true }); - await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Expected string in the form "pluginName\/objectName" but found "test11"/u); + await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Expected object with parse\(\) or parseForESLint\(\) method/u); }); it("should report zero messages when given a directory with a .js2 file", async () => { @@ -778,12 +834,216 @@ describe("FlatESLint", () => { }); const results = await eslint.lintFiles(["fixtures/files/"]); - assert.strictEqual(results.length, 2); + assert.strictEqual(results.length, 3); assert.strictEqual(results[0].messages.length, 0); assert.strictEqual(results[1].messages.length, 0); assert.strictEqual(results[0].suppressedMessages.length, 0); }); + // https://github.com/eslint/eslint/issues/16413 + it("should find files and report zero messages when given a parent directory with a .js", async () => { + eslint = new FlatESLint({ + ignore: false, + cwd: getFixturePath("example-app/subdir") + }); + const results = await eslint.lintFiles(["../*.js"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/16038 + it("should allow files patterns with '..' inside", async () => { + eslint = new FlatESLint({ + ignore: false, + cwd: getFixturePath("dots-in-files") + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("dots-in-files/a..b.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + + // https://github.com/eslint/eslint/issues/16299 + it("should only find files in the subdir1 directory when given a directory name", async () => { + eslint = new FlatESLint({ + ignore: false, + cwd: getFixturePath("example-app2") + }); + const results = await eslint.lintFiles(["subdir1"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("example-app2/subdir1/a.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/14742 + it("should run", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("{curly-path}", "server") + }); + const results = await eslint.lintFiles(["src/**/*.{js,json}"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-console"); + assert.strictEqual( + results[0].filePath, + getFixturePath("{curly-path}/server/src/two.js") + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/16265 + describe("Dot files in searches", () => { + + it("should find dot files in current directory when a . pattern is used", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("dot-files") + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].filePath, getFixturePath("dot-files/.c.js")); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].filePath, getFixturePath("dot-files/b.js")); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + + it("should find dot files in current directory when a *.js pattern is used", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("dot-files") + }); + const results = await eslint.lintFiles(["*.js"]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].filePath, getFixturePath("dot-files/.c.js")); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].filePath, getFixturePath("dot-files/b.js")); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + + it("should find dot files in current directory when a .a.js pattern is used", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("dot-files") + }); + const results = await eslint.lintFiles([".a.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + }); + + // https://github.com/eslint/eslint/issues/16275 + describe("Glob patterns without matches", () => { + + it("should throw an error for a missing pattern when combined with a found pattern", async () => { + eslint = new FlatESLint({ + ignore: false, + cwd: getFixturePath("example-app2") + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1", "doesnotexist/*.js"]); + }, /No files matching 'doesnotexist\/\*\.js' were found/u); + }); + + it("should throw an error for an ignored directory pattern when combined with a found pattern", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir2"] + } + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); + }, /All files matched by 'subdir2\/\*\.js' are ignored/u); + }); + + it("should throw an error for an ignored file pattern when combined with a found pattern", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir2/*.js"] + } + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); + }, /All files matched by 'subdir2\/\*\.js' are ignored/u); + }); + + it("should always throw an error for the first unmatched file pattern", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir1/*.js", "subdir2/*.js"] + } + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["doesnotexist1/*.js", "doesnotexist2/*.js"]); + }, /No files matching 'doesnotexist1\/\*\.js' were found/u); + + await assert.rejects(async () => { + await eslint.lintFiles(["doesnotexist1/*.js", "subdir1/*.js"]); + }, /No files matching 'doesnotexist1\/\*\.js' were found/u); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1/*.js", "doesnotexist1/*.js"]); + }, /All files matched by 'subdir1\/\*\.js' are ignored/u); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); + }, /All files matched by 'subdir1\/\*\.js' are ignored/u); + }); + + it("should not throw an error for an ignored file pattern when errorOnUnmatchedPattern is false", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir2/*.js"] + }, + errorOnUnmatchedPattern: false + }); + + const results = await eslint.lintFiles(["subdir2/*.js"]); + + assert.strictEqual(results.length, 0); + }); + + it("should not throw an error for a non-existing file pattern when errorOnUnmatchedPattern is false", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("example-app2"), + errorOnUnmatchedPattern: false + }); + + const results = await eslint.lintFiles(["doesexist/*.js"]); + + assert.strictEqual(results.length, 0); + }); + }); + // https://github.com/eslint/eslint/issues/16260 describe("Globbing based on configs", () => { it("should report zero messages when given a directory with a .js and config file specifying a subdirectory", async () => { @@ -816,8 +1076,10 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 2); assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("shallow-glob/subdir/subsubdir/broken.js")); assert(results[0].messages[0].fatal, "Fatal error expected."); assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].filePath, getFixturePath("shallow-glob/subdir/subsubdir/plain.jsx")); assert.strictEqual(results[1].messages.length, 0); assert.strictEqual(results[1].suppressedMessages.length, 0); }); @@ -860,11 +1122,13 @@ describe("FlatESLint", () => { }); const results = await eslint.lintFiles(["fixtures/files/*"]); - assert.strictEqual(results.length, 2); + assert.strictEqual(results.length, 3); assert.strictEqual(results[0].messages.length, 0); assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[2].messages.length, 0); assert.strictEqual(results[0].suppressedMessages.length, 0); assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); }); it("should resolve globs when 'globInputPaths' option is true", async () => { @@ -877,11 +1141,13 @@ describe("FlatESLint", () => { }); const results = await eslint.lintFiles(["fixtures/files/*"]); - assert.strictEqual(results.length, 2); + assert.strictEqual(results.length, 3); assert.strictEqual(results[0].messages.length, 0); assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[2].messages.length, 0); assert.strictEqual(results[0].suppressedMessages.length, 0); assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); }); // only works on a Windows machine @@ -897,11 +1163,13 @@ describe("FlatESLint", () => { }); const results = await eslint.lintFiles(["fixtures\\files\\*"]); - assert.strictEqual(results.length, 2); + assert.strictEqual(results.length, 3); assert.strictEqual(results[0].messages.length, 0); assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[2].messages.length, 0); assert.strictEqual(results[0].suppressedMessages.length, 0); assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); }); } @@ -928,7 +1196,7 @@ describe("FlatESLint", () => { cwd: getFixturePath("cli-engine") }); const results = await eslint.lintFiles(["node_modules/foo.js"]); - const expectedMsg = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; + const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override."; assert.strictEqual(results.length, 1); assert.strictEqual(results[0].errorCount, 0); @@ -968,7 +1236,7 @@ describe("FlatESLint", () => { await assert.rejects(async () => { await eslint.lintFiles(["node_modules"]); - }, /All files matched by 'node_modules\/\*\*\/\*.js' are ignored\./u); + }, /All files matched by 'node_modules' are ignored\./u); }); // https://github.com/eslint/eslint/issues/5547 @@ -980,43 +1248,32 @@ describe("FlatESLint", () => { await assert.rejects(async () => { await eslint.lintFiles(["node_modules"]); - }, /All files matched by 'node_modules\/\*\*\/\*\.js' are ignored\./u); - }); - - it("should throw an error when given a directory with all eslint excluded files in the directory", async () => { - eslint = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore") - }); - - await assert.rejects(async () => { - await eslint.lintFiles([getFixturePath("./cli-engine/")]); - }, /All files matched by '.*?cli-engine[\\/]\*\*[\\/]\*\.js' are ignored/u); + }, /All files matched by 'node_modules' are ignored\./u); }); it("should throw an error when all given files are ignored", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore") + overrideConfigFile: getFixturePath("eslint.config_with_ignores.js") }); await assert.rejects(async () => { await eslint.lintFiles(["tests/fixtures/cli-engine/"]); - }, /All files matched by 'tests\/fixtures\/cli-engine\/\*\*\/\*\.js' are ignored\./u); + }, /All files matched by 'tests\/fixtures\/cli-engine\/' are ignored\./u); }); it("should throw an error when all given files are ignored even with a `./` prefix", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore") + overrideConfigFile: getFixturePath("eslint.config_with_ignores.js") }); await assert.rejects(async () => { await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); - }, /All files matched by 'tests\/fixtures\/cli-engine\/\*\*\/\*\.js' are ignored\./u); + }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); }); // https://github.com/eslint/eslint/issues/3788 - it("should ignore one-level down node_modules when ignore file has 'node_modules/' in it", async () => { + it("should ignore one-level down node_modules by default", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath("cli-engine", "nested_node_modules", ".eslintignore"), overrideConfigFile: true, overrideConfig: { rules: { @@ -1036,10 +1293,9 @@ describe("FlatESLint", () => { }); // https://github.com/eslint/eslint/issues/3812 - it("should ignore all files and throw an error when fixtures/ is in ignore file", async () => { + it("should ignore all files and throw an error when **/fixtures/** is in `ignores` in the config file", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath("cli-engine/.eslintignore2"), - overrideConfigFile: true, + overrideConfigFile: getFixturePath("cli-engine/eslint.config_with_ignores2.js"), overrideConfig: { rules: { quotes: [2, "double"] @@ -1049,7 +1305,7 @@ describe("FlatESLint", () => { await assert.rejects(async () => { await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); - }, /All files matched by 'tests\/fixtures\/cli-engine\/\*\*\/\*\.js' are ignored\./u); + }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); }); it("should throw an error when all given files are ignored via ignore-pattern", async () => { @@ -1066,7 +1322,7 @@ describe("FlatESLint", () => { it("should return a warning when an explicitly given file is ignored", async () => { eslint = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore"), + overrideConfigFile: "eslint.config_with_ignores.js", cwd: getFixturePath() }); const filePath = getFixturePath("passing.js"); @@ -1084,12 +1340,31 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].suppressedMessages.length, 0); }); + it("should return a warning about matching ignore patterns when an explicitly given dotfile is ignored", async () => { + eslint = new FlatESLint({ + overrideConfigFile: "eslint.config_with_ignores.js", + cwd: getFixturePath() + }); + const filePath = getFixturePath("dot-files/.a.js"); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + it("should return two messages when given a file in excluded files list while ignore is off", async () => { eslint = new FlatESLint({ cwd: getFixturePath(), - ignorePath: getFixturePath(".eslintignore"), ignore: false, - overrideConfigFile: true, + overrideConfigFile: getFixturePath("eslint.config_with_ignores.js"), overrideConfig: { rules: { "no-undef": 2 @@ -1107,6 +1382,126 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].messages[1].severity, 2); assert.strictEqual(results[0].suppressedMessages.length, 0); }); + + // https://github.com/eslint/eslint/issues/16300 + it("should process ignore patterns relative to basePath not cwd", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("ignores-relative/subdir") + }); + const results = await eslint.lintFiles(["**/*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-relative/subdir/a.js")); + }); + + // https://github.com/eslint/eslint/issues/16354 + it("should skip subdirectory files when ignore pattern matches deep subdirectory", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("ignores-directory") + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir/**"]); + }, /All files matched by 'subdir\/\*\*' are ignored\./u); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir/subsubdir/**"]); + }, /All files matched by 'subdir\/subsubdir\/\*\*' are ignored\./u); + + const results = await eslint.lintFiles(["subdir/subsubdir/a.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-directory/subdir/subsubdir/a.js")); + assert.strictEqual(results[0].warningCount, 1); + assert(results[0].messages[0].message.startsWith("File ignored"), "Should contain file ignored warning"); + + }); + + // https://github.com/eslint/eslint/issues/16414 + it("should skip subdirectory files when ignore pattern matches subdirectory", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("ignores-subdirectory") + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir/**/*.js"]); + }, /All files matched by 'subdir\/\*\*\/\*\.js' are ignored\./u); + + const results = await eslint.lintFiles(["subdir/subsubdir/a.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-subdirectory/subdir/subsubdir/a.js")); + assert.strictEqual(results[0].warningCount, 1); + assert(results[0].messages[0].message.startsWith("File ignored"), "Should contain file ignored warning"); + + eslint = new FlatESLint({ + cwd: getFixturePath("ignores-subdirectory/subdir") + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subsubdir/**/*.js"]); + }, /All files matched by 'subsubdir\/\*\*\/\*\.js' are ignored\./u); + + + }); + + // https://github.com/eslint/eslint/issues/16340 + it("should lint files even when cwd directory name matches ignores pattern", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("ignores-self") + }); + + const results = await eslint.lintFiles(["*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-self/eslint.config.js")); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + + }); + + // https://github.com/eslint/eslint/issues/16416 + it("should allow reignoring of previously ignored files", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("ignores-relative"), + overrideConfigFile: true, + overrideConfig: { + ignores: [ + "*.js", + "!a*.js", + "a.js" + ] + } + }); + const results = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-relative/a.js")); + }); + + // https://github.com/eslint/eslint/issues/16415 + it("should allow directories to be unignored", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("ignores-directory"), + overrideConfigFile: true, + overrideConfig: { + ignores: [ + "subdir/*", + "!subdir/subsubdir" + ] + } + }); + const results = await eslint.lintFiles(["subdir/**/*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-directory/subdir/subsubdir/a.js")); + }); + + }); @@ -1119,11 +1514,13 @@ describe("FlatESLint", () => { }); const results = await eslint.lintFiles(["fixtures/files/*.?s*"]); - assert.strictEqual(results.length, 2); + assert.strictEqual(results.length, 3); assert.strictEqual(results[0].messages.length, 0); assert.strictEqual(results[0].suppressedMessages.length, 0); assert.strictEqual(results[1].messages.length, 0); assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); }); it("should return one error message when given a config with rules with options and severity level set to error", async () => { @@ -2546,9 +2943,10 @@ describe("FlatESLint", () => { }); it("should throw if the directory exists and is empty", async () => { + ensureDirectoryExists(getFixturePath("cli-engine/empty")); await assert.rejects(async () => { await eslint.lintFiles(["empty"]); - }, /No files matching 'empty\/\*\*\/\*\.js' were found\./u); + }, /No files matching 'empty' were found\./u); }); it("one glob pattern", async () => { @@ -2568,6 +2966,13 @@ describe("FlatESLint", () => { await eslint.lintFiles(["console.js", "non-exist.js"]); }, /No files matching 'non-exist\.js' were found\./u); }); + + // https://github.com/eslint/eslint/issues/16275 + it("a mix of an existing glob pattern and a non-existing glob pattern", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["*.js", "non-exist/*.js"]); + }, /No files matching 'non-exist\/\*\.js' were found\./u); + }); }); describe("multiple processors", () => { @@ -2615,7 +3020,17 @@ describe("FlatESLint", () => { let id; beforeEach(() => (id = Date.now().toString())); - afterEach(async () => fsp.rmdir(root, { recursive: true, force: true })); + + /* + * `fs.rmdir(path, { recursive: true })` is deprecated and will be removed. + * Use `fs.rm(path, { recursive: true })` instead. + * When supporting Node.js 14.14.0+, the compatibility condition can be removed for `fs.rmdir`. + */ + if (typeof fsp.rm === "function") { + afterEach(async () => fsp.rm(root, { recursive: true, force: true })); + } else { + afterEach(async () => fsp.rmdir(root, { recursive: true, force: true })); + } it("should lint only JavaScript blocks.", async () => { const teardown = createCustomTeardown({ @@ -3138,7 +3553,7 @@ describe("FlatESLint", () => { describe("isPathIgnored", () => { it("should check if the given path is ignored", async () => { const engine = new FlatESLint({ - ignorePath: getFixturePath(".eslintignore2"), + overrideConfigFile: getFixturePath("eslint.config_with_ignores2.js"), cwd: getFixturePath() }); @@ -3149,7 +3564,7 @@ describe("FlatESLint", () => { it("should return false if ignoring is disabled", async () => { const engine = new FlatESLint({ ignore: false, - ignorePath: getFixturePath(".eslintignore2"), + overrideConfigFile: getFixturePath("eslint.config_with_ignores2.js"), cwd: getFixturePath() }); @@ -3183,12 +3598,12 @@ describe("FlatESLint", () => { assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); }); - it("should allow subfolders of defaultPatterns to be unignored by ignorePattern", async () => { + it("should allow subfolders of defaultPatterns to be unignored by ignorePattern constructor option", async () => { const cwd = getFixturePath("ignored-paths"); const engine = new FlatESLint({ cwd, overrideConfigFile: true, - ignorePatterns: "!/node_modules/package" + ignorePatterns: ["!node_modules/", "node_modules/*", "!node_modules/package/"] }); const result = await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js")); @@ -3196,12 +3611,14 @@ describe("FlatESLint", () => { assert(!result, "File should not be ignored"); }); - it("should allow subfolders of defaultPatterns to be unignored by ignorePath", async () => { + it("should allow subfolders of defaultPatterns to be unignored by ignores in overrideConfig", async () => { const cwd = getFixturePath("ignored-paths"); const engine = new FlatESLint({ cwd, overrideConfigFile: true, - ignorePath: getFixturePath("ignored-paths", ".eslintignoreWithUnignoredDefaults") + overrideConfig: { + ignores: ["!node_modules/", "node_modules/*", "!node_modules/package/"] + } }); assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); @@ -3228,53 +3645,21 @@ describe("FlatESLint", () => { assert(!await engine.isPathIgnored(`${getFixturePath("ignored-paths", "foo")}/../unignored.js`)); }); - it("should ignore /node_modules/ relative to .eslintignore when loaded", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignorePath: getFixturePath("ignored-paths", ".eslintignore"), cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "existing.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo", "node_modules", "existing.js"))); - }); - - it("should ignore /node_modules/ relative to cwd without an .eslintignore", async () => { + it("should ignore /node_modules/ relative to cwd without any configured ignore patterns", async () => { const cwd = getFixturePath("ignored-paths", "no-ignore-file"); const engine = new FlatESLint({ cwd }); assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "node_modules", "existing.js"))); assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "foo", "node_modules", "existing.js"))); }); - }); - describe("with no .eslintignore file", () => { - it("should not travel to parent directories to find .eslintignore when it's missing and cwd is provided", async () => { - const cwd = getFixturePath("ignored-paths", "configurations"); - const engine = new FlatESLint({ cwd }); - - // a .eslintignore in parent directories includes `*.js`, but don't load it. - assert(!await engine.isPathIgnored("foo.js")); - assert(await engine.isPathIgnored("node_modules/foo.js")); - }); - - it("should return false for files outside of the cwd (with no ignore file provided)", async () => { - - // Default ignore patterns should not inadvertently ignore files in parent directories + it("should not inadvertently ignore all files in parent directories", async () => { const engine = new FlatESLint({ cwd: getFixturePath("ignored-paths", "no-ignore-file") }); assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); }); }); - describe("with .eslintignore file or package.json file", () => { - it("should load .eslintignore from cwd when explicitly passed", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ cwd }); - - // `${cwd}/.eslintignore` includes `sampleignorepattern`. - assert(await engine.isPathIgnored("sampleignorepattern")); - }); - - }); - describe("with ignorePatterns option", () => { it("should accept a string for options.ignorePatterns", async () => { const cwd = getFixturePath("ignored-paths", "ignore-pattern"); @@ -3309,16 +3694,20 @@ describe("FlatESLint", () => { it("should return true for file matching an ignore pattern exactly", async () => { const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignorePatterns: ["undef.js"], cwd }); + const engine = new FlatESLint({ + ignorePatterns: ["undef.js"], + cwd, + overrideConfigFile: true + }); assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); }); - it("should return false for file in subfolder of cwd matching an ignore pattern with leading '/'", async () => { + it("should return false for file in subfolder of cwd matching an ignore pattern with a base filename", async () => { const cwd = getFixturePath("ignored-paths"); const filePath = getFixturePath("ignored-paths", "subdir", "undef.js"); const engine = new FlatESLint({ - ignorePatterns: ["/undef.js"], + ignorePatterns: ["undef.js"], overrideConfigFile: true, cwd }); @@ -3333,11 +3722,11 @@ describe("FlatESLint", () => { assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "ignore-me.txt"))); }); - it("should return true for file matching a grandchild of an ignore pattern", async () => { + it("should return true for file matching a grandchild of a directory when the pattern is directory/**", async () => { const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignorePatterns: ["ignore-pattern"], cwd }); + const engine = new FlatESLint({ ignorePatterns: ["ignore-pattern/**"], cwd }); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "subdir", "ignore-me.txt"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "subdir", "ignore-me.js"))); }); it("should return false for file not matching any ignore pattern", async () => { @@ -3364,146 +3753,11 @@ describe("FlatESLint", () => { }); }); - describe("with ignorePath option", () => { - it("initialization with ignorePath should work when cwd is a parent directory", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored("custom-name/foo.js")); - }); - - it("initialization with ignorePath should work when the file is in the cwd", async () => { - const cwd = getFixturePath("ignored-paths", "custom-name"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored("foo.js")); - }); - - it("initialization with ignorePath should work when cwd is a subdirectory", async () => { - const cwd = getFixturePath("ignored-paths", "custom-name", "subdirectory"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored("../custom-name/foo.js")); - }); - - it("missing ignore file should throw error", done => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "not-a-directory", ".foobaz"); - const engine = new FlatESLint({ ignorePath, cwd }); - - engine.isPathIgnored("foo.js").then(() => { - assert.fail("missing file should not succeed"); - }).catch(error => { - assert(/Cannot read ignore file/u.test(error)); - done(); - }); - }); - - it("should return false for files outside of ignorePath's directory", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - - it("should resolve relative paths from CWD", async () => { - const cwd = getFixturePath("ignored-paths", "subdir"); - - // /undef.js in ignore file - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreForDifferentCwd"); - const engine = new FlatESLint({ ignorePath, cwd, overrideConfigFile: true }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/undef.js")), "subdir/undef.js should be ignored"); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/subdir/undef.js")), "subdir/subdir/undef.js should not be ignored"); - }); - - it("should resolve relative paths from CWD when it's in a child directory", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/undef.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/foo.js"))); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/bar.js"))); - }); - - it("should resolve relative paths from CWD when it contains negated globs", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new FlatESLint({ - ignorePath, - cwd, - overrideConfig: { - files: ["**/*.txt"] - } - }); - - assert(await engine.isPathIgnored("subdir/blah.txt"), "subdir/blah.txt should be ignore"); - assert(await engine.isPathIgnored("blah.txt"), "blah.txt should be ignored"); - assert(await engine.isPathIgnored("subdir/bar.txt"), "subdir/bar.txt should be ignored"); - assert(!await engine.isPathIgnored("bar.txt"), "bar.txt should not be ignored"); - assert(!await engine.isPathIgnored("baz.txt"), "baz.txt should not be ignored"); - assert(!await engine.isPathIgnored("subdir/baz.txt"), "subdir/baz.txt should not be ignored"); - }); - - it("should resolve default ignore patterns from the CWD even when the ignorePath is in a subdirectory", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored("node_modules/blah.js")); - }); - - it("should resolve default ignore patterns from the CWD even when the ignorePath is in a parent directory", async () => { - const cwd = getFixturePath("ignored-paths", "subdir"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreForDifferentCwd"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored("node_modules/blah.js")); - }); - - it("should handle .eslintignore which contains CRLF correctly.", async () => { - const ignoreFileContent = fs.readFileSync(getFixturePath("ignored-paths", "crlf/.eslintignore"), "utf8"); - - assert(ignoreFileContent.includes("\r"), "crlf/.eslintignore should contains CR.", "Ignore file must have CRLF for test to pass."); - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "crlf/.eslintignore"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide1/a.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide2/a.js"))); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide3/a.js"))); - }); - - it("should ignore a non-negated pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithNegation"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "negation", "ignore.js"))); - }); - - it("should not ignore a negated pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithNegation"); - const engine = new FlatESLint({ ignorePath, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "negation", "unignore.js"))); - }); - }); - - describe("with ignorePath option and ignorePatterns option", () => { + describe("with config ignores ignorePatterns option", () => { it("should return false for ignored file when unignored with ignore pattern", async () => { const cwd = getFixturePath("ignored-paths"); const engine = new FlatESLint({ - ignorePath: getFixturePath("ignored-paths", ".eslintignoreForNegationTest"), + overrideConfigFile: getFixturePath("eslint.config_with_ignores2.js"), ignorePatterns: ["!undef.js"], cwd }); @@ -3703,8 +3957,7 @@ describe("FlatESLint", () => { it("should return 0 error or warning messages even when the file has warnings", async () => { const engine = new FlatESLint({ - overrideConfigFile: true, - ignorePath: path.join(fixtureDir, ".eslintignore"), + overrideConfigFile: getFixturePath("eslint.config_with_ignores.js"), cwd: path.join(fixtureDir, "..") }); const options = { @@ -3755,9 +4008,54 @@ describe("FlatESLint", () => { }); }); + describe("findConfigFile()", () => { + + it("should return undefined when overrideConfigFile is true", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true + }); + + assert.strictEqual(await engine.findConfigFile(), void 0); + }); + + it("should return undefined when a config file isn't found", async () => { + const engine = new FlatESLint({ + cwd: path.resolve(__dirname, "../../../../") + }); + + assert.strictEqual(await engine.findConfigFile(), void 0); + }); + + it("should return custom config file path when overrideConfigFile is a nonempty string", async () => { + const engine = new FlatESLint({ + overrideConfigFile: "my-config.js" + }); + const configFilePath = path.resolve(__dirname, "../../../my-config.js"); + + assert.strictEqual(await engine.findConfigFile(), configFilePath); + }); + + it("should return root level eslint.config.js when overrideConfigFile is null", async () => { + const engine = new FlatESLint({ + overrideConfigFile: null + }); + const configFilePath = path.resolve(__dirname, "../../../eslint.config.js"); + + assert.strictEqual(await engine.findConfigFile(), configFilePath); + }); + + it("should return root level eslint.config.js when overrideConfigFile is not specified", async () => { + const engine = new FlatESLint(); + const configFilePath = path.resolve(__dirname, "../../../eslint.config.js"); + + assert.strictEqual(await engine.findConfigFile(), configFilePath); + }); + + }); + describe("getRulesMetaForResults()", () => { - it("should throw an error when results were not created from this instance", async () => { + it("should throw an error when this instance did not lint any files", async () => { const engine = new FlatESLint({ overrideConfigFile: true }); @@ -3794,7 +4092,99 @@ describe("FlatESLint", () => { "var err = doStuff();\nif (err) console.log('failed tests: ' + err);\nprocess.exit(1);\n" } ]); - }, /Results object was not created from this ESLint instance/u); + }, { + constructor: TypeError, + message: "Results object was not created from this ESLint instance." + }); + }); + + it("should throw an error when results were created from a different instance", async () => { + const engine1 = new FlatESLint({ + overrideConfigFile: true, + cwd: path.join(fixtureDir, "foo"), + overrideConfig: { + rules: { + semi: 2 + } + } + }); + const engine2 = new FlatESLint({ + overrideConfigFile: true, + cwd: path.join(fixtureDir, "bar"), + overrideConfig: { + rules: { + semi: 2 + } + } + }); + + const results1 = await engine1.lintText("1", { filePath: "file.js" }); + const results2 = await engine2.lintText("2", { filePath: "file.js" }); + + engine1.getRulesMetaForResults(results1); // should not throw an error + assert.throws(() => { + engine1.getRulesMetaForResults(results2); + }, { + constructor: TypeError, + message: "Results object was not created from this ESLint instance." + }); + }); + + it("should treat a result without `filePath` as if the file was located in `cwd`", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true, + cwd: path.join(fixtureDir, "foo", "bar"), + ignorePatterns: ["*/**"], // ignore all subdirectories of `cwd` + overrideConfig: { + rules: { + eqeqeq: "warn" + } + } + }); + + const results = await engine.lintText("a==b"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta.eqeqeq, coreRules.get("eqeqeq").meta); + }); + + it("should not throw an error if a result without `filePath` contains an ignored file warning", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true, + cwd: path.join(fixtureDir, "foo", "bar"), + ignorePatterns: ["**"] + }); + + const results = await engine.lintText("", { warnIgnored: true }); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + }); + + it("should not throw an error if results contain linted files and one ignored file", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true, + cwd: getFixturePath(), + ignorePatterns: ["passing*"], + overrideConfig: { + rules: { + "no-undef": 2, + semi: 1 + } + } + }); + + const results = await engine.lintFiles(["missing-semicolon.js", "passing.js", "undef.js"]); + + assert( + results.some(({ messages }) => messages.some(({ message, ruleId }) => !ruleId && message.startsWith("File ignored"))), + "At least one file should be ignored but none is." + ); + + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta["no-undef"], coreRules.get("no-undef").meta); + assert.deepStrictEqual(rulesMeta.semi, coreRules.get("semi").meta); }); it("should return empty object when there are no linting errors", async () => { @@ -3804,7 +4194,7 @@ describe("FlatESLint", () => { const rulesMeta = engine.getRulesMetaForResults([]); - assert.strictEqual(Object.keys(rulesMeta).length, 0); + assert.deepStrictEqual(rulesMeta, {}); }); it("should return one rule meta when there is a linting error", async () => { @@ -3820,6 +4210,24 @@ describe("FlatESLint", () => { const results = await engine.lintText("a", { filePath: "foo.js" }); const rulesMeta = engine.getRulesMetaForResults(results); + assert.strictEqual(Object.keys(rulesMeta).length, 1); + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + }); + + it("should return one rule meta when there is a suppressed linting error", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true, + overrideConfig: { + rules: { + semi: 2 + } + } + }); + + const results = await engine.lintText("a // eslint-disable-line semi"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(Object.keys(rulesMeta).length, 1); assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); }); @@ -3867,6 +4275,51 @@ describe("FlatESLint", () => { nodePlugin.rules["no-new-require"].meta ); }); + + it("should ignore messages not related to a rule", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true, + ignorePatterns: ["ignored.js"], + overrideConfig: { + rules: { + "no-var": "warn" + } + }, + reportUnusedDisableDirectives: "warn" + }); + + { + const results = await engine.lintText("syntax error"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText("// eslint-disable-line no-var"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText("", { filePath: "ignored.js", warnIgnored: true }); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + }); + + it("should return a non-empty value if some of the messages are related to a rule", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true, + overrideConfig: { rules: { "no-var": "warn" } }, + reportUnusedDisableDirectives: "warn" + }); + + const results = await engine.lintText("// eslint-disable-line no-var\nvar foo;"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); + }); }); describe("outputFixes()", () => { @@ -4175,19 +4628,13 @@ describe("FlatESLint", () => { }); - /* - * These tests fail due to a bug in fast-flob that doesn't allow - * negated patterns inside of ignores. These tests won't work until - * this bug is fixed: - * https://github.com/mrmlnc/fast-glob/issues/356 - */ - xdescribe("ignorePatterns can unignore '/node_modules/foo'.", () => { + describe("ignores can unignore '/node_modules/foo' with patterns ['!node_modules/', 'node_modules/*', '!node_modules/foo/'].", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: `${root}-unignores`, files: { "eslint.config.js": `module.exports = { - ignores: ["!**/node_modules/foo/**"] + ignores: ["!node_modules/", "node_modules/*", "!node_modules/foo/"] };`, "node_modules/foo/index.js": "", "node_modules/foo/.dot.js": "", @@ -4224,21 +4671,74 @@ describe("FlatESLint", () => { .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "eslint.config.js"), - path.join(root, "foo.js"), - path.join(root, "node_modules/foo/index.js") + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo.js"), + path.join(getPath(), "node_modules/foo/.dot.js"), + path.join(getPath(), "node_modules/foo/index.js") ]); }); }); - xdescribe(".eslintignore can re-ignore files that are unignored by ignorePatterns.", () => { + describe("ignores can unignore '/node_modules/foo' with patterns ['!node_modules/', 'node_modules/*', '!node_modules/foo/**'].", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: `${root}-unignores`, + files: { + "eslint.config.js": `module.exports = { + ignores: ["!node_modules/", "node_modules/*", "!node_modules/foo/**"] + };`, + "node_modules/foo/index.js": "", + "node_modules/foo/.dot.js": "", + "node_modules/bar/index.js": "", + "foo.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => { + const engine = new FlatESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("node_modules/foo/index.js"), false); + }); + + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/.dot.js'.", async () => { + const engine = new FlatESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("node_modules/foo/.dot.js"), false); + }); + + it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => { + const engine = new FlatESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("node_modules/bar/index.js"), true); + }); + + it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => { + const engine = new FlatESLint({ cwd: getPath() }); + const result = (await engine.lintFiles("**/*.js")); + + const filePaths = result + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo.js"), + path.join(getPath(), "node_modules/foo/.dot.js"), + path.join(getPath(), "node_modules/foo/index.js") + ]); + }); + }); + + describe("ignore pattern can re-ignore files that are unignored by a previous pattern.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}-reignore`, files: { "eslint.config.js": `module.exports = ${JSON.stringify({ - ignores: ["!.*"] + ignores: ["!.*", ".foo*"] })}`, - ".eslintignore": ".foo*", ".foo.js": "", ".bar.js": "" } @@ -4266,20 +4766,19 @@ describe("FlatESLint", () => { .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, ".bar.js"), - path.join(root, "eslint.config.js") + path.join(getPath(), ".bar.js"), + path.join(getPath(), "eslint.config.js") ]); }); }); - xdescribe(".eslintignore can unignore files that are ignored by ignorePatterns.", () => { + describe("ignore pattern can unignore files that are ignored by a previous pattern.", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: `${root}-dignore`, files: { "eslint.config.js": `module.exports = ${JSON.stringify({ - ignores: ["**/*.js"] + ignores: ["**/*.js", "!foo.js"] })}`, - ".eslintignore": "!foo.js", "foo.js": "", "bar.js": "" } @@ -4307,7 +4806,7 @@ describe("FlatESLint", () => { .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "foo.js") + path.join(getPath(), "foo.js") ]); }); }); @@ -4612,9 +5111,11 @@ describe("FlatESLint", () => { fixableWarningCount: 0, messages: [ { + ruleId: null, fatal: false, - message: "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override.", - severity: 1 + message: "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override.", + severity: 1, + nodeType: null } ], usedDeprecatedRules: [], @@ -4708,17 +5209,17 @@ describe("FlatESLint", () => { }); }); - // dependent on https://github.com/mrmlnc/fast-glob/issues/86 - xdescribe("if { ignores: 'foo/*.js', ... } is present by '--config node_modules/myconf/eslint.config.js',", () => { + describe("if { ignores: 'foo/*.js', ... } is present by '--config node_modules/myconf/eslint.config.js',", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ cwd: `${root}a3`, files: { - "node_modules/myconf/eslint.config.js": `module.exports = { - ignores: ["**/eslint.config.js", "!node_modules/myconf", "foo/*.js"], + "node_modules/myconf/eslint.config.js": `module.exports = [{ + ignores: ["!node_modules", "node_modules/*", "!node_modules/myconf", "foo/*.js"], + }, { rules: { eqeqeq: "error" } - }`, + }]`, "node_modules/myconf/foo/test.js": "a == b", "foo/test.js": "a == b" } @@ -4727,7 +5228,7 @@ describe("FlatESLint", () => { beforeEach(prepare); afterEach(cleanup); - it("'lintFiles()' with '**/*.js' should iterate 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", async () => { + it("'lintFiles()' with '**/*.js' should lint 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", async () => { const engine = new FlatESLint({ overrideConfigFile: "node_modules/myconf/eslint.config.js", cwd: getPath() @@ -4737,10 +5238,343 @@ describe("FlatESLint", () => { .sort(); assert.deepStrictEqual(files, [ + path.join(getPath(), "node_modules/myconf/eslint.config.js"), path.join(getPath(), "node_modules/myconf/foo/test.js") ]); }); }); }); + describe("baseConfig", () => { + it("can be an object", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: true, + baseConfig: { + rules: { + semi: 2 + } + } + }); + + const [{ messages }] = await eslint.lintText("foo"); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + }); + + it("can be an array", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: true, + baseConfig: [ + { + rules: { + "no-var": 2 + } + }, + { + rules: { + semi: 2 + } + } + ] + }); + + const [{ messages }] = await eslint.lintText("var foo"); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-var"); + assert.strictEqual(messages[1].ruleId, "semi"); + }); + + it("should be inserted after default configs", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: true, + baseConfig: { + languageOptions: { + ecmaVersion: 5, + sourceType: "script" + } + } + }); + + const [{ messages }] = await eslint.lintText("let x"); + + /* + * if baseConfig was inserted before default configs, + * `ecmaVersion: "latest"` from default configs would overwrite + * `ecmaVersion: 5` from baseConfig, so this wouldn't be a parsing error. + */ + + assert.strictEqual(messages.length, 1); + assert(messages[0].fatal, "Fatal error expected."); + }); + + it("should be inserted before configs from the config file", async () => { + const eslint = new FlatESLint({ + cwd: getFixturePath(), + baseConfig: { + rules: { + strict: ["error", "global"] + }, + languageOptions: { + sourceType: "script" + } + } + }); + + const [{ messages }] = await eslint.lintText("foo"); + + /* + * if baseConfig was inserted after configs from the config file, + * `strict: 0` from eslint.config.js wouldn't overwrite `strict: ["error", "global"]` + * from baseConfig, so there would be an error message from the `strict` rule. + */ + + assert.strictEqual(messages.length, 0); + }); + + it("should be inserted before overrideConfig", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: true, + baseConfig: { + rules: { + semi: 2 + } + }, + overrideConfig: { + rules: { + semi: 1 + } + } + }); + + const [{ messages }] = await eslint.lintText("foo"); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].severity, 1); + }); + + it("should be inserted before configs from the config file and overrideConfig", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: getFixturePath("eslint.config_with_rules.js"), + baseConfig: { + rules: { + quotes: ["error", "double"], + semi: "error" + } + }, + overrideConfig: { + rules: { + quotes: "warn" + } + } + }); + + const [{ messages }] = await eslint.lintText('const foo = "bar"'); + + /* + * baseConfig: { quotes: ["error", "double"], semi: "error" } + * eslint.config_with_rules.js: { quotes: ["error", "single"] } + * overrideConfig: { quotes: "warn" } + * + * Merged config: { quotes: ["warn", "single"], semi: "error" } + */ + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "quotes"); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[1].ruleId, "semi"); + assert.strictEqual(messages[1].severity, 2); + }); + + it("when it has 'files' they should be interpreted as relative to the config file", async () => { + + /* + * `fixtures/plugins` directory does not have a config file. + * It's parent directory `fixtures` does have a config file, so + * the base path will be `fixtures`, cwd will be `fixtures/plugins` + */ + const eslint = new FlatESLint({ + cwd: getFixturePath("plugins"), + baseConfig: { + files: ["plugins/a.js"], + rules: { + semi: 2 + } + } + }); + + const [{ messages }] = await eslint.lintText("foo", { filePath: getFixturePath("plugins/a.js") }); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + }); + + it("when it has 'ignores' they should be interpreted as relative to the config file", async () => { + + /* + * `fixtures/plugins` directory does not have a config file. + * It's parent directory `fixtures` does have a config file, so + * the base path will be `fixtures`, cwd will be `fixtures/plugins` + */ + const eslint = new FlatESLint({ + cwd: getFixturePath("plugins"), + baseConfig: { + ignores: ["plugins/a.js"] + } + }); + + const [{ messages }] = await eslint.lintText("foo", { filePath: getFixturePath("plugins/a.js"), warnIgnored: true }); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.match(messages[0].message, /ignored/u); + }); + }); + + describe("config file", () => { + + it("new instance of FlatESLint should use the latest version of the config file (ESM)", async () => { + const cwd = path.join(getFixturePath(), `config_file_${Date.now()}`); + const configFileContent = "export default [{ rules: { semi: ['error', 'always'] } }];"; + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": '{ "type": "module" }', + "eslint.config.js": configFileContent, + "a.js": "foo\nbar;" + } + }); + + await teardown.prepare(); + + let eslint = new FlatESLint({ cwd }); + let [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "missingSemi"); + assert.strictEqual(messages[0].line, 1); + + await sleep(100); + await fsp.writeFile(path.join(cwd, "eslint.config.js"), configFileContent.replace("always", "never")); + + eslint = new FlatESLint({ cwd }); + [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "extraSemi"); + assert.strictEqual(messages[0].line, 2); + }); + + it("new instance of FlatESLint should use the latest version of the config file (CJS)", async () => { + const cwd = path.join(getFixturePath(), `config_file_${Date.now()}`); + const configFileContent = "module.exports = [{ rules: { semi: ['error', 'always'] } }];"; + const teardown = createCustomTeardown({ + cwd, + files: { + "eslint.config.js": configFileContent, + "a.js": "foo\nbar;" + } + }); + + await teardown.prepare(); + + let eslint = new FlatESLint({ cwd }); + let [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "missingSemi"); + assert.strictEqual(messages[0].line, 1); + + await sleep(100); + await fsp.writeFile(path.join(cwd, "eslint.config.js"), configFileContent.replace("always", "never")); + + eslint = new FlatESLint({ cwd }); + [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "extraSemi"); + assert.strictEqual(messages[0].line, 2); + }); + }); + +}); + +describe("shouldUseFlatConfig", () => { + + /** + * Check that `shouldUseFlatConfig` returns the expected value from a CWD + * with a flat config and one without a flat config. + * @param {boolean} expectedValueWithConfig the expected return value of + * `shouldUseFlatConfig` when in a directory with a flat config present + * @param {boolean} expectedValueWithoutConfig the expected return value of + * `shouldUseFlatConfig` when in a directory without any flat config present + * @returns {void} + */ + function testShouldUseFlatConfig(expectedValueWithConfig, expectedValueWithoutConfig) { + describe("when there is a flat config file present", () => { + const originalDir = process.cwd(); + + beforeEach(() => { + process.chdir(__dirname); + }); + + afterEach(() => { + process.chdir(originalDir); + }); + + it(`is \`${expectedValueWithConfig}\``, async () => { + assert.strictEqual(await shouldUseFlatConfig(), expectedValueWithConfig); + }); + }); + + describe("when there is no flat config file present", () => { + const originalDir = process.cwd(); + + beforeEach(() => { + process.chdir(os.tmpdir()); + }); + + afterEach(() => { + process.chdir(originalDir); + }); + + it(`is \`${expectedValueWithoutConfig}\``, async () => { + assert.strictEqual(await shouldUseFlatConfig(), expectedValueWithoutConfig); + }); + }); + } + + describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is `'true'`", () => { + beforeEach(() => { + process.env.ESLINT_USE_FLAT_CONFIG = true; + }); + + afterEach(() => { + delete process.env.ESLINT_USE_FLAT_CONFIG; + }); + + testShouldUseFlatConfig(true, true); + }); + + describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is `'false'`", () => { + beforeEach(() => { + process.env.ESLINT_USE_FLAT_CONFIG = false; + }); + + afterEach(() => { + delete process.env.ESLINT_USE_FLAT_CONFIG; + }); + + testShouldUseFlatConfig(false, false); + }); + + describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is unset", () => { + testShouldUseFlatConfig(true, false); + }); }); 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 0b5dd33..cc2717a 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 @@ -66,11 +66,13 @@ describe("CodePathAnalyzer", () => { beforeEach(() => { actual = []; - linter.defineRule("test", () => ({ - onCodePathStart(codePath) { - actual.push(codePath); - } - })); + linter.defineRule("test", { + create: () => ({ + onCodePathStart(codePath) { + actual.push(codePath); + } + }) + }); linter.verify( "function foo(a) { if (a) return 0; else throw new Error(); }", { rules: { test: 2 } } @@ -142,22 +144,24 @@ describe("CodePathAnalyzer", () => { assert(actual[1].currentSegments.length === 0); // there is the current segment in progress. - linter.defineRule("test", () => { - let codePath = null; + linter.defineRule("test", { + create() { + let codePath = null; - return { - onCodePathStart(cp) { - codePath = cp; - }, - ReturnStatement() { - assert(codePath.currentSegments.length === 1); - assert(codePath.currentSegments[0] instanceof CodePathSegment); - }, - ThrowStatement() { - assert(codePath.currentSegments.length === 1); - assert(codePath.currentSegments[0] instanceof CodePathSegment); - } - }; + return { + onCodePathStart(cp) { + codePath = cp; + }, + ReturnStatement() { + assert(codePath.currentSegments.length === 1); + assert(codePath.currentSegments[0] instanceof CodePathSegment); + }, + ThrowStatement() { + assert(codePath.currentSegments.length === 1); + assert(codePath.currentSegments[0] instanceof CodePathSegment); + } + }; + } }); linter.verify( "function foo(a) { if (a) return 0; else throw new Error(); }", @@ -171,11 +175,13 @@ describe("CodePathAnalyzer", () => { beforeEach(() => { actual = []; - linter.defineRule("test", () => ({ - onCodePathSegmentStart(segment) { - actual.push(segment); - } - })); + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentStart(segment) { + actual.push(segment); + } + }) + }); linter.verify( "function foo(a) { if (a) return 0; else throw new Error(); }", { rules: { test: 2 } } @@ -258,35 +264,37 @@ describe("CodePathAnalyzer", () => { let count = 0; let lastCodePathNodeType = null; - linter.defineRule("test", () => ({ - onCodePathStart(cp, node) { - count += 1; - lastCodePathNodeType = node.type; + linter.defineRule("test", { + create: () => ({ + onCodePathStart(cp, node) { + count += 1; + lastCodePathNodeType = node.type; - assert(cp instanceof CodePath); - if (count === 1) { - assert(node.type === "Program"); - } else if (count === 2) { - assert(node.type === "FunctionDeclaration"); - } else if (count === 3) { - assert(node.type === "FunctionExpression"); - } else if (count === 4) { - assert(node.type === "ArrowFunctionExpression"); + assert(cp instanceof CodePath); + if (count === 1) { + assert(node.type === "Program"); + } else if (count === 2) { + assert(node.type === "FunctionDeclaration"); + } else if (count === 3) { + assert(node.type === "FunctionExpression"); + } else if (count === 4) { + assert(node.type === "ArrowFunctionExpression"); + } + }, + Program() { + assert(lastCodePathNodeType === "Program"); + }, + FunctionDeclaration() { + assert(lastCodePathNodeType === "FunctionDeclaration"); + }, + FunctionExpression() { + assert(lastCodePathNodeType === "FunctionExpression"); + }, + ArrowFunctionExpression() { + assert(lastCodePathNodeType === "ArrowFunctionExpression"); } - }, - Program() { - assert(lastCodePathNodeType === "Program"); - }, - FunctionDeclaration() { - assert(lastCodePathNodeType === "FunctionDeclaration"); - }, - FunctionExpression() { - assert(lastCodePathNodeType === "FunctionExpression"); - }, - ArrowFunctionExpression() { - assert(lastCodePathNodeType === "ArrowFunctionExpression"); - } - })); + }) + }); linter.verify( "foo(); function foo() {} var foo = function() {}; var foo = () => {};", { rules: { test: 2 }, env: { es6: true } } @@ -301,35 +309,37 @@ describe("CodePathAnalyzer", () => { let count = 0; let lastNodeType = null; - linter.defineRule("test", () => ({ - onCodePathEnd(cp, node) { - count += 1; + linter.defineRule("test", { + create: () => ({ + onCodePathEnd(cp, node) { + count += 1; - assert(cp instanceof CodePath); - if (count === 4) { - assert(node.type === "Program"); - } else if (count === 1) { - assert(node.type === "FunctionDeclaration"); - } else if (count === 2) { - assert(node.type === "FunctionExpression"); - } else if (count === 3) { - assert(node.type === "ArrowFunctionExpression"); + assert(cp instanceof CodePath); + if (count === 4) { + assert(node.type === "Program"); + } else if (count === 1) { + assert(node.type === "FunctionDeclaration"); + } else if (count === 2) { + assert(node.type === "FunctionExpression"); + } else if (count === 3) { + assert(node.type === "ArrowFunctionExpression"); + } + assert(node.type === lastNodeType); + }, + "Program:exit"() { + lastNodeType = "Program"; + }, + "FunctionDeclaration:exit"() { + lastNodeType = "FunctionDeclaration"; + }, + "FunctionExpression:exit"() { + lastNodeType = "FunctionExpression"; + }, + "ArrowFunctionExpression:exit"() { + lastNodeType = "ArrowFunctionExpression"; } - assert(node.type === lastNodeType); - }, - "Program:exit"() { - lastNodeType = "Program"; - }, - "FunctionDeclaration:exit"() { - lastNodeType = "FunctionDeclaration"; - }, - "FunctionExpression:exit"() { - lastNodeType = "FunctionExpression"; - }, - "ArrowFunctionExpression:exit"() { - lastNodeType = "ArrowFunctionExpression"; - } - })); + }) + }); linter.verify( "foo(); function foo() {} var foo = function() {}; var foo = () => {};", { rules: { test: 2 }, env: { es6: true } } @@ -344,35 +354,37 @@ describe("CodePathAnalyzer", () => { let count = 0; let lastCodePathNodeType = null; - linter.defineRule("test", () => ({ - onCodePathSegmentStart(segment, node) { - count += 1; - lastCodePathNodeType = node.type; + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentStart(segment, node) { + count += 1; + lastCodePathNodeType = node.type; - assert(segment instanceof CodePathSegment); - if (count === 1) { - assert(node.type === "Program"); - } else if (count === 2) { - assert(node.type === "FunctionDeclaration"); - } else if (count === 3) { - assert(node.type === "FunctionExpression"); - } else if (count === 4) { - assert(node.type === "ArrowFunctionExpression"); + assert(segment instanceof CodePathSegment); + if (count === 1) { + assert(node.type === "Program"); + } else if (count === 2) { + assert(node.type === "FunctionDeclaration"); + } else if (count === 3) { + assert(node.type === "FunctionExpression"); + } else if (count === 4) { + assert(node.type === "ArrowFunctionExpression"); + } + }, + Program() { + assert(lastCodePathNodeType === "Program"); + }, + FunctionDeclaration() { + assert(lastCodePathNodeType === "FunctionDeclaration"); + }, + FunctionExpression() { + assert(lastCodePathNodeType === "FunctionExpression"); + }, + ArrowFunctionExpression() { + assert(lastCodePathNodeType === "ArrowFunctionExpression"); } - }, - Program() { - assert(lastCodePathNodeType === "Program"); - }, - FunctionDeclaration() { - assert(lastCodePathNodeType === "FunctionDeclaration"); - }, - FunctionExpression() { - assert(lastCodePathNodeType === "FunctionExpression"); - }, - ArrowFunctionExpression() { - assert(lastCodePathNodeType === "ArrowFunctionExpression"); - } - })); + }) + }); linter.verify( "foo(); function foo() {} var foo = function() {}; var foo = () => {};", { rules: { test: 2 }, env: { es6: true } } @@ -387,35 +399,37 @@ describe("CodePathAnalyzer", () => { let count = 0; let lastNodeType = null; - linter.defineRule("test", () => ({ - onCodePathSegmentEnd(cp, node) { - count += 1; + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentEnd(cp, node) { + count += 1; - assert(cp instanceof CodePathSegment); - if (count === 4) { - assert(node.type === "Program"); - } else if (count === 1) { - assert(node.type === "FunctionDeclaration"); - } else if (count === 2) { - assert(node.type === "FunctionExpression"); - } else if (count === 3) { - assert(node.type === "ArrowFunctionExpression"); + assert(cp instanceof CodePathSegment); + if (count === 4) { + assert(node.type === "Program"); + } else if (count === 1) { + assert(node.type === "FunctionDeclaration"); + } else if (count === 2) { + assert(node.type === "FunctionExpression"); + } else if (count === 3) { + assert(node.type === "ArrowFunctionExpression"); + } + assert(node.type === lastNodeType); + }, + "Program:exit"() { + lastNodeType = "Program"; + }, + "FunctionDeclaration:exit"() { + lastNodeType = "FunctionDeclaration"; + }, + "FunctionExpression:exit"() { + lastNodeType = "FunctionExpression"; + }, + "ArrowFunctionExpression:exit"() { + lastNodeType = "ArrowFunctionExpression"; } - assert(node.type === lastNodeType); - }, - "Program:exit"() { - lastNodeType = "Program"; - }, - "FunctionDeclaration:exit"() { - lastNodeType = "FunctionDeclaration"; - }, - "FunctionExpression:exit"() { - lastNodeType = "FunctionExpression"; - }, - "ArrowFunctionExpression:exit"() { - lastNodeType = "ArrowFunctionExpression"; - } - })); + }) + }); linter.verify( "foo(); function foo() {} var foo = function() {}; var foo = () => {};", { rules: { test: 2 }, env: { es6: true } } @@ -429,14 +443,16 @@ describe("CodePathAnalyzer", () => { it("should be fired in `while` loops", () => { let count = 0; - linter.defineRule("test", () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); - assert(node.type === "WhileStatement"); - } - })); + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentLoop(fromSegment, toSegment, node) { + count += 1; + assert(fromSegment instanceof CodePathSegment); + assert(toSegment instanceof CodePathSegment); + assert(node.type === "WhileStatement"); + } + }) + }); linter.verify( "while (a) { foo(); }", { rules: { test: 2 } } @@ -448,14 +464,16 @@ describe("CodePathAnalyzer", () => { it("should be fired in `do-while` loops", () => { let count = 0; - linter.defineRule("test", () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); - assert(node.type === "DoWhileStatement"); - } - })); + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentLoop(fromSegment, toSegment, node) { + count += 1; + assert(fromSegment instanceof CodePathSegment); + assert(toSegment instanceof CodePathSegment); + assert(node.type === "DoWhileStatement"); + } + }) + }); linter.verify( "do { foo(); } while (a);", { rules: { test: 2 } } @@ -467,21 +485,23 @@ describe("CodePathAnalyzer", () => { it("should be fired in `for` loops", () => { let count = 0; - linter.defineRule("test", () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentLoop(fromSegment, toSegment, node) { + count += 1; + assert(fromSegment instanceof CodePathSegment); + assert(toSegment instanceof CodePathSegment); - if (count === 1) { + if (count === 1) { - // connect path: "update" -> "test" - assert(node.parent.type === "ForStatement"); - } else if (count === 2) { - assert(node.type === "ForStatement"); + // connect path: "update" -> "test" + assert(node.parent.type === "ForStatement"); + } else if (count === 2) { + assert(node.type === "ForStatement"); + } } - } - })); + }) + }); linter.verify( "for (var i = 0; i < 10; ++i) { foo(); }", { rules: { test: 2 } } @@ -493,21 +513,23 @@ describe("CodePathAnalyzer", () => { it("should be fired in `for-in` loops", () => { let count = 0; - linter.defineRule("test", () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentLoop(fromSegment, toSegment, node) { + count += 1; + assert(fromSegment instanceof CodePathSegment); + assert(toSegment instanceof CodePathSegment); - if (count === 1) { + if (count === 1) { - // connect path: "right" -> "left" - assert(node.parent.type === "ForInStatement"); - } else if (count === 2) { - assert(node.type === "ForInStatement"); + // connect path: "right" -> "left" + assert(node.parent.type === "ForInStatement"); + } else if (count === 2) { + assert(node.type === "ForInStatement"); + } } - } - })); + }) + }); linter.verify( "for (var k in obj) { foo(); }", { rules: { test: 2 } } @@ -519,21 +541,23 @@ describe("CodePathAnalyzer", () => { it("should be fired in `for-of` loops", () => { let count = 0; - linter.defineRule("test", () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); + linter.defineRule("test", { + create: () => ({ + onCodePathSegmentLoop(fromSegment, toSegment, node) { + count += 1; + assert(fromSegment instanceof CodePathSegment); + assert(toSegment instanceof CodePathSegment); - if (count === 1) { + if (count === 1) { - // connect path: "right" -> "left" - assert(node.parent.type === "ForOfStatement"); - } else if (count === 2) { - assert(node.type === "ForOfStatement"); + // connect path: "right" -> "left" + assert(node.parent.type === "ForOfStatement"); + } else if (count === 2) { + assert(node.type === "ForOfStatement"); + } } - } - })); + }) + }); linter.verify( "for (var x of xs) { foo(); }", { rules: { test: 2 }, env: { es6: true } } @@ -555,11 +579,13 @@ describe("CodePathAnalyzer", () => { assert(expected.length > 0, "/*expected */ comments not found."); - linter.defineRule("test", () => ({ - onCodePathEnd(codePath) { - actual.push(debug.makeDotArrows(codePath)); - } - })); + linter.defineRule("test", { + create: () => ({ + onCodePathEnd(codePath) { + actual.push(debug.makeDotArrows(codePath)); + } + }) + }); const messages = linter.verify(source, { parserOptions: { ecmaVersion: 2022 }, rules: { test: 2 } 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 40fb017..7f1d738 100644 --- a/eslint/tests/lib/linter/code-path-analysis/code-path.js +++ b/eslint/tests/lib/linter/code-path-analysis/code-path.js @@ -25,11 +25,13 @@ const linter = new Linter(); function parseCodePaths(code) { const retv = []; - linter.defineRule("test", () => ({ - onCodePathStart(codePath) { - retv.push(codePath); - } - })); + linter.defineRule("test", { + create: () => ({ + onCodePathStart(codePath) { + retv.push(codePath); + } + }) + }); linter.verify(code, { rules: { test: 2 }, diff --git a/eslint/tests/lib/linter/linter.js b/eslint/tests/lib/linter/linter.js index 407194d..be1380f 100644 --- a/eslint/tests/lib/linter/linter.js +++ b/eslint/tests/lib/linter/linter.js @@ -80,11 +80,13 @@ describe("Linter", () => { it("an error should be thrown when an error occurs inside of an event handler", () => { const config = { rules: { checker: "error" } }; - linter.defineRule("checker", () => ({ - Program() { - throw new Error("Intentional error."); - } - })); + linter.defineRule("checker", { + create: () => ({ + Program() { + throw new Error("Intentional error."); + } + }) + }); assert.throws(() => { linter.verify(code, config, filename); @@ -94,7 +96,9 @@ describe("Linter", () => { it("does not call rule listeners with a `this` value", () => { const spy = sinon.spy(); - linter.defineRule("checker", () => ({ Program: spy })); + linter.defineRule("checker", { + create: () => ({ Program: spy }) + }); linter.verify("foo", { rules: { checker: "error" } }); assert(spy.calledOnce, "Rule should have been called"); assert.strictEqual(spy.firstCall.thisValue, void 0, "this value should be undefined"); @@ -103,14 +107,17 @@ describe("Linter", () => { it("does not allow listeners to use special EventEmitter values", () => { const spy = sinon.spy(); - linter.defineRule("checker", () => ({ newListener: spy })); + linter.defineRule("checker", { + create: () => ({ newListener: spy }) + }); linter.verify("foo", { rules: { checker: "error", "no-undef": "error" } }); assert(spy.notCalled); }); it("has all the `parent` properties on nodes when the rule listeners are created", () => { const spy = sinon.spy(context => { - const ast = context.getSourceCode().ast; + assert.strictEqual(context.getSourceCode(), context.sourceCode); + const ast = context.sourceCode.ast; assert.strictEqual(ast.body[0].parent, ast); assert.strictEqual(ast.body[0].expression.parent, ast.body[0]); @@ -120,7 +127,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify("foo + bar", { rules: { checker: "error" } }); assert(spy.calledOnce); @@ -136,7 +143,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, { rules: { checker: "error" } }); assert(spy.calledOnce); }); @@ -148,7 +155,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, { rules: { checker: "error" } }); assert(spy.calledOnce); }); @@ -160,7 +167,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, { rules: { checker: "error" } }); assert(spy.calledOnce); }); @@ -172,7 +179,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, { rules: { checker: "error" } }); assert(spy.calledOnce); }); @@ -184,7 +191,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, { rules: { checker: "error" } }); assert(spy.calledOnce); }); @@ -350,11 +357,13 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - assert.strictEqual(context.getSource(), TEST_CODE); - }); - return { Program: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual(context.getSource(), TEST_CODE); + }); + return { Program: spy }; + } }); linter.verify(code, config); @@ -365,11 +374,13 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), TEST_CODE); - }); - return { Program: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node), TEST_CODE); + }); + return { Program: spy }; + } }); linter.verify(code, config); @@ -380,11 +391,13 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2, 0), TEST_CODE); - }); - return { Program: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 2, 0), TEST_CODE); + }); + return { Program: spy }; + } }); linter.verify(code, config); @@ -395,11 +408,13 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), "6 * 7"); - }); - return { BinaryExpression: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node), "6 * 7"); + }); + return { BinaryExpression: spy }; + } }); linter.verify(code, config); @@ -410,11 +425,13 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2), "= 6 * 7"); - }); - return { BinaryExpression: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 2), "= 6 * 7"); + }); + return { BinaryExpression: spy }; + } }); linter.verify(code, config); @@ -425,11 +442,13 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;"); - }); - return { BinaryExpression: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;"); + }); + return { BinaryExpression: spy }; + } }); linter.verify(code, config); @@ -440,11 +459,13 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2, 1), "= 6 * 7;"); - }); - return { BinaryExpression: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 2, 1), "= 6 * 7;"); + }); + return { BinaryExpression: spy }; + } }); linter.verify(code, config); @@ -461,13 +482,15 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const ancestors = context.getAncestors(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const ancestors = context.getAncestors(); - assert.strictEqual(ancestors.length, 3); - }); - return { BinaryExpression: spy }; + assert.strictEqual(ancestors.length, 3); + }); + return { BinaryExpression: spy }; + } }); linter.verify(code, config, filename, true); @@ -478,14 +501,16 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const ancestors = context.getAncestors(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const ancestors = context.getAncestors(); - assert.strictEqual(ancestors.length, 0); - }); + assert.strictEqual(ancestors.length, 0); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -503,7 +528,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, config); assert(spy.calledOnce); }); @@ -515,7 +540,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, config); assert(spy.calledOnce); }); @@ -530,7 +555,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, config); assert(spy.calledOnce); }); @@ -542,7 +567,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, config); assert(spy.calledOnce); }); @@ -560,7 +585,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, config); assert(spy.calledOnce); }); @@ -578,7 +603,7 @@ describe("Linter", () => { return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, config); assert(spy.calledOnce); }); @@ -592,13 +617,15 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "global"); - }); - return { Program: spy }; + assert.strictEqual(scope.type, "global"); + }); + return { Program: spy }; + } }); linter.verify(code, config); @@ -609,13 +636,15 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - }); - return { FunctionDeclaration: spy }; + assert.strictEqual(scope.type, "function"); + }); + return { FunctionDeclaration: spy }; + } }); linter.verify(code, config); @@ -626,14 +655,16 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.id.name, "foo"); - }); - return { LabeledStatement: spy }; + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.id.name, "foo"); + }); + return { LabeledStatement: spy }; + } }); linter.verify(code, config); @@ -644,15 +675,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "ArrowFunctionExpression"); - }); + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.type, "ArrowFunctionExpression"); + }); - return { ReturnStatement: spy }; + return { ReturnStatement: spy }; + } }); linter.verify(code, config); @@ -663,15 +696,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "switch"); - assert.strictEqual(scope.block.type, "SwitchStatement"); - }); + assert.strictEqual(scope.type, "switch"); + assert.strictEqual(scope.block.type, "SwitchStatement"); + }); - return { SwitchStatement: spy }; + return { SwitchStatement: spy }; + } }); linter.verify("switch(foo){ case 'a': var b = 'foo'; }", config); @@ -682,15 +717,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.block.type, "BlockStatement"); - }); + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.block.type, "BlockStatement"); + }); - return { BlockStatement: spy }; + return { BlockStatement: spy }; + } }); linter.verify("var x; {let y = 1}", config); @@ -701,15 +738,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.block.type, "BlockStatement"); - }); + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.block.type, "BlockStatement"); + }); - return { BlockStatement: spy }; + return { BlockStatement: spy }; + } }); linter.verify("if (true) { let x = 1 }", config); @@ -720,15 +759,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "FunctionDeclaration"); - }); + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.type, "FunctionDeclaration"); + }); - return { FunctionDeclaration: spy }; + return { FunctionDeclaration: spy }; + } }); linter.verify("function foo() {}", config); @@ -739,15 +780,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "FunctionExpression"); - }); + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.type, "FunctionExpression"); + }); - return { FunctionExpression: spy }; + return { FunctionExpression: spy }; + } }); linter.verify("(function foo() {})();", config); @@ -758,15 +801,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "catch"); - assert.strictEqual(scope.block.type, "CatchClause"); - }); + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block.type, "CatchClause"); + }); - return { CatchClause: spy }; + return { CatchClause: spy }; + } }); linter.verify("try {} catch (err) {}", config); @@ -777,14 +822,16 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "module"); - }); + assert.strictEqual(scope.type, "module"); + }); - return { AssignmentExpression: spy }; + return { AssignmentExpression: spy }; + } }); linter.verify("var foo = {}; foo.bar = 1;", config); @@ -795,14 +842,16 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, ecmaFeatures: { globalReturn: true } } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - }); + assert.strictEqual(scope.type, "function"); + }); - return { AssignmentExpression: spy }; + return { AssignmentExpression: spy }; + } }); linter.verify("var foo = {}; foo.bar = 1;", config); @@ -815,17 +864,19 @@ describe("Linter", () => { const code = "var a = 1, b = 2;"; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + assert.isTrue(context.markVariableAsUsed("a")); - const scope = context.getScope(); + const scope = context.getScope(); - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } }); linter.verify(code, { rules: { checker: "error" } }); @@ -835,17 +886,19 @@ describe("Linter", () => { const code = "function abc(a, b) { return 1; }"; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + assert.isTrue(context.markVariableAsUsed("a")); - const scope = context.getScope(); + const scope = context.getScope(); - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); - return { ReturnStatement: spy }; + return { ReturnStatement: spy }; + } }); linter.verify(code, { rules: { checker: "error" } }); @@ -855,18 +908,20 @@ describe("Linter", () => { const code = "var a, b; function abc() { return 1; }"; let returnSpy, exitSpy; - linter.defineRule("checker", context => { - returnSpy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); - }); - exitSpy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + returnSpy = sinon.spy(() => { + assert.isTrue(context.markVariableAsUsed("a")); + }); + exitSpy = sinon.spy(() => { + const scope = context.getScope(); - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); - return { ReturnStatement: returnSpy, "Program:exit": exitSpy }; + return { ReturnStatement: returnSpy, "Program:exit": exitSpy }; + } }); linter.verify(code, { rules: { checker: "error" } }); @@ -878,18 +933,20 @@ describe("Linter", () => { const code = "var a = 1, b = 2;"; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const globalScope = context.getScope(), - childScope = globalScope.childScopes[0]; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const globalScope = context.getScope(), + childScope = globalScope.childScopes[0]; - assert.isTrue(context.markVariableAsUsed("a")); + assert.isTrue(context.markVariableAsUsed("a")); - assert.isTrue(getVariable(childScope, "a").eslintUsed); - assert.isUndefined(getVariable(childScope, "b").eslintUsed); - }); + assert.isTrue(getVariable(childScope, "a").eslintUsed); + assert.isUndefined(getVariable(childScope, "b").eslintUsed); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } }); linter.verify(code, { rules: { checker: "error" }, env: { node: true } }); @@ -900,18 +957,20 @@ describe("Linter", () => { const code = "var a = 1, b = 2;"; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const globalScope = context.getScope(), - childScope = globalScope.childScopes[0]; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const globalScope = context.getScope(), + childScope = globalScope.childScopes[0]; - assert.isTrue(context.markVariableAsUsed("a")); + assert.isTrue(context.markVariableAsUsed("a")); - assert.isTrue(getVariable(childScope, "a").eslintUsed); - assert.isUndefined(getVariable(childScope, "b").eslintUsed); - }); + assert.isTrue(getVariable(childScope, "a").eslintUsed); + assert.isUndefined(getVariable(childScope, "b").eslintUsed); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }, filename, true); @@ -922,12 +981,14 @@ describe("Linter", () => { const code = "var a = 1, b = 2;"; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - assert.isFalse(context.markVariableAsUsed("c")); - }); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + assert.isFalse(context.markVariableAsUsed("c")); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } }); linter.verify(code, { rules: { checker: "error" } }); @@ -948,13 +1009,15 @@ describe("Linter", () => { spyIdentifier = sinon.spy(), spyBinaryExpression = sinon.spy(); - linter.defineRule("checker", () => ({ - Literal: spyLiteral, - VariableDeclarator: spyVariableDeclarator, - VariableDeclaration: spyVariableDeclaration, - Identifier: spyIdentifier, - BinaryExpression: spyBinaryExpression - })); + linter.defineRule("checker", { + create: () => ({ + Literal: spyLiteral, + VariableDeclarator: spyVariableDeclarator, + VariableDeclaration: spyVariableDeclaration, + Identifier: spyIdentifier, + BinaryExpression: spyBinaryExpression + }) + }); const messages = linter.verify(code, config, filename, true); const suppressedMessages = linter.getSuppressedMessages(); @@ -969,11 +1032,13 @@ describe("Linter", () => { }); it("should throw an error if a rule reports a problem without a message", () => { - linter.defineRule("invalid-report", context => ({ - Program(node) { - context.report({ node }); - } - })); + linter.defineRule("invalid-report", { + create: context => ({ + Program(node) { + context.report({ node }); + } + }) + }); assert.throws( () => linter.verify("foo", { rules: { "invalid-report": "error" } }), @@ -987,11 +1052,13 @@ describe("Linter", () => { const code = "test-rule"; it("should pass settings to all rules", () => { - linter.defineRule(code, context => ({ - Literal(node) { - context.report(node, context.settings.info); - } - })); + linter.defineRule(code, { + create: context => ({ + Literal(node) { + context.report(node, context.settings.info); + } + }) + }); const config = { rules: {}, settings: { info: "Hello" } }; @@ -1006,13 +1073,15 @@ describe("Linter", () => { }); it("should not have any settings if they were not passed in", () => { - linter.defineRule(code, context => ({ - Literal(node) { - if (Object.getOwnPropertyNames(context.settings).length !== 0) { - context.report(node, "Settings should be empty"); + linter.defineRule(code, { + create: context => ({ + Literal(node) { + if (Object.getOwnPropertyNames(context.settings).length !== 0) { + context.report(node, "Settings should be empty"); + } } - } - })); + }) + }); const config = { rules: {} }; @@ -1037,9 +1106,11 @@ describe("Linter", () => { } }; - linter.defineRule("test-rule", sinon.mock().withArgs( - sinon.match({ parserOptions }) - ).returns({})); + linter.defineRule("test-rule", { + create: sinon.mock().withArgs( + sinon.match({ parserOptions }) + ).returns({}) + }); const config = { rules: { "test-rule": 2 }, parserOptions }; @@ -1050,9 +1121,11 @@ describe("Linter", () => { const parserOptions = {}; - linter.defineRule("test-rule", sinon.mock().withArgs( - sinon.match({ parserOptions }) - ).returns({})); + linter.defineRule("test-rule", { + create: sinon.mock().withArgs( + sinon.match({ parserOptions }) + ).returns({}) + }); const config = { rules: { "test-rule": 2 } }; @@ -1097,9 +1170,11 @@ describe("Linter", () => { const alternateParser = "esprima"; linter.defineParser("esprima", esprima); - linter.defineRule("test-rule", sinon.mock().withArgs( - sinon.match({ parserPath: alternateParser }) - ).returns({})); + linter.defineRule("test-rule", { + create: sinon.mock().withArgs( + sinon.match({ parserPath: alternateParser }) + ).returns({}) + }); const config = { rules: { "test-rule": 2 }, parser: alternateParser }; @@ -1119,14 +1194,16 @@ describe("Linter", () => { it("should expose parser services when using parseForESLint() and services are specified", () => { linter.defineParser("enhanced-parser", testParsers.enhancedParser); - linter.defineRule("test-service-rule", context => ({ - Literal(node) { - context.report({ - node, - message: context.parserServices.test.getMessage() - }); - } - })); + linter.defineRule("test-service-rule", { + create: context => ({ + Literal(node) { + context.report({ + node, + message: context.parserServices.test.getMessage() + }); + } + }) + }); const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" }; const messages = linter.verify("0", config, filename); @@ -1139,14 +1216,16 @@ describe("Linter", () => { it("should use the same parserServices if source code object is reused", () => { linter.defineParser("enhanced-parser", testParsers.enhancedParser); - linter.defineRule("test-service-rule", context => ({ - Literal(node) { - context.report({ - node, - message: context.parserServices.test.getMessage() - }); - } - })); + linter.defineRule("test-service-rule", { + create: context => ({ + Literal(node) { + context.report({ + node, + message: context.parserServices.test.getMessage() + }); + } + }) + }); const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" }; const messages = linter.verify("0", config, filename); @@ -1165,9 +1244,11 @@ describe("Linter", () => { }); it("should pass parser as parserPath to all rules when default parser is used", () => { - linter.defineRule("test-rule", sinon.mock().withArgs( - sinon.match({ parserPath: "espree" }) - ).returns({})); + linter.defineRule("test-rule", { + create: sinon.mock().withArgs( + sinon.match({ parserPath: "espree" }) + ).returns({}) + }); const config = { rules: { "test-rule": 2 } }; @@ -1277,38 +1358,40 @@ describe("Linter", () => { `; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); - const a = getVariable(scope, "a"), - b = getVariable(scope, "b"), - c = getVariable(scope, "c"), - d = getVariable(scope, "d"), - e = getVariable(scope, "e"), - f = getVariable(scope, "f"), - mathGlobal = getVariable(scope, "Math"), - arrayGlobal = getVariable(scope, "Array"), - configGlobal = getVariable(scope, "ConfigGlobal"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); + const a = getVariable(scope, "a"), + b = getVariable(scope, "b"), + c = getVariable(scope, "c"), + d = getVariable(scope, "d"), + e = getVariable(scope, "e"), + f = getVariable(scope, "f"), + mathGlobal = getVariable(scope, "Math"), + arrayGlobal = getVariable(scope, "Array"), + configGlobal = getVariable(scope, "ConfigGlobal"); - assert.strictEqual(a.name, "a"); - assert.strictEqual(a.writeable, false); - assert.strictEqual(b.name, "b"); - assert.strictEqual(b.writeable, true); - assert.strictEqual(c.name, "c"); - assert.strictEqual(c.writeable, false); - assert.strictEqual(d.name, "d"); - assert.strictEqual(d.writeable, false); - assert.strictEqual(e.name, "e"); - assert.strictEqual(e.writeable, true); - assert.strictEqual(f.name, "f"); - assert.strictEqual(f.writeable, true); - assert.strictEqual(mathGlobal, null); - assert.strictEqual(arrayGlobal, null); - assert.strictEqual(configGlobal.name, "ConfigGlobal"); - assert.strictEqual(configGlobal.writeable, false); - }); + assert.strictEqual(a.name, "a"); + assert.strictEqual(a.writeable, false); + assert.strictEqual(b.name, "b"); + assert.strictEqual(b.writeable, true); + assert.strictEqual(c.name, "c"); + assert.strictEqual(c.writeable, false); + assert.strictEqual(d.name, "d"); + assert.strictEqual(d.writeable, false); + assert.strictEqual(e.name, "e"); + assert.strictEqual(e.writeable, true); + assert.strictEqual(f.name, "f"); + assert.strictEqual(f.writeable, true); + assert.strictEqual(mathGlobal, null); + assert.strictEqual(arrayGlobal, null); + assert.strictEqual(configGlobal.name, "ConfigGlobal"); + assert.strictEqual(configGlobal.writeable, false); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1323,22 +1406,24 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - a = getVariable(scope, "a"), - b = getVariable(scope, "b"), - c = getVariable(scope, "c"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + a = getVariable(scope, "a"), + b = getVariable(scope, "b"), + c = getVariable(scope, "c"); - assert.strictEqual(a.name, "a"); - assert.strictEqual(a.writeable, false); - assert.strictEqual(b.name, "b"); - assert.strictEqual(b.writeable, true); - assert.strictEqual(c.name, "c"); - assert.strictEqual(c.writeable, false); - }); + assert.strictEqual(a.name, "a"); + assert.strictEqual(a.writeable, false); + assert.strictEqual(b.name, "b"); + assert.strictEqual(b.writeable, true); + assert.strictEqual(c.name, "c"); + assert.strictEqual(c.writeable, false); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1362,17 +1447,19 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - exports = getVariable(scope, "exports"), - window = getVariable(scope, "window"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + exports = getVariable(scope, "exports"), + window = getVariable(scope, "window"); - assert.strictEqual(exports.writeable, true); - assert.strictEqual(window.writeable, false); - }); + assert.strictEqual(exports.writeable, true); + assert.strictEqual(window.writeable, false); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1387,17 +1474,19 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - exports = getVariable(scope, "exports"), - window = getVariable(scope, "window"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + exports = getVariable(scope, "exports"), + window = getVariable(scope, "window"); - assert.strictEqual(exports.writeable, true); - assert.strictEqual(window, null); - }); + assert.strictEqual(exports.writeable, true); + assert.strictEqual(window, null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1419,15 +1508,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse.eslintUsed, true); - }); + assert.strictEqual(horse.eslintUsed, true); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1439,15 +1530,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse, null); - }); + assert.strictEqual(horse, null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1459,15 +1552,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse.eslintUsed, true); - }); + assert.strictEqual(horse.eslintUsed, true); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1479,15 +1574,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse, null); // there is no global scope at all - }); + assert.strictEqual(horse, null); // there is no global scope at all + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1499,15 +1596,17 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, env: { node: true } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse, null); // there is no global scope at all - }); + assert.strictEqual(horse, null); // there is no global scope at all + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1522,14 +1621,16 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(getVariable(scope, "a"), null); - }); + assert.strictEqual(getVariable(scope, "a"), null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1544,17 +1645,19 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(getVariable(scope, "a"), null); - assert.strictEqual(getVariable(scope, "b"), null); - assert.strictEqual(getVariable(scope, "foo"), null); - assert.strictEqual(getVariable(scope, "c"), null); - }); + assert.strictEqual(getVariable(scope, "a"), null); + assert.strictEqual(getVariable(scope, "b"), null); + assert.strictEqual(getVariable(scope, "foo"), null); + assert.strictEqual(getVariable(scope, "c"), null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1569,16 +1672,18 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.notStrictEqual(getVariable(scope, "Object"), null); - assert.notStrictEqual(getVariable(scope, "Array"), null); - assert.notStrictEqual(getVariable(scope, "undefined"), null); - }); + assert.notStrictEqual(getVariable(scope, "Object"), null); + assert.notStrictEqual(getVariable(scope, "Array"), null); + assert.notStrictEqual(getVariable(scope, "undefined"), null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1589,16 +1694,18 @@ describe("Linter", () => { const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(getVariable(scope, "Promise"), null); - assert.strictEqual(getVariable(scope, "Symbol"), null); - assert.strictEqual(getVariable(scope, "WeakMap"), null); - }); + assert.strictEqual(getVariable(scope, "Promise"), null); + assert.strictEqual(getVariable(scope, "Symbol"), null); + assert.strictEqual(getVariable(scope, "WeakMap"), null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1609,16 +1716,18 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, env: { es6: true } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.notStrictEqual(getVariable(scope, "Promise"), null); - assert.notStrictEqual(getVariable(scope, "Symbol"), null); - assert.notStrictEqual(getVariable(scope, "WeakMap"), null); - }); + assert.notStrictEqual(getVariable(scope, "Promise"), null); + assert.notStrictEqual(getVariable(scope, "Symbol"), null); + assert.notStrictEqual(getVariable(scope, "WeakMap"), null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1629,16 +1738,18 @@ describe("Linter", () => { const config = { rules: { checker: "error" }, globals: { Promise: "off", Symbol: "off", WeakMap: "off" }, env: { es6: true } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(getVariable(scope, "Promise"), null); - assert.strictEqual(getVariable(scope, "Symbol"), null); - assert.strictEqual(getVariable(scope, "WeakMap"), null); - }); + assert.strictEqual(getVariable(scope, "Promise"), null); + assert.strictEqual(getVariable(scope, "Symbol"), null); + assert.strictEqual(getVariable(scope, "WeakMap"), null); + }); - return { Program: spy }; + return { Program: spy }; + } }); linter.verify(code, config); @@ -1650,11 +1761,13 @@ describe("Linter", () => { const code = "new-rule"; it("can add a rule dynamically", () => { - linter.defineRule(code, context => ({ - Literal(node) { - context.report(node, "message"); - } - })); + linter.defineRule(code, { + create: context => ({ + Literal(node) { + context.report(node, "message"); + } + }) + }); const config = { rules: {} }; @@ -1680,12 +1793,14 @@ describe("Linter", () => { code.forEach(item => { config.rules[item] = 1; - newRules[item] = function(context) { - return { - Literal(node) { - context.report(node, "message"); - } - }; + newRules[item] = { + create(context) { + return { + Literal(node) { + context.report(node, "message"); + } + }; + } }; }); linter.defineRules(newRules); @@ -1709,11 +1824,14 @@ describe("Linter", () => { const code = "filename-rule"; it("has access to the filename", () => { - linter.defineRule(code, context => ({ - Literal(node) { - context.report(node, context.getFilename()); - } - })); + linter.defineRule(code, { + create: context => ({ + Literal(node) { + assert.strictEqual(context.getFilename(), context.filename); + context.report(node, context.filename); + } + }) + }); const config = { rules: {} }; @@ -1727,11 +1845,14 @@ describe("Linter", () => { }); it("has access to the physicalFilename", () => { - linter.defineRule(code, context => ({ - Literal(node) { - context.report(node, context.getPhysicalFilename()); - } - })); + linter.defineRule(code, { + create: context => ({ + Literal(node) { + assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); + context.report(node, context.physicalFilename); + } + }) + }); const config = { rules: {} }; @@ -1745,11 +1866,14 @@ describe("Linter", () => { }); it("defaults filename to ''", () => { - linter.defineRule(code, context => ({ - Literal(node) { - context.report(node, context.getFilename()); - } - })); + linter.defineRule(code, { + create: context => ({ + Literal(node) { + assert.strictEqual(context.getFilename(), context.filename); + context.report(node, context.filename); + } + }) + }); const config = { rules: {} }; @@ -2011,13 +2135,17 @@ describe("Linter", () => { describe("when evaluating code with comments to disable and enable configurable rule as part of plugin", () => { beforeEach(() => { - linter.defineRule("test-plugin/test-rule", context => ({ - Literal(node) { - if (node.value === "trigger violation") { - context.report(node, "Reporting violation."); - } + linter.defineRule("test-plugin/test-rule", { + create(context) { + return { + Literal(node) { + if (node.value === "trigger violation") { + context.report(node, "Reporting violation."); + } + } + }; } - })); + }); }); it("should not report a violation when inline comment enables plugin rule and there's no violation", () => { @@ -2045,11 +2173,13 @@ describe("Linter", () => { it("should report a violation when the report is right before the comment", () => { const code = " /* eslint-disable */ "; - linter.defineRule("checker", context => ({ - Program() { - context.report({ loc: { line: 1, column: 0 }, message: "foo" }); - } - })); + linter.defineRule("checker", { + create: context => ({ + Program() { + context.report({ loc: { line: 1, column: 0 }, message: "foo" }); + } + }) + }); const problems = linter.verify(code, { rules: { checker: "error" } }); const suppressedMessages = linter.getSuppressedMessages(); @@ -2061,11 +2191,13 @@ describe("Linter", () => { it("should not report a violation when the report is right at the start of the comment", () => { const code = " /* eslint-disable */ "; - linter.defineRule("checker", context => ({ - Program() { - context.report({ loc: { line: 1, column: 1 }, message: "foo" }); - } - })); + linter.defineRule("checker", { + create: context => ({ + Program() { + context.report({ loc: { line: 1, column: 1 }, message: "foo" }); + } + }) + }); const problems = linter.verify(code, { rules: { checker: "error" } }); const suppressedMessages = linter.getSuppressedMessages(); @@ -3279,7 +3411,7 @@ var a = "test2"; return {}; }); - linter.defineRule("checker", spy); + linter.defineRule("checker", { create: spy }); linter.verify(code, config); assert(spy.calledOnce); }); @@ -3289,11 +3421,13 @@ var a = "test2"; const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), "'123';"); - }); - return { ExpressionStatement: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node), "'123';"); + }); + return { ExpressionStatement: spy }; + } }); linter.verify(code, config); @@ -3431,11 +3565,13 @@ var a = "test2"; describe("when evaluating an empty string", () => { it("runs rules", () => { - linter.defineRule("no-programs", context => ({ - Program(node) { - context.report({ node, message: "No programs allowed." }); - } - })); + linter.defineRule("no-programs", { + create: context => ({ + Program(node) { + context.report({ node, message: "No programs allowed." }); + } + }) + }); assert.strictEqual( linter.verify("", { rules: { "no-programs": "error" } }).length, @@ -3638,13 +3774,14 @@ var a = "test2"; let ok = false; linter.defineRules({ - test(context) { - return { + test: { + create: context => ({ Program() { const scope = context.getScope(); - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const comments = sourceCode.getAllComments(); + assert.strictEqual(context.getSourceCode(), sourceCode); assert.strictEqual(1, comments.length); const foo = getVariable(scope, "foo"); @@ -3653,7 +3790,7 @@ var a = "test2"; ok = true; } - }; + }) } }); @@ -3810,11 +3947,14 @@ var a = "test2"; const linterWithOption = new Linter({ cwd }); let spy; - linterWithOption.defineRule("checker", context => { - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), cwd); - }); - return { Program: spy }; + linterWithOption.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), context.cwd); + assert.strictEqual(context.cwd, cwd); + }); + return { Program: spy }; + } }); linterWithOption.verify(code, config); @@ -3825,12 +3965,15 @@ var a = "test2"; let spy; const linterWithOption = new Linter({ }); - linterWithOption.defineRule("checker", context => { + linterWithOption.defineRule("checker", { + create(context) { - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), process.cwd()); - }); - return { Program: spy }; + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), context.cwd); + assert.strictEqual(context.cwd, process.cwd()); + }); + return { Program: spy }; + } }); linterWithOption.verify(code, config); @@ -3840,12 +3983,15 @@ var a = "test2"; it("should assign process.cwd() to it if the option is undefined", () => { let spy; - linter.defineRule("checker", context => { + linter.defineRule("checker", { + create(context) { - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), process.cwd()); - }); - return { Program: spy }; + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), context.cwd); + assert.strictEqual(context.cwd, process.cwd()); + }); + return { Program: spy }; + } }); linter.verify(code, config); @@ -4051,6 +4197,27 @@ var a = "test2"; assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); }); + it("reports no problems for no-fallthrough despite comment pattern match", () => { + const code = "switch (foo) { case 0: a(); \n// eslint-disable-next-line no-fallthrough\n case 1: }"; + const config = { + reportUnusedDisableDirectives: true, + rules: { + "no-fallthrough": 2 + } + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-fallthrough"); + }); + describe("autofix", () => { const alwaysReportsRule = { create(context) { @@ -4640,11 +4807,13 @@ var a = "test2"; const config = { rules: { checker: "error" } }; let spy; - linter.defineRule("checker", context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), "'123';"); - }); - return { ExpressionStatement: spy }; + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node), "'123';"); + }); + return { ExpressionStatement: spy }; + } }); linter.verify(code, config); @@ -4656,44 +4825,44 @@ var a = "test2"; describe("filenames", () => { it("should allow filename to be passed on options object", () => { const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.getFilename(), "foo.js"); + assert.strictEqual(context.filename, "foo.js"); return {}; }); - linter.defineRule("checker", filenameChecker); + linter.defineRule("checker", { create: filenameChecker }); linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" }); assert(filenameChecker.calledOnce); }); it("should allow filename to be passed as third argument", () => { const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.getFilename(), "bar.js"); + assert.strictEqual(context.filename, "bar.js"); return {}; }); - linter.defineRule("checker", filenameChecker); + linter.defineRule("checker", { create: filenameChecker }); linter.verify("foo;", { rules: { checker: "error" } }, "bar.js"); assert(filenameChecker.calledOnce); }); it("should default filename to when options object doesn't have filename", () => { const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.getFilename(), ""); + assert.strictEqual(context.filename, ""); return {}; }); - linter.defineRule("checker", filenameChecker); + linter.defineRule("checker", { create: filenameChecker }); linter.verify("foo;", { rules: { checker: "error" } }, {}); assert(filenameChecker.calledOnce); }); it("should default filename to when only two arguments are passed", () => { const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.getFilename(), ""); + assert.strictEqual(context.filename, ""); return {}; }); - linter.defineRule("checker", filenameChecker); + linter.defineRule("checker", { create: filenameChecker }); linter.verify("foo;", { rules: { checker: "error" } }); assert(filenameChecker.calledOnce); }); @@ -4702,33 +4871,36 @@ var a = "test2"; describe("physicalFilenames", () => { it("should be same as `filename` passed on options object, if no processors are used", () => { const physicalFilenameChecker = sinon.spy(context => { - assert.strictEqual(context.getPhysicalFilename(), "foo.js"); + assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); + assert.strictEqual(context.physicalFilename, "foo.js"); return {}; }); - linter.defineRule("checker", physicalFilenameChecker); + linter.defineRule("checker", { create: physicalFilenameChecker }); linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" }); assert(physicalFilenameChecker.calledOnce); }); it("should default physicalFilename to when options object doesn't have filename", () => { const physicalFilenameChecker = sinon.spy(context => { - assert.strictEqual(context.getPhysicalFilename(), ""); + assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); + assert.strictEqual(context.physicalFilename, ""); return {}; }); - linter.defineRule("checker", physicalFilenameChecker); + linter.defineRule("checker", { create: physicalFilenameChecker }); linter.verify("foo;", { rules: { checker: "error" } }, {}); assert(physicalFilenameChecker.calledOnce); }); it("should default physicalFilename to when only two arguments are passed", () => { const physicalFilenameChecker = sinon.spy(context => { - assert.strictEqual(context.getPhysicalFilename(), ""); + assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); + assert.strictEqual(context.physicalFilename, ""); return {}; }); - linter.defineRule("checker", physicalFilenameChecker); + linter.defineRule("checker", { create: physicalFilenameChecker }); linter.verify("foo;", { rules: { checker: "error" } }); assert(physicalFilenameChecker.calledOnce); }); @@ -4777,11 +4949,13 @@ var a = "test2"; let ecmaVersion = null; const config = { rules: { "ecma-version": 2 }, parserOptions: { ecmaVersion: "latest" } }; - linter.defineRule("ecma-version", context => ({ - Program() { - ecmaVersion = context.parserOptions.ecmaVersion; - } - })); + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.parserOptions.ecmaVersion; + } + }) + }); linter.verify("", config); assert.strictEqual(ecmaVersion, espree.latestEcmaVersion, "ecmaVersion should be 13"); }); @@ -4791,11 +4965,13 @@ var a = "test2"; const config = { rules: { "ecma-version": 2 }, parser: "custom-parser", parserOptions: { ecmaVersion: "latest" } }; linter.defineParser("custom-parser", testParsers.enhancedParser); - linter.defineRule("ecma-version", context => ({ - Program() { - ecmaVersion = context.parserOptions.ecmaVersion; - } - })); + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.parserOptions.ecmaVersion; + } + }) + }); linter.verify("", config); assert.strictEqual(ecmaVersion, "latest", "ecmaVersion should be latest"); }); @@ -4804,11 +4980,13 @@ var a = "test2"; let ecmaVersion = null; const config = { rules: { "ecma-version": 2 }, parserOptions: { ecmaVersion: "latest" } }; - linter.defineRule("ecma-version", context => ({ - Program() { - ecmaVersion = context.languageOptions.ecmaVersion; - } - })); + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.languageOptions.ecmaVersion; + } + }) + }); linter.verify("", config); assert.strictEqual(ecmaVersion, espree.latestEcmaVersion + 2009, "ecmaVersion should be 2022"); }); @@ -4818,11 +4996,13 @@ var a = "test2"; const config = { rules: { "ecma-version": 2 }, parser: "custom-parser", parserOptions: { ecmaVersion: "next" } }; linter.defineParser("custom-parser", testParsers.stubParser); - linter.defineRule("ecma-version", context => ({ - Program() { - ecmaVersion = context.languageOptions.ecmaVersion; - } - })); + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.languageOptions.ecmaVersion; + } + }) + }); linter.verify("", config); assert.strictEqual(ecmaVersion, espree.latestEcmaVersion + 2009, "ecmaVersion should be 2022"); }); @@ -4832,11 +5012,13 @@ var a = "test2"; const config = { rules: { "ecma-version": 2 }, parser: "custom-parser" }; linter.defineParser("custom-parser", testParsers.enhancedParser); - linter.defineRule("ecma-version", context => ({ - Program() { - ecmaVersion = context.languageOptions.ecmaVersion; - } - })); + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.languageOptions.ecmaVersion; + } + }) + }); linter.verify("", config); assert.strictEqual(ecmaVersion, 5, "ecmaVersion should be 5"); }); @@ -4844,11 +5026,13 @@ var a = "test2"; it("should pass normalized ecmaVersion to eslint-scope", () => { let blockScope = null; - linter.defineRule("block-scope", context => ({ - BlockStatement() { - blockScope = context.getScope(); - } - })); + linter.defineRule("block-scope", { + create: context => ({ + BlockStatement() { + blockScope = context.getScope(); + } + }) + }); linter.defineParser("custom-parser", { parse: (...args) => espree.parse(...args) }); @@ -5247,13 +5431,14 @@ var a = "test2"; let ok = false; linter.defineRules({ - test(context) { - return { + test: { + create: context => ({ Program() { const scope = context.getScope(); - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const comments = sourceCode.getAllComments(); + assert.strictEqual(context.getSourceCode(), sourceCode); assert.strictEqual(2, comments.length); const foo = getVariable(scope, "foo"); @@ -5273,7 +5458,7 @@ var a = "test2"; ok = true; } - }; + }) } }); @@ -5321,16 +5506,20 @@ var a = "test2"; let ast1 = null, ast2 = null; - linter.defineRule("save-ast1", () => ({ - Program(node) { - ast1 = node; - } - })); - linter.defineRule("save-ast2", () => ({ - Program(node) { - ast2 = node; - } - })); + linter.defineRule("save-ast1", { + create: () => ({ + Program(node) { + ast1 = node; + } + }) + }); + linter.defineRule("save-ast2", { + create: () => ({ + Program(node) { + ast2 = node; + } + }) + }); linter.verify("function render() { return
    {hello}
    }", { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, rules: { "save-ast1": 2 } }); linter.verify(linter.getSourceCode(), { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, rules: { "save-ast2": 2 } }); @@ -5365,7 +5554,7 @@ var a = "test2"; return {}; }); - linter.defineRule("foo-bar-baz", spy); + linter.defineRule("foo-bar-baz", { create: spy }); linter.verify("x", { rules: { "foo-bar-baz": "error" } }); assert(spy.calledOnce); }); @@ -5827,12 +6016,14 @@ var a = "test2"; function getScope(code, astSelector, ecmaVersion = 5) { let node, scope; - linter.defineRule("get-scope", context => ({ - [astSelector](node0) { - node = node0; - scope = context.getScope(); - } - })); + linter.defineRule("get-scope", { + create: context => ({ + [astSelector](node0) { + node = node0; + scope = context.getScope(); + } + }) + }); linter.verify( code, { @@ -6084,13 +6275,13 @@ var a = "test2"; let ok = false; linter.defineRules({ - test(context) { - return { + test: { + create: context => ({ Program() { scope = context.getScope(); ok = true; } - }; + }) } }); linter.verify(code, { rules: { test: 2 }, globals: { e: true, f: false } }); @@ -6177,78 +6368,80 @@ var a = "test2"; */ function verify(code, type, expectedNamesList) { linter.defineRules({ - test(context) { + test: { + create(context) { - /** - * Assert `context.getDeclaredVariables(node)` is empty. - * @param {ASTNode} node A node to check. - * @returns {void} - */ - function checkEmpty(node) { - assert.strictEqual(0, context.getDeclaredVariables(node).length); - } - const rule = { - Program: checkEmpty, - EmptyStatement: checkEmpty, - BlockStatement: checkEmpty, - ExpressionStatement: checkEmpty, - LabeledStatement: checkEmpty, - BreakStatement: checkEmpty, - ContinueStatement: checkEmpty, - WithStatement: checkEmpty, - SwitchStatement: checkEmpty, - ReturnStatement: checkEmpty, - ThrowStatement: checkEmpty, - TryStatement: checkEmpty, - WhileStatement: checkEmpty, - DoWhileStatement: checkEmpty, - ForStatement: checkEmpty, - ForInStatement: checkEmpty, - DebuggerStatement: checkEmpty, - ThisExpression: checkEmpty, - ArrayExpression: checkEmpty, - ObjectExpression: checkEmpty, - Property: checkEmpty, - SequenceExpression: checkEmpty, - UnaryExpression: checkEmpty, - BinaryExpression: checkEmpty, - AssignmentExpression: checkEmpty, - UpdateExpression: checkEmpty, - LogicalExpression: checkEmpty, - ConditionalExpression: checkEmpty, - CallExpression: checkEmpty, - NewExpression: checkEmpty, - MemberExpression: checkEmpty, - SwitchCase: checkEmpty, - Identifier: checkEmpty, - Literal: checkEmpty, - ForOfStatement: checkEmpty, - ArrowFunctionExpression: checkEmpty, - YieldExpression: checkEmpty, - TemplateLiteral: checkEmpty, - TaggedTemplateExpression: checkEmpty, - TemplateElement: checkEmpty, - ObjectPattern: checkEmpty, - ArrayPattern: checkEmpty, - RestElement: checkEmpty, - AssignmentPattern: checkEmpty, - ClassBody: checkEmpty, - MethodDefinition: checkEmpty, - MetaProperty: checkEmpty - }; - - rule[type] = function(node) { - const expectedNames = expectedNamesList.shift(); - const variables = context.getDeclaredVariables(node); - - assert(Array.isArray(expectedNames)); - assert(Array.isArray(variables)); - assert.strictEqual(expectedNames.length, variables.length); - for (let i = variables.length - 1; i >= 0; i--) { - assert.strictEqual(expectedNames[i], variables[i].name); + /** + * Assert `context.getDeclaredVariables(node)` is empty. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function checkEmpty(node) { + assert.strictEqual(0, context.getDeclaredVariables(node).length); } - }; - return rule; + const rule = { + Program: checkEmpty, + EmptyStatement: checkEmpty, + BlockStatement: checkEmpty, + ExpressionStatement: checkEmpty, + LabeledStatement: checkEmpty, + BreakStatement: checkEmpty, + ContinueStatement: checkEmpty, + WithStatement: checkEmpty, + SwitchStatement: checkEmpty, + ReturnStatement: checkEmpty, + ThrowStatement: checkEmpty, + TryStatement: checkEmpty, + WhileStatement: checkEmpty, + DoWhileStatement: checkEmpty, + ForStatement: checkEmpty, + ForInStatement: checkEmpty, + DebuggerStatement: checkEmpty, + ThisExpression: checkEmpty, + ArrayExpression: checkEmpty, + ObjectExpression: checkEmpty, + Property: checkEmpty, + SequenceExpression: checkEmpty, + UnaryExpression: checkEmpty, + BinaryExpression: checkEmpty, + AssignmentExpression: checkEmpty, + UpdateExpression: checkEmpty, + LogicalExpression: checkEmpty, + ConditionalExpression: checkEmpty, + CallExpression: checkEmpty, + NewExpression: checkEmpty, + MemberExpression: checkEmpty, + SwitchCase: checkEmpty, + Identifier: checkEmpty, + Literal: checkEmpty, + ForOfStatement: checkEmpty, + ArrowFunctionExpression: checkEmpty, + YieldExpression: checkEmpty, + TemplateLiteral: checkEmpty, + TaggedTemplateExpression: checkEmpty, + TemplateElement: checkEmpty, + ObjectPattern: checkEmpty, + ArrayPattern: checkEmpty, + RestElement: checkEmpty, + AssignmentPattern: checkEmpty, + ClassBody: checkEmpty, + MethodDefinition: checkEmpty, + MetaProperty: checkEmpty + }; + + rule[type] = function(node) { + const expectedNames = expectedNamesList.shift(); + const variables = context.getDeclaredVariables(node); + + assert(Array.isArray(expectedNames)); + assert(Array.isArray(variables)); + assert.strictEqual(expectedNames.length, variables.length); + for (let i = variables.length - 1; i >= 0; i--) { + assert.strictEqual(expectedNames[i], variables[i].name); + } + }; + return rule; + } } }); linter.verify(code, { @@ -6548,7 +6741,9 @@ var a = "test2"; }); it("loading rule in one doesn't change the other", () => { - linter1.defineRule("mock-rule", () => ({})); + linter1.defineRule("mock-rule", { + create: () => ({}) + }); assert.isTrue(linter1.getRules().has("mock-rule"), "mock rule is present"); assert.isFalse(linter2.getRules().has("mock-rule"), "mock rule is not present"); @@ -6565,13 +6760,19 @@ var a = "test2"; receivedPhysicalFilenames = []; // A rule that always reports the AST with a message equal to the source text - linter.defineRule("report-original-text", context => ({ - Program(ast) { - receivedFilenames.push(context.getFilename()); - receivedPhysicalFilenames.push(context.getPhysicalFilename()); - context.report({ node: ast, message: context.getSourceCode().text }); - } - })); + linter.defineRule("report-original-text", { + create: context => ({ + Program(ast) { + assert.strictEqual(context.getFilename(), context.filename); + assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); + + receivedFilenames.push(context.filename); + receivedPhysicalFilenames.push(context.physicalFilename); + + context.report({ node: ast, message: context.sourceCode.text }); + } + }) + }); }); describe("preprocessors", () => { @@ -6681,7 +6882,8 @@ var a = "test2"; severity: 2, message: "Preprocessing error: Invalid syntax", line: 1, - column: 1 + column: 1, + nodeType: null } ]); }); @@ -6907,11 +7109,13 @@ var a = "test2"; }); 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: "" })); - } - })); + linter.defineRule("test-rule", { + create: context => ({ + Program(node) { + context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); + } + }) + }); assert.throws(() => { linter.verify("0", { rules: { "test-rule": "error" } }); @@ -7000,7 +7204,9 @@ var a = "test2"; * This test focuses on the instance of https://github.com/eslint/eslint/blob/v2.0.0-alpha-2/conf/environments.js#L26-L28 * This `verify()` takes the instance and runs https://github.com/eslint/eslint/blob/v2.0.0-alpha-2/lib/eslint.js#L416 */ - linter.defineRule("test", () => ({})); + linter.defineRule("test", { + create: () => ({}) + }); linter.verify("var a = 0;", { env: { node: true }, parserOptions: { ecmaVersion: 6, sourceType: "module" }, @@ -7010,13 +7216,15 @@ var a = "test2"; // This `verify()` takes the instance and tests that the instance was not modified. let ok = false; - linter.defineRule("test", context => { - assert( - context.parserOptions.ecmaFeatures.globalReturn, - "`ecmaFeatures.globalReturn` of the node environment should not be modified." - ); - ok = true; - return {}; + linter.defineRule("test", { + create(context) { + assert( + context.parserOptions.ecmaFeatures.globalReturn, + "`ecmaFeatures.globalReturn` of the node environment should not be modified." + ); + ok = true; + return {}; + } }); linter.verify("var a = 0;", { env: { node: true }, @@ -7029,13 +7237,17 @@ var a = "test2"; it("should throw when rule's create() function does not return an object", () => { const config = { rules: { checker: "error" } }; - linter.defineRule("checker", () => null); // returns null + linter.defineRule("checker", { + create: () => null + }); // returns null assert.throws(() => { linter.verify("abc", config, filename); }, "The create() function for rule 'checker' did not return an object."); - linter.defineRule("checker", () => {}); // returns undefined + linter.defineRule("checker", { + create() {} + }); // returns undefined assert.throws(() => { linter.verify("abc", config, filename); @@ -7111,11 +7323,13 @@ var a = "test2"; const nodes = []; - linter.defineRule("collect-node-types", () => ({ - "*"(node) { - nodes.push(node.type); - } - })); + linter.defineRule("collect-node-types", { + create: () => ({ + "*"(node) { + nodes.push(node.type); + } + }) + }); linter.defineParser("non-js-parser", testParsers.nonJSParser); @@ -7166,21 +7380,27 @@ var a = "test2"; beforeEach(() => { types = []; firstChildNodes = []; - linter.defineRule("collect-node-types", () => ({ - "*"(node) { - types.push(node.type); - } - })); - linter.defineRule("save-scope-manager", context => { - scopeManager = context.getSourceCode().scopeManager; - - return {}; + linter.defineRule("collect-node-types", { + create: () => ({ + "*"(node) { + types.push(node.type); + } + }) }); - linter.defineRule("esquery-option", () => ({ - ":first-child"(node) { - firstChildNodes.push(node); + linter.defineRule("save-scope-manager", { + create(context) { + scopeManager = context.sourceCode.scopeManager; + + return {}; } - })); + }); + linter.defineRule("esquery-option", { + create: () => ({ + ":first-child"(node) { + firstChildNodes.push(node); + } + }) + }); linter.defineParser("enhanced-parser2", testParsers.enhancedParser2); linter.verify("@foo class A {}", { parser: "enhanced-parser2", @@ -7211,11 +7431,13 @@ var a = "test2"; it("should use the same visitorKeys if the source code object is reused", () => { const types2 = []; - linter.defineRule("collect-node-types", () => ({ - "*"(node) { - types2.push(node.type); - } - })); + linter.defineRule("collect-node-types", { + create: () => ({ + "*"(node) { + types2.push(node.type); + } + }) + }); linter.verify(sourceCode, { rules: { "collect-node-types": "error" @@ -7242,11 +7464,13 @@ var a = "test2"; beforeEach(() => { linter.defineParser("enhanced-parser3", testParsers.enhancedParser3); - linter.defineRule("save-scope1", context => ({ - Program() { - scope = context.getScope(); - } - })); + linter.defineRule("save-scope1", { + create: context => ({ + Program() { + scope = context.getScope(); + } + }) + }); linter.verify("@foo class A {}", { parser: "enhanced-parser3", rules: { "save-scope1": 2 } }); sourceCode = linter.getSourceCode(); @@ -7263,11 +7487,13 @@ var a = "test2"; it("should use the same scope if the source code object is reused", () => { let scope2 = null; - linter.defineRule("save-scope2", context => ({ - Program() { - scope2 = context.getScope(); - } - })); + linter.defineRule("save-scope2", { + create: context => ({ + Program() { + scope2 = context.getScope(); + } + }) + }); linter.verify(sourceCode, { rules: { "save-scope2": 2 } }, "test.js"); assert(scope2 !== null); @@ -7410,12 +7636,12 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker(context) { - return { + checker: { + create: context => ({ Program() { assert.strictEqual(context.languageOptions.ecmaVersion, 2015); } - }; + }) } } } @@ -7434,12 +7660,12 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker(context) { - return { + checker: { + create: context => ({ Program() { assert.strictEqual(context.languageOptions.ecmaVersion, espree.latestEcmaVersion + 2009); } - }; + }) } } } @@ -7455,12 +7681,12 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker(context) { - return { + checker: { + create: context => ({ Program() { assert.strictEqual(context.languageOptions.ecmaVersion, 5); } - }; + }) } } } @@ -7479,12 +7705,12 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker(context) { - return { + checker: { + create: context => ({ Program() { assert.strictEqual(context.languageOptions.ecmaVersion, espree.latestEcmaVersion + 2009); } - }; + }) } } } @@ -7508,12 +7734,12 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker(context) { - return { + checker: { + create: context => ({ Program() { assert.strictEqual(context.languageOptions.sourceType, "module"); } - }; + }) } } } @@ -7529,12 +7755,12 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker(context) { - return { + checker: { + create: context => ({ Program() { assert.strictEqual(context.languageOptions.sourceType, "commonjs"); } - }; + }) } } } @@ -7669,15 +7895,8 @@ describe("Linter with FlatConfigArray", () => { }; const config = { - plugins: { - test: { - parsers: { - "test-parser": parser - } - } - }, languageOptions: { - parser: "test/test-parser" + parser } }; @@ -7695,9 +7914,11 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "test-rule": sinon.mock().withArgs( - sinon.match({ languageOptions: { parser: esprima } }) - ).returns({}) + "test-rule": { + create: sinon.mock().withArgs( + sinon.match({ languageOptions: { parser: esprima } }) + ).returns({}) + } } } }, @@ -7714,15 +7935,8 @@ describe("Linter with FlatConfigArray", () => { it("should use parseForESLint() in custom parser when custom parser is specified", () => { const config = { - plugins: { - test: { - parsers: { - "enhanced-parser": testParsers.enhancedParser - } - } - }, languageOptions: { - parser: "test/enhanced-parser" + parser: testParsers.enhancedParser } }; @@ -7738,23 +7952,22 @@ describe("Linter with FlatConfigArray", () => { const config = { plugins: { test: { - parsers: { - "enhanced-parser": testParsers.enhancedParser - }, rules: { - "test-service-rule": context => ({ - Literal(node) { - context.report({ - node, - message: context.parserServices.test.getMessage() - }); - } - }) + "test-service-rule": { + create: context => ({ + Literal(node) { + context.report({ + node, + message: context.parserServices.test.getMessage() + }); + } + }) + } } } }, languageOptions: { - parser: "test/enhanced-parser" + parser: testParsers.enhancedParser }, rules: { "test/test-service-rule": 2 @@ -7775,23 +7988,22 @@ describe("Linter with FlatConfigArray", () => { const config = { plugins: { test: { - parsers: { - "enhanced-parser": testParsers.enhancedParser - }, rules: { - "test-service-rule": context => ({ - Literal(node) { - context.report({ - node, - message: context.parserServices.test.getMessage() - }); - } - }) + "test-service-rule": { + create: context => ({ + Literal(node) { + context.report({ + node, + message: context.parserServices.test.getMessage() + }); + } + }) + } } } }, languageOptions: { - parser: "test/enhanced-parser" + parser: testParsers.enhancedParser }, rules: { "test/test-service-rule": 2 @@ -7829,7 +8041,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "test-rule": spy + "test-rule": { create: spy } } } }, @@ -7921,11 +8133,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "collect-node-types": () => ({ - "*"(node) { - nodes.push(node.type); - } - }) + "collect-node-types": { + create: () => ({ + "*"(node) { + nodes.push(node.type); + } + }) + } } } }, @@ -7988,21 +8202,27 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "collect-node-types": () => ({ - "*"(node) { - types.push(node.type); - } - }), - "save-scope-manager": context => { - scopeManager = context.getSourceCode().scopeManager; - - return {}; + "collect-node-types": { + create: () => ({ + "*"(node) { + types.push(node.type); + } + }) }, - "esquery-option": () => ({ - ":first-child"(node) { - firstChildNodes.push(node); + "save-scope-manager": { + create(context) { + scopeManager = context.sourceCode.scopeManager; + + return {}; } - }) + }, + "esquery-option": { + create: () => ({ + ":first-child"(node) { + firstChildNodes.push(node); + } + }) + } } } }, @@ -8041,11 +8261,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "collect-node-types": () => ({ - "*"(node) { - types2.push(node.type); - } - }) + "collect-node-types": { + create: () => ({ + "*"(node) { + types2.push(node.type); + } + }) + } } } }, @@ -8079,11 +8301,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "save-scope1": context => ({ - Program() { - scope = context.getScope(); - } - }) + "save-scope1": { + create: context => ({ + Program() { + scope = context.getScope(); + } + }) + } } } }, @@ -8114,11 +8338,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "save-scope2": context => ({ - Program() { - scope2 = context.getScope(); - } - }) + "save-scope2": { + create: context => ({ + Program() { + scope2 = context.getScope(); + } + }) + } } } }, @@ -8170,9 +8396,11 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "test-rule": sinon.mock().withArgs( - sinon.match({ languageOptions: { parserOptions } }) - ).returns({}) + "test-rule": { + create: sinon.mock().withArgs( + sinon.match({ languageOptions: { parserOptions } }) + ).returns({}) + } } } }, @@ -8193,17 +8421,19 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "test-rule": sinon.mock().withArgs( - sinon.match({ - languageOptions: { - parserOptions: { - ecmaFeatures: { - globalReturn: false + "test-rule": { + create: sinon.mock().withArgs( + sinon.match({ + languageOptions: { + parserOptions: { + ecmaFeatures: { + globalReturn: false + } } } - } - }) - ).returns({}) + }) + ).returns({}) + } } } }, @@ -8392,7 +8622,7 @@ describe("Linter with FlatConfigArray", () => { const code = "var enum;"; const messages = linter.verify(code, { languageOptions: { - ...ecmaVersion ? { ecmaVersion } : {}, + ...(ecmaVersion ? { ecmaVersion } : {}), sourceType: "script" } }, filename); @@ -8411,7 +8641,7 @@ describe("Linter with FlatConfigArray", () => { const code = "obj.enum; obj.function; var obj = { enum: 1, function: 2 };"; const messages = linter.verify(code, { languageOptions: { - ...ecmaVersion ? { ecmaVersion } : {}, + ...(ecmaVersion ? { ecmaVersion } : {}), sourceType: "script" } }, filename); @@ -8427,7 +8657,7 @@ describe("Linter with FlatConfigArray", () => { const code = ""; const messages = linter.verify(code, { languageOptions: { - ...ecmaVersion ? { ecmaVersion } : {}, + ...(ecmaVersion ? { ecmaVersion } : {}), sourceType: "script", parserOptions: { allowReserved: true @@ -8453,11 +8683,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - [ruleId]: context => ({ - Literal(node) { - context.report(node, context.settings.info); - } - }) + [ruleId]: { + create: context => ({ + Literal(node) { + context.report(node, context.settings.info); + } + }) + } } } }, @@ -8484,13 +8716,15 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - [ruleId]: context => ({ - Literal(node) { - if (Object.getOwnPropertyNames(context.settings).length !== 0) { - context.report(node, "Settings should be empty"); + [ruleId]: { + create: context => ({ + Literal(node) { + if (Object.getOwnPropertyNames(context.settings).length !== 0) { + context.report(node, "Settings should be empty"); + } } - } - }) + }) + } } } }, @@ -8582,7 +8816,7 @@ describe("Linter with FlatConfigArray", () => { assert.throws(() => { linter.verify(code, config, filename, true); - }, /Key "rules": Key "semi"/u); + }, /Key "rules": Key "semi": Expected severity/u); }); it("should process empty config", () => { @@ -8631,7 +8865,8 @@ describe("Linter with FlatConfigArray", () => { severity: 1, message: "No matching configuration found for filename.ts.", line: 0, - column: 0 + column: 0, + nodeType: null }); }); @@ -8645,7 +8880,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } } @@ -8665,11 +8900,14 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: () => ({ - Program() { - throw new Error("Intentional error."); - } - }) + checker: { + create: () => ({ + Program() { + throw new Error("Intentional error."); + } + + }) + } } } }, @@ -8687,9 +8925,11 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: () => ({ - Program: spy - }) + checker: { + create: () => ({ + Program: spy + }) + } } } }, @@ -8707,9 +8947,11 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: () => ({ - newListener: spy - }) + checker: { + create: () => ({ + newListener: spy + }) + } } } }, @@ -8725,7 +8967,8 @@ describe("Linter with FlatConfigArray", () => { it("should have all the `parent` properties on nodes when the rule visitors are created", () => { const spy = sinon.spy(context => { - const ast = context.getSourceCode().ast; + assert.strictEqual(context.getSourceCode(), context.sourceCode); + const ast = context.sourceCode.ast; assert.strictEqual(ast.body[0].parent, ast); assert.strictEqual(ast.body[0].expression.parent, ast.body[0]); @@ -8739,7 +8982,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -8763,14 +9006,16 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker() { - return { - Literal: spyLiteral, - VariableDeclarator: spyVariableDeclarator, - VariableDeclaration: spyVariableDeclaration, - Identifier: spyIdentifier, - BinaryExpression: spyBinaryExpression - }; + checker: { + create() { + return { + Literal: spyLiteral, + VariableDeclarator: spyVariableDeclarator, + VariableDeclaration: spyVariableDeclaration, + Identifier: spyIdentifier, + BinaryExpression: spyBinaryExpression + }; + } } } } @@ -8797,12 +9042,12 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "invalid-report"(context) { - return { + "invalid-report": { + create: context => ({ Program(node) { context.report({ node }); } - }; + }) } } } @@ -8831,11 +9076,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - [ruleId]: context => ({ - Literal(node) { - context.report(node, context.getFilename()); - } - }) + [ruleId]: { + create: context => ({ + Literal(node) { + context.report(node, context.getFilename()); + } + }) + } } } }, @@ -8857,11 +9104,76 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - [ruleId]: context => ({ - Literal(node) { - context.report(node, context.getFilename()); - } - }) + [ruleId]: { + create: context => ({ + Literal(node) { + context.report(node, context.getFilename()); + } + }) + } + } + } + }, + rules: { + [`test/${ruleId}`]: 1 + } + }; + + + const messages = linter.verify("0", config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages[0].message, ""); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("context.filename", () => { + const ruleId = "filename-rule"; + + it("has access to the filename", () => { + + const config = { + plugins: { + test: { + rules: { + [ruleId]: { + create: context => ({ + Literal(node) { + assert.strictEqual(context.getFilename(), context.filename); + context.report(node, context.filename); + } + }) + } + } + } + }, + rules: { + [`test/${ruleId}`]: 1 + } + }; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages[0].message, filename); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("defaults filename to ''", () => { + + const config = { + plugins: { + test: { + rules: { + [ruleId]: { + create: context => ({ + Literal(node) { + assert.strictEqual(context.getFilename(), context.filename); + context.report(node, context.filename); + } + }) + } } } }, @@ -8889,11 +9201,48 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - [ruleId]: context => ({ - Literal(node) { - context.report(node, context.getPhysicalFilename()); - } - }) + [ruleId]: { + create: context => ({ + Literal(node) { + context.report(node, context.getPhysicalFilename()); + } + }) + } + } + } + }, + rules: { + [`test/${ruleId}`]: 1 + } + }; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages[0].message, filename); + assert.strictEqual(suppressedMessages.length, 0); + }); + + }); + + describe("context.physicalFilename", () => { + + const ruleId = "filename-rule"; + + it("has access to the physicalFilename", () => { + + const config = { + plugins: { + test: { + rules: { + [ruleId]: { + create: context => ({ + Literal(node) { + assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); + context.report(node, context.physicalFilename); + } + }) + } } } }, @@ -8924,7 +9273,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -8946,7 +9295,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -8968,7 +9317,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -8990,7 +9339,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9012,7 +9361,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9036,11 +9385,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - assert.strictEqual(context.getSource(), TEST_CODE); - }); - return { Program: spy }; + checker: { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual(context.getSource(), TEST_CODE); + }); + return { Program: spy }; + } } } } @@ -9060,11 +9411,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), TEST_CODE); - }); - return { Program: spy }; + checker: { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node), TEST_CODE); + }); + return { Program: spy }; + } } } } @@ -9083,11 +9436,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2, 0), TEST_CODE); - }); - return { Program: spy }; + checker: { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 2, 0), TEST_CODE); + }); + return { Program: spy }; + } } } } @@ -9106,11 +9461,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), "6 * 7"); - }); - return { BinaryExpression: spy }; + checker: { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node), "6 * 7"); + }); + return { BinaryExpression: spy }; + } } } } @@ -9129,11 +9486,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2), "= 6 * 7"); - }); - return { BinaryExpression: spy }; + checker: { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 2), "= 6 * 7"); + }); + return { BinaryExpression: spy }; + } } } } @@ -9152,11 +9511,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;"); - }); - return { BinaryExpression: spy }; + checker: { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;"); + }); + return { BinaryExpression: spy }; + } } } } @@ -9175,11 +9536,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2, 1), "= 6 * 7;"); - }); - return { BinaryExpression: spy }; + checker: { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual(context.getSource(node, 2, 1), "= 6 * 7;"); + }); + return { BinaryExpression: spy }; + } } } } @@ -9204,13 +9567,15 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const ancestors = context.getAncestors(); + checker: { + create(context) { + spy = sinon.spy(() => { + const ancestors = context.getAncestors(); - assert.strictEqual(ancestors.length, 3); - }); - return { BinaryExpression: spy }; + assert.strictEqual(ancestors.length, 3); + }); + return { BinaryExpression: spy }; + } } } } @@ -9229,14 +9594,16 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const ancestors = context.getAncestors(); + checker: { + create(context) { + spy = sinon.spy(() => { + const ancestors = context.getAncestors(); - assert.strictEqual(ancestors.length, 0); - }); + assert.strictEqual(ancestors.length, 0); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -9262,7 +9629,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9283,7 +9650,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9307,7 +9674,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9328,7 +9695,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9355,7 +9722,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9382,7 +9749,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -9404,13 +9771,15 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "global"); - }); - return { Program: spy }; + assert.strictEqual(scope.type, "global"); + }); + return { Program: spy }; + } } } } @@ -9432,13 +9801,15 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - }); - return { FunctionDeclaration: spy }; + assert.strictEqual(scope.type, "function"); + }); + return { FunctionDeclaration: spy }; + } } } } @@ -9460,14 +9831,16 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.id.name, "foo"); - }); - return { LabeledStatement: spy }; + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.id.name, "foo"); + }); + return { LabeledStatement: spy }; + } } } } @@ -9490,15 +9863,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "ArrowFunctionExpression"); - }); + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.type, "ArrowFunctionExpression"); + }); - return { ReturnStatement: spy }; + return { ReturnStatement: spy }; + } } } } @@ -9521,15 +9896,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "switch"); - assert.strictEqual(scope.block.type, "SwitchStatement"); - }); + assert.strictEqual(scope.type, "switch"); + assert.strictEqual(scope.block.type, "SwitchStatement"); + }); - return { SwitchStatement: spy }; + return { SwitchStatement: spy }; + } } } } @@ -9551,15 +9928,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.block.type, "BlockStatement"); - }); + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.block.type, "BlockStatement"); + }); - return { BlockStatement: spy }; + return { BlockStatement: spy }; + } } } } @@ -9582,15 +9961,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.block.type, "BlockStatement"); - }); + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.block.type, "BlockStatement"); + }); - return { BlockStatement: spy }; + return { BlockStatement: spy }; + } } } } @@ -9613,15 +9994,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "FunctionDeclaration"); - }); + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.type, "FunctionDeclaration"); + }); - return { FunctionDeclaration: spy }; + return { FunctionDeclaration: spy }; + } } } } @@ -9644,15 +10027,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "FunctionExpression"); - }); + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block.type, "FunctionExpression"); + }); - return { FunctionExpression: spy }; + return { FunctionExpression: spy }; + } } } } @@ -9675,15 +10060,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "catch"); - assert.strictEqual(scope.block.type, "CatchClause"); - }); + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block.type, "CatchClause"); + }); - return { CatchClause: spy }; + return { CatchClause: spy }; + } } } } @@ -9705,14 +10092,16 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "module"); - }); + assert.strictEqual(scope.type, "module"); + }); - return { AssignmentExpression: spy }; + return { AssignmentExpression: spy }; + } } } } @@ -9736,14 +10125,16 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(scope.type, "function"); - }); + assert.strictEqual(scope.type, "function"); + }); - return { AssignmentExpression: spy }; + return { AssignmentExpression: spy }; + } } } } @@ -9775,12 +10166,14 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - "get-scope": context => ({ - [astSelector](node0) { - node = node0; - scope = context.getScope(); - } - }) + "get-scope": { + create: context => ({ + [astSelector](node0) { + node = node0; + scope = context.getScope(); + } + }) + } } } }, @@ -10042,13 +10435,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - test(context) { - return { + test: { + create: context => ({ Program() { scope = context.getScope(); ok = true; } - }; + }) } } } @@ -10152,78 +10545,81 @@ describe("Linter with FlatConfigArray", () => { test: { rules: { - test(context) { + test: { + create(context) { - /** - * Assert `context.getDeclaredVariables(node)` is empty. - * @param {ASTNode} node A node to check. - * @returns {void} - */ - function checkEmpty(node) { - assert.strictEqual(0, context.getDeclaredVariables(node).length); - } - const rule = { - Program: checkEmpty, - EmptyStatement: checkEmpty, - BlockStatement: checkEmpty, - ExpressionStatement: checkEmpty, - LabeledStatement: checkEmpty, - BreakStatement: checkEmpty, - ContinueStatement: checkEmpty, - WithStatement: checkEmpty, - SwitchStatement: checkEmpty, - ReturnStatement: checkEmpty, - ThrowStatement: checkEmpty, - TryStatement: checkEmpty, - WhileStatement: checkEmpty, - DoWhileStatement: checkEmpty, - ForStatement: checkEmpty, - ForInStatement: checkEmpty, - DebuggerStatement: checkEmpty, - ThisExpression: checkEmpty, - ArrayExpression: checkEmpty, - ObjectExpression: checkEmpty, - Property: checkEmpty, - SequenceExpression: checkEmpty, - UnaryExpression: checkEmpty, - BinaryExpression: checkEmpty, - AssignmentExpression: checkEmpty, - UpdateExpression: checkEmpty, - LogicalExpression: checkEmpty, - ConditionalExpression: checkEmpty, - CallExpression: checkEmpty, - NewExpression: checkEmpty, - MemberExpression: checkEmpty, - SwitchCase: checkEmpty, - Identifier: checkEmpty, - Literal: checkEmpty, - ForOfStatement: checkEmpty, - ArrowFunctionExpression: checkEmpty, - YieldExpression: checkEmpty, - TemplateLiteral: checkEmpty, - TaggedTemplateExpression: checkEmpty, - TemplateElement: checkEmpty, - ObjectPattern: checkEmpty, - ArrayPattern: checkEmpty, - RestElement: checkEmpty, - AssignmentPattern: checkEmpty, - ClassBody: checkEmpty, - MethodDefinition: checkEmpty, - MetaProperty: checkEmpty - }; - - rule[type] = function(node) { - const expectedNames = expectedNamesList.shift(); - const variables = context.getDeclaredVariables(node); - - assert(Array.isArray(expectedNames)); - assert(Array.isArray(variables)); - assert.strictEqual(expectedNames.length, variables.length); - for (let i = variables.length - 1; i >= 0; i--) { - assert.strictEqual(expectedNames[i], variables[i].name); + /** + * Assert `context.getDeclaredVariables(node)` is empty. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function checkEmpty(node) { + assert.strictEqual(0, context.getDeclaredVariables(node).length); } - }; - return rule; + const rule = { + Program: checkEmpty, + EmptyStatement: checkEmpty, + BlockStatement: checkEmpty, + ExpressionStatement: checkEmpty, + LabeledStatement: checkEmpty, + BreakStatement: checkEmpty, + ContinueStatement: checkEmpty, + WithStatement: checkEmpty, + SwitchStatement: checkEmpty, + ReturnStatement: checkEmpty, + ThrowStatement: checkEmpty, + TryStatement: checkEmpty, + WhileStatement: checkEmpty, + DoWhileStatement: checkEmpty, + ForStatement: checkEmpty, + ForInStatement: checkEmpty, + DebuggerStatement: checkEmpty, + ThisExpression: checkEmpty, + ArrayExpression: checkEmpty, + ObjectExpression: checkEmpty, + Property: checkEmpty, + SequenceExpression: checkEmpty, + UnaryExpression: checkEmpty, + BinaryExpression: checkEmpty, + AssignmentExpression: checkEmpty, + UpdateExpression: checkEmpty, + LogicalExpression: checkEmpty, + ConditionalExpression: checkEmpty, + CallExpression: checkEmpty, + NewExpression: checkEmpty, + MemberExpression: checkEmpty, + SwitchCase: checkEmpty, + Identifier: checkEmpty, + Literal: checkEmpty, + ForOfStatement: checkEmpty, + ArrowFunctionExpression: checkEmpty, + YieldExpression: checkEmpty, + TemplateLiteral: checkEmpty, + TaggedTemplateExpression: checkEmpty, + TemplateElement: checkEmpty, + ObjectPattern: checkEmpty, + ArrayPattern: checkEmpty, + RestElement: checkEmpty, + AssignmentPattern: checkEmpty, + ClassBody: checkEmpty, + MethodDefinition: checkEmpty, + MetaProperty: checkEmpty + }; + + rule[type] = function(node) { + const expectedNames = expectedNamesList.shift(); + const variables = context.getDeclaredVariables(node); + + assert(Array.isArray(expectedNames)); + assert(Array.isArray(variables)); + assert.strictEqual(expectedNames.length, variables.length); + for (let i = variables.length - 1; i >= 0; i--) { + assert.strictEqual(expectedNames[i], variables[i].name); + } + }; + return rule; + } + } } @@ -10396,17 +10792,19 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); + checker: { + create(context) { + spy = sinon.spy(() => { + assert.isTrue(context.markVariableAsUsed("a")); - const scope = context.getScope(); + const scope = context.getScope(); - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } } } } @@ -10429,17 +10827,19 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); + checker: { + create(context) { + spy = sinon.spy(() => { + assert.isTrue(context.markVariableAsUsed("a")); - const scope = context.getScope(); + const scope = context.getScope(); - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); - return { ReturnStatement: spy }; + return { ReturnStatement: spy }; + } } } } @@ -10459,18 +10859,20 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - returnSpy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); - }); - exitSpy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + returnSpy = sinon.spy(() => { + assert.isTrue(context.markVariableAsUsed("a")); + }); + exitSpy = sinon.spy(() => { + const scope = context.getScope(); - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); - return { ReturnStatement: returnSpy, "Program:exit": exitSpy }; + return { ReturnStatement: returnSpy, "Program:exit": exitSpy }; + } } } } @@ -10494,18 +10896,20 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const globalScope = context.getScope(), - childScope = globalScope.childScopes[0]; + checker: { + create(context) { + spy = sinon.spy(() => { + const globalScope = context.getScope(), + childScope = globalScope.childScopes[0]; - assert.isTrue(context.markVariableAsUsed("a"), "Call to markVariableAsUsed should return true"); + assert.isTrue(context.markVariableAsUsed("a"), "Call to markVariableAsUsed should return true"); - assert.isTrue(getVariable(childScope, "a").eslintUsed, "'a' should be marked as used."); - assert.isUndefined(getVariable(childScope, "b").eslintUsed, "'b' should be marked as used."); - }); + assert.isTrue(getVariable(childScope, "a").eslintUsed, "'a' should be marked as used."); + assert.isUndefined(getVariable(childScope, "b").eslintUsed, "'b' should be marked as used."); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } } } } @@ -10528,18 +10932,20 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const globalScope = context.getScope(), - childScope = globalScope.childScopes[0]; + checker: { + create(context) { + spy = sinon.spy(() => { + const globalScope = context.getScope(), + childScope = globalScope.childScopes[0]; - assert.isTrue(context.markVariableAsUsed("a")); + assert.isTrue(context.markVariableAsUsed("a")); - assert.isTrue(getVariable(childScope, "a").eslintUsed); - assert.isUndefined(getVariable(childScope, "b").eslintUsed); - }); + assert.isTrue(getVariable(childScope, "a").eslintUsed); + assert.isUndefined(getVariable(childScope, "b").eslintUsed); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } } } } @@ -10563,12 +10969,14 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - assert.isFalse(context.markVariableAsUsed("c")); - }); + checker: { + create(context) { + spy = sinon.spy(() => { + assert.isFalse(context.markVariableAsUsed("c")); + }); - return { "Program:exit": spy }; + return { "Program:exit": spy }; + } } } } @@ -10593,11 +11001,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), cwd); - }); - return { Program: spy }; + checker: { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), cwd); + }); + return { Program: spy }; + } } } } @@ -10617,12 +11027,14 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { + checker: { + create(context) { - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), process.cwd()); - }); - return { Program: spy }; + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), process.cwd()); + }); + return { Program: spy }; + } } } } @@ -10640,12 +11052,99 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { + checker: { + create(context) { - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), process.cwd()); - }); - return { Program: spy }; + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), process.cwd()); + }); + return { Program: spy }; + } + } + } + } + }, + ...baseConfig + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("context.cwd", () => { + const code = "a;\nb;"; + const baseConfig = { rules: { "test/checker": "error" } }; + + it("should get cwd correctly in the context", () => { + const cwd = "cwd"; + const linterWithOption = new Linter({ cwd, configType: "flat" }); + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual(context.cwd, cwd); + }); + return { Program: spy }; + } + } + } + } + }, + ...baseConfig + }; + + linterWithOption.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("should assign process.cwd() to it if cwd is undefined", () => { + + const linterWithOption = new Linter({ configType: "flat" }); + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), context.cwd); + assert.strictEqual(context.cwd, process.cwd()); + }); + return { Program: spy }; + } + } + } + } + }, + ...baseConfig + }; + + linterWithOption.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("should assign process.cwd() to it if the option is undefined", () => { + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), context.cwd); + assert.strictEqual(context.cwd, process.cwd()); + }); + return { Program: spy }; + } } } } @@ -10747,7 +11246,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: spy + checker: { create: spy } } } }, @@ -10766,7 +11265,7 @@ describe("Linter with FlatConfigArray", () => { describe("filename", () => { it("should allow filename to be passed on options object", () => { const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.getFilename(), "foo.js"); + assert.strictEqual(context.filename, "foo.js"); return {}; }); @@ -10774,7 +11273,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: filenameChecker + checker: { create: filenameChecker } } } }, @@ -10789,7 +11288,7 @@ describe("Linter with FlatConfigArray", () => { it("should allow filename to be passed as third argument", () => { const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.getFilename(), "bar.js"); + assert.strictEqual(context.filename, "bar.js"); return {}; }); @@ -10797,7 +11296,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: filenameChecker + checker: { create: filenameChecker } } } }, @@ -10812,7 +11311,7 @@ describe("Linter with FlatConfigArray", () => { it("should default filename to when options object doesn't have filename", () => { const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.getFilename(), ""); + assert.strictEqual(context.filename, ""); return {}; }); @@ -10820,7 +11319,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: filenameChecker + checker: { create: filenameChecker } } } }, @@ -10835,7 +11334,7 @@ describe("Linter with FlatConfigArray", () => { it("should default filename to when only two arguments are passed", () => { const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.getFilename(), ""); + assert.strictEqual(context.filename, ""); return {}; }); @@ -10843,7 +11342,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: filenameChecker + checker: { create: filenameChecker } } } }, @@ -10860,7 +11359,8 @@ describe("Linter with FlatConfigArray", () => { describe("physicalFilename", () => { it("should be same as `filename` passed on options object, if no processors are used", () => { const physicalFilenameChecker = sinon.spy(context => { - assert.strictEqual(context.getPhysicalFilename(), "foo.js"); + assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); + assert.strictEqual(context.physicalFilename, "foo.js"); return {}; }); @@ -10868,7 +11368,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: physicalFilenameChecker + checker: { create: physicalFilenameChecker } } } }, @@ -10883,7 +11383,8 @@ describe("Linter with FlatConfigArray", () => { it("should default physicalFilename to when options object doesn't have filename", () => { const physicalFilenameChecker = sinon.spy(context => { - assert.strictEqual(context.getPhysicalFilename(), ""); + assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); + assert.strictEqual(context.physicalFilename, ""); return {}; }); @@ -10891,7 +11392,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: physicalFilenameChecker + checker: { create: physicalFilenameChecker } } } }, @@ -10906,7 +11407,8 @@ describe("Linter with FlatConfigArray", () => { it("should default physicalFilename to when only two arguments are passed", () => { const physicalFilenameChecker = sinon.spy(context => { - assert.strictEqual(context.getPhysicalFilename(), ""); + assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); + assert.strictEqual(context.physicalFilename, ""); return {}; }); @@ -10914,7 +11416,7 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: physicalFilenameChecker + checker: { create: physicalFilenameChecker } } } }, @@ -10949,38 +11451,40 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); - const a = getVariable(scope, "a"), - b = getVariable(scope, "b"), - c = getVariable(scope, "c"), - d = getVariable(scope, "d"), - e = getVariable(scope, "e"), - f = getVariable(scope, "f"), - mathGlobal = getVariable(scope, "Math"), - arrayGlobal = getVariable(scope, "Array"), - configGlobal = getVariable(scope, "ConfigGlobal"); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); + const a = getVariable(scope, "a"), + b = getVariable(scope, "b"), + c = getVariable(scope, "c"), + d = getVariable(scope, "d"), + e = getVariable(scope, "e"), + f = getVariable(scope, "f"), + mathGlobal = getVariable(scope, "Math"), + arrayGlobal = getVariable(scope, "Array"), + configGlobal = getVariable(scope, "ConfigGlobal"); - assert.strictEqual(a.name, "a"); - assert.strictEqual(a.writeable, false); - assert.strictEqual(b.name, "b"); - assert.strictEqual(b.writeable, true); - assert.strictEqual(c.name, "c"); - assert.strictEqual(c.writeable, false); - assert.strictEqual(d.name, "d"); - assert.strictEqual(d.writeable, false); - assert.strictEqual(e.name, "e"); - assert.strictEqual(e.writeable, true); - assert.strictEqual(f.name, "f"); - assert.strictEqual(f.writeable, true); - assert.strictEqual(mathGlobal, null); - assert.strictEqual(arrayGlobal, null); - assert.strictEqual(configGlobal.name, "ConfigGlobal"); - assert.strictEqual(configGlobal.writeable, false); - }); + assert.strictEqual(a.name, "a"); + assert.strictEqual(a.writeable, false); + assert.strictEqual(b.name, "b"); + assert.strictEqual(b.writeable, true); + assert.strictEqual(c.name, "c"); + assert.strictEqual(c.writeable, false); + assert.strictEqual(d.name, "d"); + assert.strictEqual(d.writeable, false); + assert.strictEqual(e.name, "e"); + assert.strictEqual(e.writeable, true); + assert.strictEqual(f.name, "f"); + assert.strictEqual(f.writeable, true); + assert.strictEqual(mathGlobal, null); + assert.strictEqual(arrayGlobal, null); + assert.strictEqual(configGlobal.name, "ConfigGlobal"); + assert.strictEqual(configGlobal.writeable, false); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11006,22 +11510,24 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - a = getVariable(scope, "a"), - b = getVariable(scope, "b"), - c = getVariable(scope, "c"); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + a = getVariable(scope, "a"), + b = getVariable(scope, "b"), + c = getVariable(scope, "c"); - assert.strictEqual(a.name, "a"); - assert.strictEqual(a.writeable, false); - assert.strictEqual(b.name, "b"); - assert.strictEqual(b.writeable, true); - assert.strictEqual(c.name, "c"); - assert.strictEqual(c.writeable, false); - }); + assert.strictEqual(a.name, "a"); + assert.strictEqual(a.writeable, false); + assert.strictEqual(b.name, "b"); + assert.strictEqual(b.writeable, true); + assert.strictEqual(c.name, "c"); + assert.strictEqual(c.writeable, false); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11044,14 +11550,16 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(getVariable(scope, "a"), null); - }); + assert.strictEqual(getVariable(scope, "a"), null); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11075,17 +11583,19 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.strictEqual(getVariable(scope, "a"), null); - assert.strictEqual(getVariable(scope, "b"), null); - assert.strictEqual(getVariable(scope, "foo"), null); - assert.strictEqual(getVariable(scope, "c"), null); - }); + assert.strictEqual(getVariable(scope, "a"), null); + assert.strictEqual(getVariable(scope, "b"), null); + assert.strictEqual(getVariable(scope, "foo"), null); + assert.strictEqual(getVariable(scope, "c"), null); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11106,13 +11616,14 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - test(context) { - return { + test: { + create: context => ({ Program() { const scope = context.getScope(); - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const comments = sourceCode.getAllComments(); + assert.strictEqual(context.getSourceCode(), sourceCode); assert.strictEqual(2, comments.length); const foo = getVariable(scope, "foo"); @@ -11132,7 +11643,7 @@ describe("Linter with FlatConfigArray", () => { ok = true; } - }; + }) } } } @@ -11195,15 +11706,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse.eslintUsed, true); - }); + assert.strictEqual(horse.eslintUsed, true); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11225,15 +11738,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse, null); - }); + assert.strictEqual(horse, null); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11255,15 +11770,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse.eslintUsed, true); - }); + assert.strictEqual(horse.eslintUsed, true); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11285,15 +11802,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse, null); // there is no global scope at all - }); + assert.strictEqual(horse, null); // there is no global scope at all + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11316,15 +11835,17 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + horse = getVariable(scope, "horse"); - assert.strictEqual(horse, null); // there is no global scope at all - }); + assert.strictEqual(horse, null); // there is no global scope at all + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -11598,14 +12119,14 @@ describe("Linter with FlatConfigArray", () => { plugins: { "test-plugin": { rules: { - "test-rule"(context) { - return { + "test-rule": { + create: context => ({ Literal(node) { if (node.value === "trigger violation") { context.report(node, "Reporting violation."); } } - }; + }) } } } @@ -11643,11 +12164,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => ({ - Program() { - context.report({ loc: { line: 1, column: 0 }, message: "foo" }); - } - }) + checker: { + create: context => ({ + Program() { + context.report({ loc: { line: 1, column: 0 }, message: "foo" }); + } + }) + } } } }, @@ -11672,11 +12195,13 @@ describe("Linter with FlatConfigArray", () => { plugins: { test: { rules: { - checker: context => ({ - Program() { - context.report({ loc: { line: 1, column: 1 }, message: "foo" }); - } - }) + checker: { + create: context => ({ + Program() { + context.report({ loc: { line: 1, column: 1 }, message: "foo" }); + } + }) + } } } }, @@ -13374,13 +13899,14 @@ var a = "test2"; plugins: { test: { rules: { - test(context) { - return { + test: { + create: context => ({ Program() { const scope = context.getScope(); - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const comments = sourceCode.getAllComments(); + assert.strictEqual(context.getSourceCode(), sourceCode); assert.strictEqual(1, comments.length); const foo = getVariable(scope, "foo"); @@ -13389,7 +13915,7 @@ var a = "test2"; ok = true; } - }; + }) } } } @@ -14413,16 +14939,18 @@ var a = "test2"; plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.notStrictEqual(getVariable(scope, "Object"), null); - assert.notStrictEqual(getVariable(scope, "Array"), null); - assert.notStrictEqual(getVariable(scope, "undefined"), null); - }); + assert.notStrictEqual(getVariable(scope, "Object"), null); + assert.notStrictEqual(getVariable(scope, "Array"), null); + assert.notStrictEqual(getVariable(scope, "undefined"), null); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -14446,16 +14974,18 @@ var a = "test2"; plugins: { test: { rules: { - checker: context => { - spy = sinon.spy(() => { - const scope = context.getScope(); + checker: { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(); - assert.notStrictEqual(getVariable(scope, "Promise"), null); - assert.notStrictEqual(getVariable(scope, "Symbol"), null); - assert.notStrictEqual(getVariable(scope, "WeakMap"), null); - }); + assert.notStrictEqual(getVariable(scope, "Promise"), null); + assert.notStrictEqual(getVariable(scope, "Symbol"), null); + assert.notStrictEqual(getVariable(scope, "WeakMap"), null); + }); - return { Program: spy }; + return { Program: spy }; + } } } } @@ -14781,7 +15311,9 @@ var a = "test2"; describe("defineRule()", () => { it("should throw an error when called in flat config mode", () => { assert.throws(() => { - linter.defineRule("foo", () => {}); + linter.defineRule("foo", { + create() {} + }); }, /This method cannot be used with flat config/u); }); }); @@ -14982,11 +15514,13 @@ var a = "test2"; plugins: { test: { rules: { - "test-rule": context => ({ - Program(node) { - context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); - } - }) + "test-rule": { + create: context => ({ + Program(node) { + context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); + } + }) + } } } }, @@ -15017,7 +15551,9 @@ var a = "test2"; }); it("loading rule in one doesn't change the other", () => { - linter1.defineRule("mock-rule", () => ({})); + linter1.defineRule("mock-rule", { + create: () => ({}) + }); assert.isTrue(linter1.getRules().has("mock-rule"), "mock rule is present"); assert.isFalse(linter2.getRules().has("mock-rule"), "mock rule is not present"); @@ -15040,9 +15576,13 @@ var a = "test2"; create(context) { return { Program(ast) { - receivedFilenames.push(context.getFilename()); - receivedPhysicalFilenames.push(context.getPhysicalFilename()); - context.report({ node: ast, message: context.getSourceCode().text }); + assert.strictEqual(context.getFilename(), context.filename); + assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); + + receivedFilenames.push(context.filename); + receivedPhysicalFilenames.push(context.physicalFilename); + + context.report({ node: ast, message: context.sourceCode.text }); } }; } @@ -15227,7 +15767,8 @@ var a = "test2"; severity: 2, message: "Preprocessing error: Invalid syntax", line: 1, - column: 1 + column: 1, + nodeType: null } ]); }); @@ -15480,17 +16021,21 @@ var a = "test2"; plugins: { test: { rules: { - "save-ast1": () => ({ - Program(node) { - ast1 = node; - } - }), + "save-ast1": { + create: () => ({ + Program(node) { + ast1 = node; + } + }) + }, - "save-ast2": () => ({ - Program(node) { - ast2 = node; - } - }) + "save-ast2": { + create: () => ({ + Program(node) { + ast2 = node; + } + }) + } } } @@ -15530,7 +16075,7 @@ var a = "test2"; plugins: { test: { rules: { - "foo-bar-baz": spy + "foo-bar-baz": { create: spy } } } }, @@ -15552,11 +16097,15 @@ var a = "test2"; plugins: { test: { rules: { - "no-programs": context => ({ - Program(node) { - context.report({ node, message: "No programs allowed." }); + "no-programs": { + create(context) { + return { + Program(node) { + context.report({ node, message: "No programs allowed." }); + } + }; } - }) + } } } }, diff --git a/eslint/tests/lib/options.js b/eslint/tests/lib/options.js index 4d3507a..d8f795b 100644 --- a/eslint/tests/lib/options.js +++ b/eslint/tests/lib/options.js @@ -124,14 +124,6 @@ describe("options", () => { }); }); - describe("--ignore-path", () => { - it("should return a string for .ignorePath when passed", () => { - const currentOptions = options.parse("--ignore-path .gitignore"); - - assert.strictEqual(currentOptions.ignorePath, ".gitignore"); - }); - }); - describe("--ignore-pattern", () => { it("should return a string array for .ignorePattern when passed", () => { const currentOptions = options.parse("--ignore-pattern *.js"); @@ -371,6 +363,14 @@ describe("options", () => { }); }); + describe("--ignore-path", () => { + it("should return a string for .ignorePath when passed", () => { + const currentOptions = eslintrcOptions.parse("--ignore-path .gitignore"); + + assert.strictEqual(currentOptions.ignorePath, ".gitignore"); + }); + }); + describe("--parser", () => { it("should return a string for --parser when passed", () => { const currentOptions = eslintrcOptions.parse("--parser test"); diff --git a/eslint/tests/lib/rule-tester/flat-rule-tester.js b/eslint/tests/lib/rule-tester/flat-rule-tester.js index bdc196e..f65c3bb 100644 --- a/eslint/tests/lib/rule-tester/flat-rule-tester.js +++ b/eslint/tests/lib/rule-tester/flat-rule-tester.js @@ -142,12 +142,14 @@ describe("FlatRuleTester", () => { FlatRuleTester.setDefaultConfig(config); }; } - assert.throw(setConfig()); - assert.throw(setConfig(1)); - assert.throw(setConfig(3.14)); - assert.throw(setConfig("foo")); - assert.throw(setConfig(null)); - assert.throw(setConfig(true)); + const errorMessage = "FlatRuleTester.setDefaultConfig: config must be an object"; + + assert.throw(setConfig(), errorMessage); + assert.throw(setConfig(1), errorMessage); + assert.throw(setConfig(3.14), errorMessage); + assert.throw(setConfig("foo"), errorMessage); + assert.throw(setConfig(null), errorMessage); + assert.throw(setConfig(true), errorMessage); }); it("should pass-through the globals config to the tester then to the to rule", () => { @@ -1445,7 +1447,7 @@ describe("FlatRuleTester", () => { const usesStartEndRule = { create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { CallExpression(node) { @@ -2548,7 +2550,7 @@ describe("FlatRuleTester", () => { const useGetCommentsRule = { create: context => ({ Program(node) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; sourceCode.getComments(node); } diff --git a/eslint/tests/lib/rule-tester/rule-tester.js b/eslint/tests/lib/rule-tester/rule-tester.js index 0e35258..a36edaf 100644 --- a/eslint/tests/lib/rule-tester/rule-tester.js +++ b/eslint/tests/lib/rule-tester/rule-tester.js @@ -1412,12 +1412,14 @@ describe("RuleTester", () => { RuleTester.setDefaultConfig(config); }; } - assert.throw(setConfig()); - assert.throw(setConfig(1)); - assert.throw(setConfig(3.14)); - assert.throw(setConfig("foo")); - assert.throw(setConfig(null)); - assert.throw(setConfig(true)); + const errorMessage = "RuleTester.setDefaultConfig: config must be an object"; + + assert.throw(setConfig(), errorMessage); + assert.throw(setConfig(1), errorMessage); + assert.throw(setConfig(3.14), errorMessage); + assert.throw(setConfig("foo"), errorMessage); + assert.throw(setConfig(null), errorMessage); + assert.throw(setConfig(true), errorMessage); }); it("should pass-through the globals config to the tester then to the to rule", () => { @@ -1496,7 +1498,7 @@ describe("RuleTester", () => { const usesStartEndRule = { create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; return { CallExpression(node) { @@ -2286,7 +2288,7 @@ describe("RuleTester", () => { assert.deepStrictEqual( processStub.getCall(0).args, [ - "\"function-style-rule\" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/developer-guide/working-with-rules", + "\"function-style-rule\" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/latest/extend/custom-rules", "DeprecationWarning" ] ); @@ -2304,7 +2306,7 @@ describe("RuleTester", () => { assert.deepStrictEqual( processStub.getCall(0).args, [ - "\"rule-with-no-meta-1\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/developer-guide/working-with-rules#options-schemas", + "\"rule-with-no-meta-1\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", "DeprecationWarning" ] ); @@ -2322,7 +2324,7 @@ describe("RuleTester", () => { assert.deepStrictEqual( processStub.getCall(0).args, [ - "\"rule-with-no-schema-1\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/developer-guide/working-with-rules#options-schemas", + "\"rule-with-no-schema-1\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", "DeprecationWarning" ] ); @@ -2332,7 +2334,7 @@ describe("RuleTester", () => { const ruleWithUndefinedSchema = { meta: { type: "problem", - // eslint-disable-next-line no-undefined -- intentioally added for test case + // eslint-disable-next-line no-undefined -- intentionally added for test case schema: undefined }, create(context) { @@ -2355,7 +2357,7 @@ describe("RuleTester", () => { assert.deepStrictEqual( processStub.getCall(0).args, [ - "\"rule-with-undefined-schema\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/developer-guide/working-with-rules#options-schemas", + "\"rule-with-undefined-schema\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", "DeprecationWarning" ] ); @@ -2387,7 +2389,7 @@ describe("RuleTester", () => { assert.deepStrictEqual( processStub.getCall(0).args, [ - "\"rule-with-null-schema\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/developer-guide/working-with-rules#options-schemas", + "\"rule-with-null-schema\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", "DeprecationWarning" ] ); @@ -2791,7 +2793,7 @@ describe("RuleTester", () => { const useGetCommentsRule = { create: context => ({ Program(node) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; sourceCode.getComments(node); } diff --git a/eslint/tests/lib/rules/array-callback-return.js b/eslint/tests/lib/rules/array-callback-return.js index 13a0112..6343d3e 100644 --- a/eslint/tests/lib/rules/array-callback-return.js +++ b/eslint/tests/lib/rules/array-callback-return.js @@ -57,6 +57,8 @@ ruleTester.run("array-callback-return", rule, { "foo.filter(function() { return true; })", "foo.find(function() { return true; })", "foo.findIndex(function() { return true; })", + "foo.findLast(function() { return true; })", + "foo.findLastIndex(function() { return true; })", "foo.flatMap(function() { return true; })", "foo.forEach(function() { return; })", "foo.map(function() { return true; })", @@ -64,6 +66,7 @@ ruleTester.run("array-callback-return", rule, { "foo.reduceRight(function() { return true; })", "foo.some(function() { return true; })", "foo.sort(function() { return 0; })", + "foo.toSorted(function() { return 0; })", { code: "foo.every(() => { return true; })", parserOptions: { ecmaVersion: 6 } }, "foo.every(function() { if (a) return true; else return false; })", "foo.every(function() { switch (a) { case 0: bar(); default: return true; } })", @@ -77,6 +80,8 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.filter(function() { return; })", options: allowImplicitOptions }, { code: "foo.find(function() { return; })", options: allowImplicitOptions }, { code: "foo.findIndex(function() { return; })", options: allowImplicitOptions }, + { code: "foo.findLast(function() { return; })", options: allowImplicitOptions }, + { code: "foo.findLastIndex(function() { return; })", options: allowImplicitOptions }, { code: "foo.flatMap(function() { return; })", options: allowImplicitOptions }, { code: "foo.forEach(function() { return; })", options: allowImplicitOptions }, { code: "foo.map(function() { return; })", options: allowImplicitOptions }, @@ -84,6 +89,7 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.reduceRight(function() { return; })", options: allowImplicitOptions }, { code: "foo.some(function() { return; })", options: allowImplicitOptions }, { code: "foo.sort(function() { return; })", options: allowImplicitOptions }, + { code: "foo.toSorted(function() { return; })", options: allowImplicitOptions }, { code: "foo.every(() => { return; })", options: allowImplicitOptions, parserOptions: { ecmaVersion: 6 } }, { code: "foo.every(function() { if (a) return; else return a; })", options: allowImplicitOptions }, { code: "foo.every(function() { switch (a) { case 0: bar(); default: return; } })", options: allowImplicitOptions }, @@ -129,8 +135,12 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.filter(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] }, { code: "foo.find(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.find" } }] }, { code: "foo.find(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.find" } }] }, + { code: "foo.findLast(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.findLast" } }] }, + { code: "foo.findLast(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.findLast" } }] }, { code: "foo.findIndex(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.findIndex" } }] }, { code: "foo.findIndex(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.findIndex" } }] }, + { code: "foo.findLastIndex(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.findLastIndex" } }] }, + { code: "foo.findLastIndex(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.findLastIndex" } }] }, { code: "foo.flatMap(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.flatMap" } }] }, { code: "foo.flatMap(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.flatMap" } }] }, { code: "foo.map(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.map" } }] }, @@ -143,6 +153,8 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.some(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.some" } }] }, { code: "foo.sort(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.sort" } }] }, { code: "foo.sort(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.sort" } }] }, + { code: "foo.toSorted(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.toSorted" } }] }, + { code: "foo.toSorted(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.toSorted" } }] }, { code: "foo.bar.baz.every(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, { code: "foo.bar.baz.every(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] }, { code: "foo[\"every\"](function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, @@ -182,6 +194,7 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.bar.baz.every(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] }, { code: "foo.every(cb || function() {})", options: allowImplicitOptions, errors: ["Array.prototype.every() expects a return value from function."] }, { code: "[\"foo\",\"bar\"].sort(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.sort" } }] }, + { code: "[\"foo\",\"bar\"].toSorted(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.toSorted" } }] }, { code: "foo.forEach(x => x)", options: allowImplicitCheckForEach, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, { code: "foo.forEach(function(x) { if (a == b) {return x;}})", options: allowImplicitCheckForEach, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] }, { code: "foo.forEach(function bar(x) { return x;})", options: allowImplicitCheckForEach, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] }, diff --git a/eslint/tests/lib/rules/comma-dangle.js b/eslint/tests/lib/rules/comma-dangle.js index b02a74a..49cd6c2 100644 --- a/eslint/tests/lib/rules/comma-dangle.js +++ b/eslint/tests/lib/rules/comma-dangle.js @@ -12,7 +12,8 @@ const path = require("path"), { unIndent } = require("../../_utils"), rule = require("../../../lib/rules/comma-dangle"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + FlatRuleTester = require("../../../lib/rule-tester/flat-rule-tester"); //------------------------------------------------------------------------------ // Helpers @@ -43,7 +44,7 @@ ruleTester.defineRule("add-named-import", { create(context) { return { ImportDeclaration(node) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode; const closingBrace = sourceCode.getLastToken(node, token => token.value === "}"); const addComma = sourceCode.getTokenBefore(closingBrace).value !== ","; @@ -279,6 +280,14 @@ ruleTester.run("comma-dangle", rule, { code: "function foo(a,\nb) {}", options: ["always-multiline"] }, + { + code: "foo(a,\nb\n)", + options: ["always-multiline"] + }, + { + code: "function foo(a,\nb\n) {}", + options: ["always-multiline"] + }, { code: "foo(a,\nb)", options: ["always-multiline"] @@ -321,6 +330,16 @@ ruleTester.run("comma-dangle", rule, { options: ["always-multiline"], parserOptions: { ecmaVersion: 7 } }, + { + code: "function foo(a,\nb\n) {}", + options: ["always-multiline"], + parserOptions: { ecmaVersion: 7 } + }, + { + code: "foo(a,\nb\n)", + options: ["always-multiline"], + parserOptions: { ecmaVersion: 7 } + }, { code: "function foo(a,\nb) {}", options: ["only-multiline"], @@ -1853,3 +1872,100 @@ let d = 0;export {d,}; } ] }); + +const flatRuleTester = new FlatRuleTester(); + +// https://github.com/eslint/eslint/issues/16442 +flatRuleTester.run("comma-dangle", rule, { + valid: [ + { + code: "function f(\n a,\n b\n) {}", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 5, + sourceType: "script" + } + }, + { + code: "f(\n a,\n b\n);", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 5, + sourceType: "script" + } + }, + { + code: "function f(\n a,\n b\n) {}", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 2016 + } + }, + { + code: "f(\n a,\n b\n);", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 2016 + } + } + ], + + invalid: [ + { + code: "function f(\n a,\n b\n) {}", + output: "function f(\n a,\n b,\n) {}", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 2017 + }, + errors: [{ + messageId: "missing", + type: "Identifier", + line: 3, + column: 3 + }] + }, + { + code: "f(\n a,\n b\n);", + output: "f(\n a,\n b,\n);", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 2017 + }, + errors: [{ + messageId: "missing", + type: "Identifier", + line: 3, + column: 3 + }] + }, + { + code: "function f(\n a,\n b\n) {}", + output: "function f(\n a,\n b,\n) {}", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: "latest" + }, + errors: [{ + messageId: "missing", + type: "Identifier", + line: 3, + column: 3 + }] + }, + { + code: "f(\n a,\n b\n);", + output: "f(\n a,\n b,\n);", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: "latest" + }, + errors: [{ + messageId: "missing", + type: "Identifier", + line: 3, + column: 3 + }] + } + ] +}); diff --git a/eslint/tests/lib/rules/func-name-matching.js b/eslint/tests/lib/rules/func-name-matching.js index 7e21525..c020066 100644 --- a/eslint/tests/lib/rules/func-name-matching.js +++ b/eslint/tests/lib/rules/func-name-matching.js @@ -10,7 +10,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/func-name-matching"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + FlatRuleTester = require("../../../lib/rule-tester/flat-rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -504,7 +505,8 @@ ruleTester.run("func-name-matching", rule, { code: "class C { #x; foo() { a.b.#x = function y() {}; } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } - } + }, + "var obj = { '\\u1885': function foo() {} };" // not a valid identifier in es5 ], invalid: [ { @@ -878,6 +880,39 @@ ruleTester.run("func-name-matching", rule, { errors: [ { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } } ] + }, + { + code: "var obj = { '\\u1885': function foo() {} };", // valid identifier in es2015 + parserOptions: { ecmaVersion: 6 }, + errors: [ + { messageId: "matchProperty", data: { funcName: "foo", name: "\u1885" } } + ] + } + ] +}); + +const flatRuleTester = new FlatRuleTester(); + +flatRuleTester.run("func-name-matching", rule, { + valid: [ + { + code: "var obj = { '\\u1885': function foo() {} };", // not a valid identifier in es5 + languageOptions: { + ecmaVersion: 5, + sourceType: "script" + } + } + ], + + invalid: [ + { + code: "var obj = { '\\u1885': function foo() {} };", // valid identifier in es2015 + languageOptions: { + ecmaVersion: 2015 + }, + errors: [ + { messageId: "matchProperty", data: { funcName: "foo", name: "\u1885" } } + ] } ] }); diff --git a/eslint/tests/lib/rules/getter-return.js b/eslint/tests/lib/rules/getter-return.js index 92032c0..3fb755c 100644 --- a/eslint/tests/lib/rules/getter-return.js +++ b/eslint/tests/lib/rules/getter-return.js @@ -57,11 +57,27 @@ ruleTester.run("getter-return", rule, { "Object.defineProperties(foo, { bar: { get: function () {return true;}} });", "Object.defineProperties(foo, { bar: { get: function () { ~function (){ return true; }(); return true;}} });", + /* + * test reflect.defineProperty(s) + * option: {allowImplicit: false} + */ + "Reflect.defineProperty(foo, \"bar\", { get: function () {return true;}});", + "Reflect.defineProperty(foo, \"bar\", { get: function () { ~function (){ return true; }();return true;}});", + + /* + * test object.create(s) + * option: {allowImplicit: false} + */ + "Object.create(foo, { bar: { get() {return true;} } });", + "Object.create(foo, { bar: { get: function () {return true;} } });", + "Object.create(foo, { bar: { get: () => {return true;} } });", + // option: {allowImplicit: true} { code: "Object.defineProperty(foo, \"bar\", { get: function () {return true;}});", options }, { code: "Object.defineProperty(foo, \"bar\", { get: function (){return;}});", options }, { code: "Object.defineProperties(foo, { bar: { get: function () {return true;}} });", options }, { code: "Object.defineProperties(foo, { bar: { get: function () {return;}} });", options }, + { code: "Reflect.defineProperty(foo, \"bar\", { get: function () {return true;}});", options }, // not getter. "var get = function(){};", @@ -73,7 +89,10 @@ ruleTester.run("getter-return", rule, { "var foo = { bar: function(){return true;} };", "var foo = { get: function () {} }", "var foo = { get: () => {}};", - "class C { get; foo() {} }" + "class C { get; foo() {} }", + "foo.defineProperty(null, { get() {} });", + "foo.defineProperties(null, { bar: { get() {} } });", + "foo.create(null, { bar: { get() {} } });" ], invalid: [ @@ -220,11 +239,67 @@ ruleTester.run("getter-return", rule, { { code: "Object.defineProperty(foo, \"bar\", { get: function (){if(bar) {return true;}}});", errors: [{ messageId: "expectedAlways" }] }, { code: "Object.defineProperty(foo, \"bar\", { get: function (){ ~function () { return true; }()}});", errors: [{ messageId: "expected" }] }, + /* + * test reflect.defineProperty(s) + * option: {allowImplicit: false} + */ + { + code: "Reflect.defineProperty(foo, 'bar', { get: function (){}});", + errors: [{ + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 38, + endLine: 1, + endColumn: 52 + }] + }, + + /* + * test object.create(s) + * option: {allowImplicit: false} + */ + { + code: "Object.create(foo, { bar: { get: function() {} } })", + errors: [{ + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 29, + endLine: 1, + endColumn: 42 + }] + }, + { + code: "Object.create(foo, { bar: { get() {} } })", + errors: [{ + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 29, + endLine: 1, + endColumn: 32 + }] + }, + { + code: "Object.create(foo, { bar: { get: () => {} } })", + errors: [{ + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 29, + endLine: 1, + endColumn: 34 + }] + }, + // option: {allowImplicit: true} { code: "Object.defineProperties(foo, { bar: { get: function () {}} });", options, errors: [{ messageId: "expected" }] }, { code: "Object.defineProperties(foo, { bar: { get: function (){if(bar) {return true;}}}});", options, errors: [{ messageId: "expectedAlways" }] }, { code: "Object.defineProperties(foo, { bar: { get: function () {~function () { return true; }()}} });", options, errors: [{ messageId: "expected" }] }, { code: "Object.defineProperty(foo, \"bar\", { get: function (){}});", options, errors: [{ messageId: "expected" }] }, + { code: "Object.create(foo, { bar: { get: function (){} } });", options, errors: [{ messageId: "expected" }] }, + { code: "Reflect.defineProperty(foo, \"bar\", { get: function (){}});", options, errors: [{ messageId: "expected" }] }, // Optional chaining { @@ -248,6 +323,12 @@ ruleTester.run("getter-return", rule, { options, parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "expected", data: { name: "method 'get'" } }] + }, + { + code: "(Object?.create)(foo, { bar: { get: function (){} } });", + options, + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "expected", data: { name: "method 'get'" } }] } ] }); diff --git a/eslint/tests/lib/rules/id-length.js b/eslint/tests/lib/rules/id-length.js index e9a023b..871db90 100644 --- a/eslint/tests/lib/rules/id-length.js +++ b/eslint/tests/lib/rules/id-length.js @@ -113,6 +113,123 @@ ruleTester.run("id-length", rule, { code: "class Foo { #abc = 1 }", options: [{ max: 3 }], parserOptions: { ecmaVersion: 2022 } + }, + + // Identifier consisting of two code units + { + code: "var 𠮟 = 2", + options: [{ min: 1, max: 1 }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var 葛󠄀 = 2", // 2 code points but only 1 grapheme + options: [{ min: 1, max: 1 }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "var a = { 𐌘: 1 };", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "(𐌘) => { 𐌘 * 𐌘 };", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "class 𠮟 { }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "class F { 𐌘() {} }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "class F { #𐌘() {} }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "class F { 𐌘 = 1 }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "class F { #𐌘 = 1 }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "function f(...𐌘) { }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "function f([𐌘]) { }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "var [ 𐌘 ] = a;", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "var { p: [𐌘]} = {};", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "function f({𐌘}) { }", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "var { 𐌘 } = {};", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "var { p: 𐌘} = {};", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "({ prop: o.𐌘 } = {});", + options: [{ min: 1, max: 1 }], + parserOptions: { + ecmaVersion: 6 + } } ], invalid: [ @@ -564,6 +681,157 @@ ruleTester.run("id-length", rule, { errors: [ tooLongErrorPrivate ] + }, + + // Identifier consisting of two code units + { + code: "var 𠮟 = 2", + parserOptions: { ecmaVersion: 6 }, + errors: [ + tooShortError + ] + }, + { + code: "var 葛󠄀 = 2", // 2 code points but only 1 grapheme + parserOptions: { ecmaVersion: 6 }, + errors: [ + tooShortError + ] + }, + { + code: "var myObj = { 𐌘: 1 };", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "(𐌘) => { 𐌘 * 𐌘 };", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "class 𠮟 { }", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "class Foo { 𐌘() {} }", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "class Foo1 { #𐌘() {} }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [ + tooShortErrorPrivate + ] + }, + { + code: "class Foo2 { 𐌘 = 1 }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [ + tooShortError + ] + }, + { + code: "class Foo3 { #𐌘 = 1 }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [ + tooShortErrorPrivate + ] + }, + { + code: "function foo1(...𐌘) { }", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "function foo([𐌘]) { }", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "var [ 𐌘 ] = arr;", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "var { prop: [𐌘]} = {};", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "function foo({𐌘}) { }", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "var { 𐌘 } = {};", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "var { prop: 𐌘} = {};", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] + }, + { + code: "({ prop: obj.𐌘 } = {});", + parserOptions: { + ecmaVersion: 6 + }, + errors: [ + tooShortError + ] } ] }); diff --git a/eslint/tests/lib/rules/key-spacing.js b/eslint/tests/lib/rules/key-spacing.js index dd0fcbc..9ac345d 100644 --- a/eslint/tests/lib/rules/key-spacing.js +++ b/eslint/tests/lib/rules/key-spacing.js @@ -278,7 +278,9 @@ ruleTester.run("key-spacing", rule, { " method() {", " return 42;", " },", - " baz: 456", + " baz: 456,", + " 10: ", + " 10", "};" ].join("\n"), options: [{ align: "value" }], @@ -360,6 +362,10 @@ ruleTester.run("key-spacing", rule, { " bat: function() {", " return this.a;", " },", + " barfoo:", + " [", + " 1", + " ],", " baz: 42", "};" ].join("\n"), @@ -633,6 +639,10 @@ ruleTester.run("key-spacing", rule, { " internalGroup: {", " internal : true,", " ext : false", + " },", + " func3:", + " function () {", + " var test3 = true;", " }", "})" ].join("\n"), @@ -971,6 +981,109 @@ ruleTester.run("key-spacing", rule, { } }], parserOptions: { ecmaVersion: 6 } + }, + + // https://github.com/eslint/eslint/issues/16490 + { + code: ` + var foo = + { + id: 1, + code: 2, + [n]: 3, + message: + "some value on the next line", + }; + `, + options: [{ + align: "value" + }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: ` + var foo = + { + id : 1, + code : 2, + message : + "some value on the next line", + }; + `, + options: [{ + align: "colon", + beforeColon: true + }] + }, + { + code: ` + ({ + a: 1, + // different group + bcd: + 2 + }) + `, + options: [{ + align: "value" + }] + }, + { + code: ` + ({ + foo : 1, + bar : 2, + foobar : + 3 + }) + `, + options: [{ + align: "value", + beforeColon: true, + mode: "minimum" + }] + }, + { + code: ` + ({ + oneLine: 1, + ["some key " + + "spanning multiple lines"]: 2 + }) + `, + options: [{ + align: "value" + }], + parserOptions: { ecmaVersion: 6 } + }, + + // https://github.com/eslint/eslint/issues/16674 + { + code: ` + a = { + item : 123, + longerItem : ( + 1 + 1 + ), + }; + `, + options: [{ + align: { + beforeColon: true, + afterColon: true, + on: "colon" + } + }] + }, + { + code: ` + a = { + item: 123, + longerItem: // a comment - not a token + (1 + 1), + }; + `, + options: [{ align: "value" }] }], invalid: [{ code: "var a ={'key' : value };", @@ -1464,7 +1577,9 @@ ruleTester.run("key-spacing", rule, { " method() {", " return 42;", " },", - " baz: 456", + " baz: 456,", + " 10: ", + " 10", "};" ].join("\n"), output: [ @@ -1473,7 +1588,9 @@ ruleTester.run("key-spacing", rule, { " method() {", " return 42;", " },", - " baz: 456", + " baz: 456,", + " 10: ", + " 10", "};" ].join("\n"), options: [{ align: "value" }], @@ -2374,6 +2491,149 @@ ruleTester.run("key-spacing", rule, { { messageId: "extraValue", data: { computed: "", key: "🎁" }, line: 4, column: 21, type: "Literal" }, { messageId: "extraValue", data: { computed: "", key: "🇮🇳" }, line: 5, column: 23, type: "Literal" } ] - } - ] + }, + + // https://github.com/eslint/eslint/issues/16490 + { + code: ` + var foo = + { + id: 1, + code: 2, + [n]: 3, + message: + "some value on the next line", + }; + `, + output: ` + var foo = + { + id: 1, + code: 2, + [n]: 3, + message: + "some value on the next line", + }; + `, + options: [{ + align: "value" + }], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { messageId: "extraValue", data: { computed: "", key: "id" }, line: 4, column: 19, type: "Literal" }, + { messageId: "extraValue", data: { computed: "", key: "code" }, line: 5, column: 21, type: "Literal" }, + { messageId: "extraValue", data: { computed: "computed ", key: "n" }, line: 6, column: 20, type: "Literal" } + ] + }, + { + code: ` + var foo = + { + id : 1, + code : 2, + message : + "some value on the next line", + }; + `, + output: ` + var foo = + { + id : 1, + code : 2, + message : + "some value on the next line", + }; + `, + options: [{ + align: "colon", + beforeColon: true + }], + errors: [ + { messageId: "extraKey", data: { computed: "", key: "id" }, line: 4, column: 19, type: "Identifier" }, + { messageId: "extraKey", data: { computed: "", key: "code" }, line: 5, column: 21, type: "Identifier" } + ] + }, + { + code: ` + ({ + a: 1, + // different group + bcd: + 2 + }) + `, + output: ` + ({ + a: 1, + // different group + bcd: + 2 + }) + `, + options: [{ + align: "value" + }], + errors: [ + { messageId: "extraValue", data: { computed: "", key: "a" }, line: 3, column: 18, type: "Literal" } + ] + }, + { + code: [ + "({", + " singleLine : 10,", + " newGroup :", + " function() {", + " var test3 = true;", + " }", + "})" + ].join("\n"), + output: [ + "({", + " singleLine: 10,", + " newGroup:", + " function() {", + " var test3 = true;", + " }", + "})" + ].join("\n"), + options: [{ + multiLine: { + beforeColon: false + }, + align: { + on: "colon", + beforeColon: true + } + }], + errors: [ + { messageId: "extraKey", data: { computed: "", key: "singleLine" }, line: 2, column: 15, type: "Identifier" }, + { messageId: "extraKey", data: { computed: "", key: "newGroup" }, line: 3, column: 13, type: "Identifier" } + ] + }, + + // https://github.com/eslint/eslint/issues/16674 + { + code: + ` + c = { + item: 123, + longerItem: ( + 1 + 1 + ), + }; + `, + output: + ` + c = { + item : 123, + longerItem: ( + 1 + 1 + ), + }; + `, + options: [{ align: "colon" }], + errors: [ + { messageId: "missingKey", data: { computed: "", key: "item" }, line: 3, column: 13, type: "Identifier" } + ] + }] }); diff --git a/eslint/tests/lib/rules/lines-around-comment.js b/eslint/tests/lib/rules/lines-around-comment.js index 3796985..a55d6fe 100644 --- a/eslint/tests/lib/rules/lines-around-comment.js +++ b/eslint/tests/lib/rules/lines-around-comment.js @@ -1051,6 +1051,25 @@ ruleTester.run("lines-around-comment", rule, { { code: "foo\n/* this is pragmatic */", options: [{ applyDefaultIgnorePatterns: false, ignorePattern: "pragma" }] + }, + + // Hashbang comment + { + code: "#!comment\n\nvar a = 1;", + options: [{ afterHashbangComment: true }] + }, + "#!comment\nvar a = 1;", + { + code: "#!comment\nvar a = 1;", + options: [{}] + }, + { + code: "#!comment\nvar a = 1;", + options: [{ afterHashbangComment: false }] + }, + { + code: "#!comment\nvar a = 1;", + options: [{ afterLineComment: true, afterBlockComment: true }] } ], @@ -2193,6 +2212,14 @@ ruleTester.run("lines-around-comment", rule, { afterLineComment: true }], errors: [{ messageId: "before", type: "Line" }] + }, + + // Hashbang comment + { + code: "#!foo\nvar a = 1;", + output: "#!foo\n\nvar a = 1;", + options: [{ afterHashbangComment: true }], + errors: [{ messageId: "after", type: "Shebang" }] } ] diff --git a/eslint/tests/lib/rules/logical-assignment-operators.js b/eslint/tests/lib/rules/logical-assignment-operators.js new file mode 100644 index 0000000..ba839b5 --- /dev/null +++ b/eslint/tests/lib/rules/logical-assignment-operators.js @@ -0,0 +1,1460 @@ +/** + * @fileoverview Tests for logical-assignment-operators. + * @author Daniel Martens + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/logical-assignment-operators"), + { RuleTester } = require("../../../lib/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } }); + +ruleTester.run("logical-assignment-operators", rule, { + valid: [ + + // Unrelated + "a || b", + "a && b", + "a ?? b", + "a || a || b", + "var a = a || b", + "a === undefined ? a : b", + "while (a) a = b", + + // Preferred + "a ||= b", + "a &&= b", + "a ??= b", + + // > Operator + "a += a || b", + "a *= a || b", + "a ||= a || b", + "a &&= a || b", + + // > Right + "a = a", + "a = b", + "a = a === b", + "a = a + b", + "a = a / b", + "a = fn(a) || b", + + // > Reference + "a = false || c", + "a = f() || g()", + "a = b || c", + "a = b || a", + "object.a = object.b || c", + "[a] = a || b", + "({ a } = a || b)", + + // Logical + "(a = b) || a", + "a + (a = b)", + "a || (b ||= c)", + "a || (b &&= c)", + "a || b === 0", + "a || fn()", + "a || (b && c)", + "a || (b ?? c)", + + // > Reference + "a || (b = c)", + "a || (a ||= b)", + "fn() || (a = b)", + "a.b || (a = b)", + "a?.b || (a.b = b)", + { + code: "class Class { #prop; constructor() { this.#prop || (this.prop = value) } }", + parserOptions: { ecmaVersion: 2022 } + }, { + code: "class Class { #prop; constructor() { this.prop || (this.#prop = value) } }", + parserOptions: { ecmaVersion: 2022 } + }, + + // If + "if (a) a = b", + { + code: "if (a) a = b", + options: ["always", { enforceForIfStatements: false }] + }, { + code: "if (a) { a = b } else {}", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) { a = b } else if (a) {}", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (unrelated) {} else if (a) a = b; else {}", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (unrelated) {} else if (a) a = b; else if (unrelated) {}", + options: ["always", { enforceForIfStatements: true }] + }, + + // > Body + { + code: "if (a) {}", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) { before; a = b }", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) { a = b; after }", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) throw new Error()", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) a", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) a ||= b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) b = a", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) { a() }", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a) { a += a || b }", + options: ["always", { enforceForIfStatements: true }] + }, + + // > Test + { + code: "if (true) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (predicate(a)) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a?.b) a.b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (!a?.b) a.b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === b) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === undefined) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a != null) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null && a === undefined) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === 0 || a === undefined) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === 1) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a == null || a == undefined) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === !0) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === +0) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === null) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === undefined || a === void 0) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === void void 0) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === void 'string') a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === void fn()) a = b", + options: ["always", { enforceForIfStatements: true }] + }, + + // > Test > Yoda + { + code: "if (a == a) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a == b) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null == null) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (undefined == undefined) undefined = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null == x) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null == fn()) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null === a || a === 0) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (0 === a || null === a) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (1 === a || a === undefined) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (undefined === a || 1 === a) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === b) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (b === undefined || a === null) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null === a || b === a) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null === null || undefined === undefined) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null === null || a === a) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (undefined === undefined || a === a) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (null === undefined || a === a) a = b", + options: ["always", { enforceForIfStatements: true }] + }, + + // > Test > Undefined + { + code: [ + "{", + " const undefined = 0;", + " if (a == undefined) a = b", + "}" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }] + }, { + code: [ + "(() => {", + " const undefined = 0;", + " if (condition) {", + " if (a == undefined) a = b", + " }", + "})()" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }] + }, { + code: [ + "{", + " if (a == undefined) a = b", + "}", + "var undefined = 0;" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }] + }, { + code: [ + "{", + " const undefined = 0;", + " if (undefined == null) undefined = b", + "}" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }] + }, { + code: [ + "{", + " const undefined = 0;", + " if (a === undefined || a === null) a = b", + "}" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }] + }, { + code: [ + "{", + " const undefined = 0;", + " if (undefined === a || null === a) a = b", + "}" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }] + }, + + // > Reference + { + code: "if (a) b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (!a) b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (!!a) b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a == null) b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || a === undefined) b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || b === undefined) a = b", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (a === null || b === undefined) b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: "if (Boolean(a)) b = c", + options: ["always", { enforceForIfStatements: true }] + }, { + code: [ + "function fn(Boolean) {", + " if (Boolean(a)) a = b", + "}" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }] + }, + + // Never + { + code: "a = a || b", + options: ["never"] + }, { + code: "a = a && b", + options: ["never"] + }, { + code: "a = a ?? b", + options: ["never"] + }, { + code: "a = b", + options: ["never"] + }, { + code: "a += b", + options: ["never"] + }, { + code: "a -= b", + options: ["never"] + }, { + code: "a.b = a.b || c", + options: ["never"] + } + ], + invalid: [ + + // Assignment + { + code: "a = a || b", + output: "a ||= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a && b", + output: "a &&= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "&&=" }, suggestions: [] }] + }, { + code: "a = a ?? b", + output: "a ??= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "foo = foo || bar", + output: "foo ||= bar", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, + + // > Right + { + code: "a = a || fn()", + output: "a ||= fn()", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || b && c", + output: "a ||= b && c", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || (b || c)", + output: "a ||= (b || c)", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || (b ? c : d)", + output: "a ||= (b ? c : d)", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, + + // > Comments + { + code: "/* before */ a = a || b", + output: "/* before */ a ||= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || b // after", + output: "a ||= b // after", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a /* between */ = a || b", + output: null, + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = /** @type */ a || b", + output: null, + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || /* between */ b", + output: null, + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, + + // > Parenthesis + { + code: "(a) = a || b", + output: "(a) ||= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = (a) || b", + output: "a ||= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || (b)", + output: "a ||= (b)", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || ((b))", + output: "a ||= ((b))", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "(a = a || b)", + output: "(a ||= b)", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || (f(), b)", + output: "a ||= (f(), b)", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, + + // > Suggestions + { + code: "a.b = a.b ?? c", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "??=" }, + output: "a.b ??= c" + }] + }] + }, { + code: "a.b.c = a.b.c ?? d", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "??=" }, + output: "a.b.c ??= d" + }] + }] + }, { + code: "a[b] = a[b] ?? c", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "??=" }, + output: "a[b] ??= c" + }] + }] + }, { + code: "a['b'] = a['b'] ?? c", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "??=" }, + output: "a['b'] ??= c" + }] + }] + }, { + code: "a.b = a['b'] ?? c", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "??=" }, + output: "a.b ??= c" + }] + }] + }, { + code: "a['b'] = a.b ?? c", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "??=" }, + output: "a['b'] ??= c" + }] + }] + }, { + code: "this.prop = this.prop ?? {}", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "??=" }, + output: "this.prop ??= {}" + }] + }] + }, + + // > With + { + code: "with (object) a = a || b", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "||=" }, + + output: "with (object) a ||= b" + }] + }] + }, { + code: "with (object) { a = a || b }", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "||=" }, + output: "with (object) { a ||= b }" + }] + }] + }, { + code: "with (object) { if (condition) a = a || b }", + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "||=" }, + output: "with (object) { if (condition) a ||= b }" + }] + }] + }, { + code: "with (a = a || b) {}", + output: "with (a ||= b) {}", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "with (object) {} a = a || b", + output: "with (object) {} a ||= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = a || b; with (object) {}", + output: "a ||= b; with (object) {}", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "if (condition) a = a || b", + output: "if (condition) a ||= b", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: [ + "with (object) {", + ' "use strict";', + " a = a || b", + "}" + ].join("\n"), + output: null, + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "useLogicalOperator", + data: { operator: "||=" }, + output: [ + "with (object) {", + ' "use strict";', + " a ||= b", + "}" + ].join("\n") + }] + }] + }, + + // > Context + { + code: "fn(a = a || b)", + output: "fn(a ||= b)", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "fn((a = a || b))", + output: "fn((a ||= b))", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "(a = a || b) ? c : d", + output: "(a ||= b) ? c : d", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, { + code: "a = b = b || c", + output: "a = b ||= c", + errors: [{ messageId: "assignment", type: "AssignmentExpression", data: { operator: "||=" }, suggestions: [] }] + }, + + // Logical + { + code: "a || (a = b)", + output: "a ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a && (a = b)", + output: "a &&= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "&&=" } }] + }, { + code: "a ?? (a = b)", + output: "a ??= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "??=" } }] + }, { + code: "foo ?? (foo = bar)", + output: "foo ??= bar", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "??=" } }] + }, + + // > Right + { + code: "a || (a = 0)", + output: "a ||= 0", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || (a = fn())", + output: "a ||= fn()", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || (a = (b || c))", + output: "a ||= (b || c)", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, + + // > Parenthesis + { + code: "(a) || (a = b)", + output: "a ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || ((a) = b)", + output: "(a) ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || (a = (b))", + output: "a ||= (b)", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || ((a = b))", + output: "a ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || (((a = b)))", + output: "a ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || ( ( a = b ) )", + output: "a ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, + + // > Comments + { + code: "/* before */ a || (a = b)", + output: "/* before */ a ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || (a = b) // after", + output: "a ||= b // after", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a /* between */ || (a = b)", + output: null, + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || /* between */ (a = b)", + output: null, + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, + + // > Fix Condition + { + code: "a.b || (a.b = c)", + output: "a.b ||= c", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "class Class { #prop; constructor() { this.#prop || (this.#prop = value) } }", + output: "class Class { #prop; constructor() { this.#prop ||= value } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a['b'] || (a['b'] = c)", + output: "a['b'] ||= c", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a[0] || (a[0] = b)", + output: "a[0] ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a[this] || (a[this] = b)", + output: "a[this] ||= b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "foo.bar || (foo.bar = baz)", + output: "foo.bar ||= baz", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a.b.c || (a.b.c = d)", + output: null, + errors: [{ + messageId: "logical", + type: "LogicalExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "convertLogical", + data: { operator: "||=" }, + output: "a.b.c ||= d" + }] + }] + }, { + code: "a[b.c] || (a[b.c] = d)", + output: null, + errors: [{ + messageId: "logical", + type: "LogicalExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "convertLogical", + data: { operator: "||=" }, + output: "a[b.c] ||= d" + }] + }] + }, { + code: "a[b?.c] || (a[b?.c] = d)", + output: null, + errors: [{ + messageId: "logical", + type: "LogicalExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "convertLogical", + data: { operator: "||=" }, + output: "a[b?.c] ||= d" + }] + }] + }, { + code: "with (object) a.b || (a.b = c)", + output: null, + errors: [{ + messageId: "logical", + type: "LogicalExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "convertLogical", + data: { operator: "||=" }, + output: "with (object) a.b ||= c" + }] + }] + }, + + // > Context + { + code: "a = a.b || (a.b = {})", + output: "a = a.b ||= {}", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" }, suggestions: [] }] + }, + { + code: "a || (a = 0) || b", + output: "(a ||= 0) || b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "(a || (a = 0)) || b", + output: "(a ||= 0) || b", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || (b || (b = 0))", + output: "a || (b ||= 0)", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a = b || (b = c)", + output: "a = b ||= c", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "a || (a = 0) ? b : c", + output: "(a ||= 0) ? b : c", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, { + code: "fn(a || (a = 0))", + output: "fn(a ||= 0)", + errors: [{ messageId: "logical", type: "LogicalExpression", data: { operator: "||=" } }] + }, + + // If + { + code: "if (a) a = b", + output: "a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (Boolean(a)) a = b", + output: "a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (!!a) a = b", + output: "a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (!a) a = b", + output: "a ||= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "||=" } }] + }, { + code: "if (!Boolean(a)) a = b", + output: "a ||= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "||=" } }] + }, { + code: "if (a == undefined) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, { + code: "if (a == null) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, { + code: "if (a === null || a === undefined) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, { + code: "if (a === undefined || a === null) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, { + code: "if (a === null || a === void 0) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, { + code: "if (a === void 0 || a === null) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, { + code: "if (a) { a = b; }", + output: "a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: [ + "{ const undefined = 0; }", + "if (a == undefined) a = b" + ].join("\n"), + output: [ + "{ const undefined = 0; }", + "a ??= b" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, { + code: [ + "if (a == undefined) a = b", + "{ const undefined = 0; }" + ].join("\n"), + output: [ + "a ??= b", + "{ const undefined = 0; }" + ].join("\n"), + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" } }] + }, + + // > Yoda + { + code: "if (null == a) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "if (undefined == a) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "if (undefined === a || a === null) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "if (a === undefined || null === a) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "if (undefined === a || null === a) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "if (null === a || a === undefined) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "if (a === null || undefined === a) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, { + code: "if (null === a || undefined === a) a = b", + output: "a ??= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "??=" }, suggestions: [] }] + }, + + // > Parenthesis + { + code: "if ((a)) a = b", + output: "a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) (a) = b", + output: "(a) &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) a = (b)", + output: "a &&= (b)", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) (a = b)", + output: "(a &&= b)", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // > Previous statement + { + code: ";if (a) (a) = b", + output: ";(a) &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "{ if (a) (a) = b }", + output: "{ (a) &&= b }", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "fn();if (a) (a) = b", + output: "fn();(a) &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "fn()\nif (a) a = b", + output: "fn()\na &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "id\nif (a) (a) = b", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "object.prop\nif (a) (a) = b", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "object[computed]\nif (a) (a) = b", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "fn()\nif (a) (a) = b", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // > Adding semicolon + { + code: "if (a) a = b; fn();", + output: "a &&= b; fn();", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) { a = b }", + output: "a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) { a = b; }\nfn();", + output: "a &&= b;\nfn();", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) { a = b }\nfn();", + output: "a &&= b;\nfn();", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) { a = b } fn();", + output: "a &&= b; fn();", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) { a = b\n} fn();", + output: "a &&= b; fn();", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // > Spacing + { + code: "if (a) a = b", + output: "a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a)\n a = b", + output: "a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) {\n a = b; \n}", + output: "a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // > Comments + { + code: "/* before */ if (a) a = b", + output: "/* before */ a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) a = b /* after */", + output: "a &&= b /* after */", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) /* between */ a = b", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) a = /* between */ b", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // > Members > Single Property Access + { + code: "if (a.b) a.b = c", + output: "a.b &&= c", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" }, suggestions: [] }] + }, { + code: "if (a[b]) a[b] = c", + output: "a[b] &&= c", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" }, suggestions: [] }] + }, { + code: "if (a['b']) a['b'] = c", + output: "a['b'] &&= c", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" }, suggestions: [] }] + }, { + code: "if (this.prop) this.prop = value", + output: "this.prop &&= value", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", suggestions: [] }] + }, { + code: "(class extends SuperClass { method() { if (super.prop) super.prop = value } })", + output: "(class extends SuperClass { method() { super.prop &&= value } })", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" }, suggestions: [] }] + }, { + code: "with (object) if (a) a = b", + output: "with (object) a &&= b", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" }, suggestions: [] }] + }, + + // > Members > Possible Multiple Property Accesses + { + code: "if (a.b === undefined || a.b === null) a.b = c", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ + messageId: "if", + type: "IfStatement", + data: { operator: "??=" }, + suggestions: [{ + messageId: "convertIf", + data: { operator: "??=" }, + output: "a.b ??= c" + }] + }] + }, { + code: "if (a.b.c) a.b.c = d", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ + messageId: "if", + type: "IfStatement", + data: { operator: "&&=" }, + suggestions: [{ + messageId: "convertIf", + data: { operator: "&&=" }, + output: "a.b.c &&= d" + }] + }] + }, { + code: "if (a.b.c.d) a.b.c.d = e", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ + messageId: "if", + type: "IfStatement", + data: { operator: "&&=" }, + suggestions: [{ + messageId: "convertIf", + data: { operator: "&&=" }, + output: "a.b.c.d &&= e" + }] + }] + }, { + code: "if (a[b].c) a[b].c = d", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ + messageId: "if", + type: "IfStatement", + data: { operator: "&&=" }, + suggestions: [{ + messageId: "convertIf", + data: { operator: "&&=" }, + output: "a[b].c &&= d" + }] + }] + }, { + code: "with (object) if (a.b) a.b = c", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ + messageId: "if", + type: "IfStatement", + data: { operator: "&&=" }, + suggestions: [{ + messageId: "convertIf", + data: { operator: "&&=" }, + output: "with (object) a.b &&= c" + }] + }] + }, + + // > Else if + { + code: "if (unrelated) {} else if (a) a = b;", + output: "if (unrelated) {} else a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (a) {} else if (b) {} else if (a) a = b;", + output: "if (a) {} else if (b) {} else a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) {} else\nif (a) a = b;", + output: "if (unrelated) {} else\na &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) {\n}\nelse if (a) {\na = b;\n}", + output: "if (unrelated) {\n}\nelse a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) statement; else if (a) a = b;", + output: "if (unrelated) statement; else a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) id\nelse if (a) (a) = b", + output: null, + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) {} else if (a) a = b; else if (c) c = d", + output: "if (unrelated) {} else if (a) a = b; else c &&= d", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // > Else if > Comments + { + code: "if (unrelated) { /* body */ } else if (a) a = b;", + output: "if (unrelated) { /* body */ } else a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) {} /* before else */ else if (a) a = b;", + output: "if (unrelated) {} /* before else */ else a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) {} else // Line\nif (a) a = b;", + output: "if (unrelated) {} else // Line\na &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, { + code: "if (unrelated) {} else /* Block */ if (a) a = b;", + output: "if (unrelated) {} else /* Block */ a &&= b;", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // > Patterns + { + code: "if (array) array = array.filter(predicate)", + output: "array &&= array.filter(predicate)", + options: ["always", { enforceForIfStatements: true }], + errors: [{ messageId: "if", type: "IfStatement", data: { operator: "&&=" } }] + }, + + // Never + { + code: "a ||= b", + output: "a = a || b", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "a &&= b", + output: "a = a && b", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "&&=" } }] + }, { + code: "a ??= b", + output: "a = a ?? b", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "??=" } }] + }, { + code: "foo ||= bar", + output: "foo = foo || bar", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, + + // > Suggestions + { + code: "a.b ||= c", + output: null, + options: ["never"], + errors: [{ + messageId: "unexpected", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "separate", + output: "a.b = a.b || c" + }] + }] + }, { + code: "a[b] ||= c", + output: null, + options: ["never"], + errors: [{ + messageId: "unexpected", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "separate", + output: "a[b] = a[b] || c" + }] + }] + }, { + code: "a['b'] ||= c", + output: null, + options: ["never"], + errors: [{ + messageId: "unexpected", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "separate", + output: "a['b'] = a['b'] || c" + }] + }] + }, { + code: "this.prop ||= 0", + output: null, + options: ["never"], + errors: [{ + messageId: "unexpected", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "separate", + output: "this.prop = this.prop || 0" + }] + }] + }, { + code: "with (object) a ||= b", + output: null, + options: ["never"], + errors: [{ + messageId: "unexpected", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "separate", + output: "with (object) a = a || b" + }] + }] + }, + + // > Parenthesis + { + code: "(a) ||= b", + output: "(a) = a || b", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "a ||= (b)", + output: "a = a || (b)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "(a ||= b)", + output: "(a = a || b)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, + + // > Comments + { + code: "/* before */ a ||= b", + output: "/* before */ a = a || b", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "a ||= b // after", + output: "a = a || b // after", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "a /* before */ ||= b", + output: null, + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "a ||= /* after */ b", + output: null, + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, + + // > Precedence + { + code: "a ||= b && c", + output: "a = a || b && c", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "a &&= b || c", + output: "a = a && (b || c)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "&&=" } }] + }, { + code: "a ||= b || c", + output: "a = a || (b || c)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, { + code: "a &&= b && c", + output: "a = a && (b && c)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "&&=" } }] + }, + + // > Mixed + { + code: "a ??= b || c", + output: "a = a ?? (b || c)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "??=" } }] + }, { + code: "a ??= b && c", + output: "a = a ?? (b && c)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "??=" } }] + }, { + code: "a ??= b ?? c", + output: "a = a ?? (b ?? c)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "??=" } }] + }, { + code: "a ??= (b || c)", + output: "a = a ?? (b || c)", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "??=" } }] + }, { + code: "a ??= b + c", + output: "a = a ?? b + c", + options: ["never"], + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "??=" } }] + }] +}); diff --git a/eslint/tests/lib/rules/multiline-comment-style.js b/eslint/tests/lib/rules/multiline-comment-style.js index 1f2302f..a127d7e 100644 --- a/eslint/tests/lib/rules/multiline-comment-style.js +++ b/eslint/tests/lib/rules/multiline-comment-style.js @@ -628,6 +628,20 @@ ruleTester.run("multiline-comment-style", rule, { options: ["separate-lines"], errors: [{ messageId: "expectedLines", line: 2 }] }, + { + code: ` + /** + * JSDoc + * Comment + */ + `, + output: ` + // JSDoc + // Comment + `, + options: ["separate-lines", { checkJSDoc: true }], + errors: [{ messageId: "expectedLines", line: 2 }] + }, { code: ` /* foo diff --git a/eslint/tests/lib/rules/no-constant-binary-expression.js b/eslint/tests/lib/rules/no-constant-binary-expression.js index c430c77..d931e83 100644 --- a/eslint/tests/lib/rules/no-constant-binary-expression.js +++ b/eslint/tests/lib/rules/no-constant-binary-expression.js @@ -59,7 +59,11 @@ ruleTester.run("no-constant-binary-expression", rule, { "function foo(undefined) { undefined === true;}", "[...arr, 1] == true", "[,,,] == true", - { code: "new Foo() === bar;", globals: { Foo: "writable" } } + { code: "new Foo() === bar;", globals: { Foo: "writable" } }, + "(foo && true) ?? bar", + "foo ?? null ?? bar", + "a ?? (doSomething(), undefined) ?? b", + "a ?? (something = null) ?? b" ], invalid: [ @@ -308,6 +312,10 @@ ruleTester.run("no-constant-binary-expression", rule, { { code: "x === /[a-z]/", errors: [{ messageId: "alwaysNew" }] }, // It's not obvious what this does, but it compares the old value of `x` to the new object. - { code: "x === (x = {})", errors: [{ messageId: "alwaysNew" }] } + { code: "x === (x = {})", errors: [{ messageId: "alwaysNew" }] }, + + { code: "window.abc && false && anything", errors: [{ messageId: "constantShortCircuit" }] }, + { code: "window.abc || true || anything", errors: [{ messageId: "constantShortCircuit" }] }, + { code: "window.abc ?? 'non-nullish' ?? anything", errors: [{ messageId: "constantShortCircuit" }] } ] }); diff --git a/eslint/tests/lib/rules/no-empty-static-block.js b/eslint/tests/lib/rules/no-empty-static-block.js new file mode 100644 index 0000000..592c840 --- /dev/null +++ b/eslint/tests/lib/rules/no-empty-static-block.js @@ -0,0 +1,51 @@ +/** + * @fileoverview Tests for no-empty-static-block rule. + * @author Sosuke Suzuki + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/no-empty-static-block"), + { RuleTester } = require("../../../lib/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 2022 } +}); + +ruleTester.run("no-empty-static-block", rule, { + valid: [ + "class Foo { static { bar(); } }", + "class Foo { static { /* comments */ } }", + "class Foo { static {\n// comment\n} }", + "class Foo { static { bar(); } static { bar(); } }" + ], + invalid: [ + { + code: "class Foo { static {} }", + errors: [{ messageId: "unexpected" }] + }, + { + code: "class Foo { static { } }", + errors: [{ messageId: "unexpected" }] + }, + { + code: "class Foo { static { \n\n } }", + errors: [{ messageId: "unexpected" }] + }, + { + code: "class Foo { static { bar(); } static {} }", + errors: [{ messageId: "unexpected" }] + }, + { + code: "class Foo { static // comment\n {} }", + errors: [{ messageId: "unexpected" }] + } + ] +}); diff --git a/eslint/tests/lib/rules/no-empty.js b/eslint/tests/lib/rules/no-empty.js index 98651a4..812aa8d 100644 --- a/eslint/tests/lib/rules/no-empty.js +++ b/eslint/tests/lib/rules/no-empty.js @@ -44,36 +44,187 @@ ruleTester.run("no-empty", rule, { { code: "try { foo(); } catch (ex) {} finally { bar(); }", options: [{ allowEmptyCatch: true }] } ], invalid: [ - { code: "try {} catch (ex) {throw ex}", errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] }, - { code: "try { foo() } catch (ex) {}", errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] }, - { code: "try { foo() } catch (ex) {throw ex} finally {}", errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] }, - { code: "if (foo) {}", errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] }, - { code: "while (foo) {}", errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] }, - { code: "for (;foo;) {}", errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] }, - { code: "switch(foo) {}", errors: [{ messageId: "unexpected", data: { type: "switch" }, type: "SwitchStatement" }] }, + { + code: "try {} catch (ex) {throw ex}", + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "try { /* empty */ } catch (ex) {throw ex}" + }] + }] + }, + { + code: "try { foo() } catch (ex) {}", + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "try { foo() } catch (ex) { /* empty */ }" + }] + }] + }, + { + code: "try { foo() } catch (ex) {throw ex} finally {}", + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "try { foo() } catch (ex) {throw ex} finally { /* empty */ }" + }] + }] + }, + { + code: "if (foo) {}", + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "if (foo) { /* empty */ }" + }] + }] + }, + { + code: "while (foo) {}", + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "while (foo) { /* empty */ }" + }] + }] + }, + { + code: "for (;foo;) {}", + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "for (;foo;) { /* empty */ }" + }] + }] + }, + { + code: "switch(foo) {}", + errors: [{ + messageId: "unexpected", + data: { type: "switch" }, + type: "SwitchStatement", + suggestions: null + }] + }, + { + code: "switch (foo) { /* empty */ }", + errors: [{ + messageId: "unexpected", + data: { type: "switch" }, + type: "SwitchStatement", + suggestions: null + }] + }, { code: "try {} catch (ex) {}", options: [{ allowEmptyCatch: true }], - errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "try { /* empty */ } catch (ex) {}" + }] + }] }, { code: "try { foo(); } catch (ex) {} finally {}", options: [{ allowEmptyCatch: true }], - errors: [{ messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }] + errors: [{ + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [{ + messageId: "suggestComment", + data: { type: "block" }, + output: "try { foo(); } catch (ex) {} finally { /* empty */ }" + }] + }] }, { code: "try {} catch (ex) {} finally {}", options: [{ allowEmptyCatch: true }], errors: [ - { messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }, - { messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" } + { + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [ + { + messageId: "suggestComment", + data: { type: "block" }, + output: "try { /* empty */ } catch (ex) {} finally {}" + } + ] + }, + { + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [ + { + messageId: "suggestComment", + data: { type: "block" }, + output: "try {} catch (ex) {} finally { /* empty */ }" + } + ] + } ] }, { code: "try { foo(); } catch (ex) {} finally {}", errors: [ - { messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" }, - { messageId: "unexpected", data: { type: "block" }, type: "BlockStatement" } + { + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [ + { + messageId: "suggestComment", + data: { type: "block" }, + output: "try { foo(); } catch (ex) { /* empty */ } finally {}" + } + ] + }, + { + messageId: "unexpected", + data: { type: "block" }, + type: "BlockStatement", + suggestions: [ + { + messageId: "suggestComment", + data: { type: "block" }, + output: "try { foo(); } catch (ex) {} finally { /* empty */ }" + } + ] + } ] } ] diff --git a/eslint/tests/lib/rules/no-extra-parens.js b/eslint/tests/lib/rules/no-extra-parens.js index 557b641..c51a47c 100644 --- a/eslint/tests/lib/rules/no-extra-parens.js +++ b/eslint/tests/lib/rules/no-extra-parens.js @@ -735,6 +735,54 @@ ruleTester.run("no-extra-parens", rule, { code: "var foo = (function(){}?.call())", options: ["all", { enforceForFunctionPrototypeMethods: false }], parserOptions: { ecmaVersion: 2020 } + }, + { + code: "(Object.prototype.toString.call())", + options: ["functions"] + }, + + // "allowParensAfterCommentPattern" option + { + code: "const span = /**@type {HTMLSpanElement}*/(event.currentTarget);", + options: ["all", { allowParensAfterCommentPattern: "@type" }] + }, + { + code: "if (/** @type {Compiler | MultiCompiler} */(options).hooks) console.log('good');", + options: ["all", { allowParensAfterCommentPattern: "@type" }] + }, + { + code: ` + validate(/** @type {Schema} */ (schema), options, { + name: "Dev Server", + baseDataPath: "options", + }); + `, + options: ["all", { allowParensAfterCommentPattern: "@type" }] + }, + { + code: ` + if (condition) { + /** @type {ServerOptions} */ + (options.server.options).requestCert = false; + } + `, + options: ["all", { allowParensAfterCommentPattern: "@type" }] + }, + { + code: "const net = ipaddr.parseCIDR(/** @type {string} */ (cidr));", + options: ["all", { allowParensAfterCommentPattern: "@type" }] + }, + + // https://github.com/eslint/eslint/issues/16850 + "(a) = function () {};", + "(a) = () => {};", + "(a) = class {};", + "(a) ??= function () {};", + "(a) &&= class extends SuperClass {};", + "(a) ||= async () => {}", + { + code: "((a)) = function () {};", + options: ["functions"] } ], @@ -3187,6 +3235,160 @@ ruleTester.run("no-extra-parens", rule, { errors: [{ messageId: "unexpected" }] }, + // "allowParensAfterCommentPattern" option (off by default) + { + code: "const span = /**@type {HTMLSpanElement}*/(event.currentTarget);", + output: "const span = /**@type {HTMLSpanElement}*/event.currentTarget;", + options: ["all"], + errors: [{ messageId: "unexpected" }] + }, + { + code: "if (/** @type {Compiler | MultiCompiler} */(options).hooks) console.log('good');", + output: "if (/** @type {Compiler | MultiCompiler} */options.hooks) console.log('good');", + options: ["all"], + errors: [{ messageId: "unexpected" }] + }, + { + code: ` + validate(/** @type {Schema} */ (schema), options, { + name: "Dev Server", + baseDataPath: "options", + }); + `, + output: ` + validate(/** @type {Schema} */ schema, options, { + name: "Dev Server", + baseDataPath: "options", + }); + `, + options: ["all"], + errors: [{ messageId: "unexpected" }] + }, + { + code: ` + if (condition) { + /** @type {ServerOptions} */ + (options.server.options).requestCert = false; + } + `, + output: ` + if (condition) { + /** @type {ServerOptions} */ + options.server.options.requestCert = false; + } + `, + options: ["all"], + errors: [{ messageId: "unexpected" }] + }, + { + code: "const net = ipaddr.parseCIDR(/** @type {string} */ (cidr));", + output: "const net = ipaddr.parseCIDR(/** @type {string} */ cidr);", + options: ["all"], + errors: [{ messageId: "unexpected" }] + }, + { + code: "const span = /**@type {HTMLSpanElement}*/(event.currentTarget);", + output: "const span = /**@type {HTMLSpanElement}*/event.currentTarget;", + options: ["all", { allowParensAfterCommentPattern: "invalid" }], + errors: [{ messageId: "unexpected" }] + }, + { + code: "if (/** @type {Compiler | MultiCompiler} */(options).hooks) console.log('good');", + output: "if (/** @type {Compiler | MultiCompiler} */options.hooks) console.log('good');", + options: ["all", { allowParensAfterCommentPattern: "invalid" }], + errors: [{ messageId: "unexpected" }] + }, + { + code: ` + validate(/** @type {Schema} */ (schema), options, { + name: "Dev Server", + baseDataPath: "options", + }); + `, + output: ` + validate(/** @type {Schema} */ schema, options, { + name: "Dev Server", + baseDataPath: "options", + }); + `, + options: ["all", { allowParensAfterCommentPattern: "invalid" }], + errors: [{ messageId: "unexpected" }] + }, + { + code: ` + if (condition) { + /** @type {ServerOptions} */ + (options.server.options).requestCert = false; + } + `, + output: ` + if (condition) { + /** @type {ServerOptions} */ + options.server.options.requestCert = false; + } + `, + options: ["all", { allowParensAfterCommentPattern: "invalid" }], + errors: [{ messageId: "unexpected" }] + }, + { + code: ` + if (condition) { + /** @type {ServerOptions} */ + /** extra comment */ + (options.server.options).requestCert = false; + } + `, + output: ` + if (condition) { + /** @type {ServerOptions} */ + /** extra comment */ + options.server.options.requestCert = false; + } + `, + options: ["all", { allowParensAfterCommentPattern: "@type" }], + errors: [{ messageId: "unexpected" }] + }, + { + code: ` + if (condition) { + /** @type {ServerOptions} */ + ((options.server.options)).requestCert = false; + } + `, + output: ` + if (condition) { + /** @type {ServerOptions} */ + (options.server.options).requestCert = false; + } + `, + options: ["all", { allowParensAfterCommentPattern: "@type" }], + errors: [{ messageId: "unexpected" }] + }, + { + code: ` + if (condition) { + /** @type {ServerOptions} */ + let foo = "bar"; + (options.server.options).requestCert = false; + } + `, + output: ` + if (condition) { + /** @type {ServerOptions} */ + let foo = "bar"; + options.server.options.requestCert = false; + } + `, + options: ["all", { allowParensAfterCommentPattern: "@type" }], + errors: [{ messageId: "unexpected" }] + }, + { + code: "const net = ipaddr.parseCIDR(/** @type {string} */ (cidr));", + output: "const net = ipaddr.parseCIDR(/** @type {string} */ cidr);", + options: ["all", { allowParensAfterCommentPattern: "invalid" }], + errors: [{ messageId: "unexpected" }] + }, + // Optional chaining { code: "var v = (obj?.aaa)?.aaa", @@ -3213,6 +3415,35 @@ ruleTester.run("no-extra-parens", rule, { options: ["all", { enforceForFunctionPrototypeMethods: true }], parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "unexpected" }] - } + }, + { + code: "(Object.prototype.toString.call())", + output: "Object.prototype.toString.call()", + options: ["all"], + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpected" }] + }, + + // https://github.com/eslint/eslint/issues/16850 + invalid("(a) = function foo() {};", "a = function foo() {};", "Identifier"), + invalid("(a) = class Bar {};", "a = class Bar {};", "Identifier"), + invalid("(a.b) = function () {};", "a.b = function () {};", "MemberExpression"), + { + code: "(newClass) = [(one)] = class { static * [Symbol.iterator]() { yield 1; } };", + output: "newClass = [one] = class { static * [Symbol.iterator]() { yield 1; } };", + errors: [ + { messageId: "unexpected", type: "Identifier" }, + { messageId: "unexpected", type: "Identifier" } + ] + }, + invalid("((a)) = () => {};", "(a) = () => {};", "Identifier"), + invalid("(a) = (function () {})();", "a = (function () {})();", "Identifier"), + ...["**=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", ">>>=", "&=", "^=", "|="].map( + operator => invalid( + `(a) ${operator} function () {};`, + `a ${operator} function () {};`, + "Identifier" + ) + ) ] }); diff --git a/eslint/tests/lib/rules/no-fallthrough.js b/eslint/tests/lib/rules/no-fallthrough.js index d6f6c6f..80fa617 100644 --- a/eslint/tests/lib/rules/no-fallthrough.js +++ b/eslint/tests/lib/rules/no-fallthrough.js @@ -63,6 +63,7 @@ ruleTester.run("no-fallthrough", rule, { "switch (foo) { case 0: try {} finally { break; } default: b(); }", "switch (foo) { case 0: try { throw 0; } catch (err) { break; } default: b(); }", "switch (foo) { case 0: do { throw 0; } while(a); default: b(); }", + "switch (foo) { case 0: a(); \n// eslint-disable-next-line no-fallthrough\n case 1: }", { code: "switch(foo) { case 0: a(); /* no break */ case 1: b(); }", options: [{ @@ -297,6 +298,18 @@ ruleTester.run("no-fallthrough", rule, { column: 34 } ] + }, + { + code: "switch (foo) { case 0: a(); \n// eslint-enable no-fallthrough\n case 1: }", + options: [{}], + errors: [ + { + messageId: "case", + type: "SwitchCase", + line: 3, + column: 2 + } + ] } ] }); diff --git a/eslint/tests/lib/rules/no-implicit-coercion.js b/eslint/tests/lib/rules/no-implicit-coercion.js index f7ca9dc..e935081 100644 --- a/eslint/tests/lib/rules/no-implicit-coercion.js +++ b/eslint/tests/lib/rules/no-implicit-coercion.js @@ -104,7 +104,12 @@ ruleTester.run("no-implicit-coercion", rule, { { code: "String(foo) + ``", parserOptions: { ecmaVersion: 6 } }, { code: "`${'foo'}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } }, { code: "`${`foo`}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } }, - { code: "`${String(foo)}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } } + { code: "`${String(foo)}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } }, + + // https://github.com/eslint/eslint/issues/16373 + "console.log(Math.PI * 1/4)", + "a * 1 / 2", + "a * 1 / b" ], invalid: [ { @@ -426,6 +431,44 @@ ruleTester.run("no-implicit-coercion", rule, { data: { recommendation: "(foo?.indexOf)(1) !== -1" }, type: "UnaryExpression" }] + }, + + // https://github.com/eslint/eslint/issues/16373 regression tests + { + code: "1 * a / 2", + output: "Number(a) / 2", + errors: [{ + messageId: "useRecommendation", + data: { recommendation: "Number(a)" }, + type: "BinaryExpression" + }] + }, + { + code: "(a * 1) / 2", + output: "(Number(a)) / 2", + errors: [{ + messageId: "useRecommendation", + data: { recommendation: "Number(a)" }, + type: "BinaryExpression" + }] + }, + { + code: "a * 1 / (b * 1)", + output: "a * 1 / (Number(b))", + errors: [{ + messageId: "useRecommendation", + data: { recommendation: "Number(b)" }, + type: "BinaryExpression" + }] + }, + { + code: "a * 1 + 2", + output: "Number(a) + 2", + errors: [{ + messageId: "useRecommendation", + data: { recommendation: "Number(a)" }, + type: "BinaryExpression" + }] } ] }); diff --git a/eslint/tests/lib/rules/no-implicit-globals.js b/eslint/tests/lib/rules/no-implicit-globals.js index 348ef6f..412ba76 100644 --- a/eslint/tests/lib/rules/no-implicit-globals.js +++ b/eslint/tests/lib/rules/no-implicit-globals.js @@ -412,7 +412,92 @@ ruleTester.run("no-implicit-globals", rule, { // This rule doesn't disallow assignments to properties of readonly globals "Array.from = 1;", "Object['assign'] = 1;", - "/*global foo:readonly*/ foo.bar = 1;" + "/*global foo:readonly*/ foo.bar = 1;", + + + //------------------------------------------------------------------------------ + // exported + //------------------------------------------------------------------------------ + + // `var` and functions + "/* exported foo */ var foo = 'foo';", + "/* exported foo */ function foo() {}", + { + code: "/* exported foo */ function *foo() {}", + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported foo */ async function foo() {}", + parserOptions: { ecmaVersion: 2017 } + }, + { + code: "/* exported foo */ async function *foo() {}", + parserOptions: { ecmaVersion: 2018 } + }, + "/* exported foo */ var foo = function() {};", + "/* exported foo */ var foo = function foo() {};", + { + code: "/* exported foo */ var foo = function*() {};", + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported foo */ var foo = function *foo() {};", + parserOptions: { ecmaVersion: 2015 } + }, + "/* exported foo, bar */ var foo = 1, bar = 2;", + + + // `const`, `let` and `class` + { + code: "/* exported a */ const a = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a */ let a;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a */ let a = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported A */ class A {}", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b */ const a = 1; const b = 2;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b */ const a = 1, b = 2;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b */ let a, b = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b, C */ const a = 1; let b; class C {}", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b, c */ const [a, b, ...c] = [];", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b, c */ let { a, foo: b, bar: { c } } = {};", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + } ], invalid: [ @@ -1241,6 +1326,298 @@ ruleTester.run("no-implicit-globals", rule, { type: "VariableDeclarator" } ] + }, + + //------------------------------------------------------------------------------ + // exported + //------------------------------------------------------------------------------ + + // `var` and `function` + { + code: "/* exported bar */ var foo = 'text';", + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ function foo() {}", + errors: [ + { + message: functionMessage, + type: "FunctionDeclaration" + } + ] + }, + { + code: "/* exported bar */ function *foo() {}", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: functionMessage, + type: "FunctionDeclaration" + } + ] + }, + { + code: "/* exported bar */ async function foo() {}", + parserOptions: { ecmaVersion: 2017 }, + errors: [ + { + message: functionMessage, + type: "FunctionDeclaration" + } + ] + }, + { + code: "/* exported bar */ async function *foo() {}", + parserOptions: { ecmaVersion: 2018 }, + errors: [ + { + message: functionMessage, + type: "FunctionDeclaration" + } + ] + }, + { + code: "/* exported bar */ var foo = function() {};", + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ var foo = function foo() {};", + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ var foo = function*() {};", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ var foo = function *foo() {};", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ var foo = 1, bar = 2;", + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + + // `let`, `const` and `class` + { + code: "/* exported b */ const a = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: constMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported b */ let a;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported b */ let a = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported B */ class A {}", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: classMessage, + type: "ClassDeclaration" + } + ] + }, + { + code: "/* exported a */ const a = 1; const b = 2;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: constMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported a */ const a = 1, b = 2;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: constMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported a */ let a, b = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported a */ const a = 1; let b; class C {}", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + }, + { + message: classMessage, + type: "ClassDeclaration" + } + ] + }, + { + code: "/* exported a */ const [a, b, ...c] = [];", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: constMessage, + type: "VariableDeclarator" + }, + { + message: constMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported a */ let { a, foo: b, bar: { c } } = {};", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + }, + { + message: letMessage, + type: "VariableDeclarator" + } + ] + }, + + // Global variable leaks + { + code: "/* exported foo */ foo = 1", + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ foo = function() {};", + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ foo = function*() {};", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ window.foo = function() { bar = 1; }", + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ (function() {}(foo = 1));", + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ for (foo in {});", + errors: [ + { + message: leakMessage, + type: "ForInStatement" + } + ] + }, + { + code: "/* exported foo */ for (foo of []);", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: leakMessage, + type: "ForOfStatement" + } + ] } ] }); diff --git a/eslint/tests/lib/rules/no-invalid-regexp.js b/eslint/tests/lib/rules/no-invalid-regexp.js index a34752d..ceaa8f1 100644 --- a/eslint/tests/lib/rules/no-invalid-regexp.js +++ b/eslint/tests/lib/rules/no-invalid-regexp.js @@ -57,6 +57,12 @@ ruleTester.run("no-invalid-regexp", rule, { options: [{ allowConstructorFlags: ["a"] }] }, + // unknown pattern + "new RegExp(pattern, 'g')", + "new RegExp('.' + '', 'g')", + "new RegExp(pattern, '')", + "new RegExp(pattern)", + // ES2020 "new RegExp('(?<\\\\ud835\\\\udc9c>.)', 'g')", "new RegExp('(?<\\\\u{1d49c}>.)', 'g')", @@ -65,6 +71,15 @@ ruleTester.run("no-invalid-regexp", rule, { // ES2022 "new RegExp('a+(?z)?', 'd')", + "new RegExp('\\\\p{Script=Cpmn}', 'u')", + "new RegExp('\\\\p{Script=Cypro_Minoan}', 'u')", + "new RegExp('\\\\p{Script=Old_Uyghur}', 'u')", + "new RegExp('\\\\p{Script=Ougr}', 'u')", + "new RegExp('\\\\p{Script=Tangsa}', 'u')", + "new RegExp('\\\\p{Script=Tnsa}', 'u')", + "new RegExp('\\\\p{Script=Toto}', 'u')", + "new RegExp('\\\\p{Script=Vith}', 'u')", + "new RegExp('\\\\p{Script=Vithkuqi}', 'u')", // allowConstructorFlags { @@ -87,6 +102,14 @@ ruleTester.run("no-invalid-regexp", rule, { code: "new RegExp('.', 'ga')", options: [{ allowConstructorFlags: ["a"] }] }, + { + code: "new RegExp(pattern, 'ga')", + options: [{ allowConstructorFlags: ["a"] }] + }, + { + code: "new RegExp('.' + '', 'ga')", + options: [{ allowConstructorFlags: ["a"] }] + }, { code: "new RegExp('.', 'a')", options: [{ allowConstructorFlags: ["a", "z"] }] @@ -237,6 +260,34 @@ ruleTester.run("no-invalid-regexp", rule, { data: { message: "Invalid regular expression: /\\/: \\ at end of pattern" }, type: "NewExpression" }] + }, + + // https://github.com/eslint/eslint/issues/16573 + { + code: "RegExp(')' + '', 'a');", + errors: [{ + messageId: "regexMessage", + data: { message: "Invalid flags supplied to RegExp constructor 'a'" }, + type: "CallExpression" + }] + }, + { + code: "new RegExp('.' + '', 'az');", + options: [{ allowConstructorFlags: ["z"] }], + errors: [{ + messageId: "regexMessage", + data: { message: "Invalid flags supplied to RegExp constructor 'a'" }, + type: "NewExpression" + }] + }, + { + code: "new RegExp(pattern, 'az');", + options: [{ allowConstructorFlags: ["a"] }], + errors: [{ + messageId: "regexMessage", + data: { message: "Invalid flags supplied to RegExp constructor 'z'" }, + type: "NewExpression" + }] } ] }); diff --git a/eslint/tests/lib/rules/no-magic-numbers.js b/eslint/tests/lib/rules/no-magic-numbers.js index 6afc328..e168249 100644 --- a/eslint/tests/lib/rules/no-magic-numbers.js +++ b/eslint/tests/lib/rules/no-magic-numbers.js @@ -257,6 +257,33 @@ ruleTester.run("no-magic-numbers", rule, { code: "foo?.[777]", options: [{ ignoreArrayIndexes: true }], parserOptions: { ecmaVersion: 2020 } + }, + + // ignoreClassFieldInitialValues + { + code: "class C { foo = 2; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = -2; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static foo = 2; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #foo = 2; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static #foo = 2; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -812,6 +839,88 @@ ruleTester.run("no-magic-numbers", rule, { { messageId: "noMagic", data: { raw: "1" }, line: 1 }, { messageId: "noMagic", data: { raw: "2" }, line: 1 } ] + }, + + // ignoreClassFieldInitialValues + { + code: "class C { foo = 2; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 17 } + ] + }, + { + code: "class C { foo = 2; }", + options: [{}], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 17 } + ] + }, + { + code: "class C { foo = 2; }", + options: [{ ignoreClassFieldInitialValues: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 17 } + ] + }, + { + code: "class C { foo = -2; }", + options: [{ ignoreClassFieldInitialValues: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "-2" }, line: 1, column: 17 } + ] + }, + { + code: "class C { static foo = 2; }", + options: [{ ignoreClassFieldInitialValues: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 24 } + ] + }, + { + code: "class C { #foo = 2; }", + options: [{ ignoreClassFieldInitialValues: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 18 } + ] + }, + { + code: "class C { static #foo = 2; }", + options: [{ ignoreClassFieldInitialValues: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 25 } + ] + }, + { + code: "class C { foo = 2 + 3; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 17 }, + { messageId: "noMagic", data: { raw: "3" }, line: 1, column: 21 } + ] + }, + { + code: "class C { 2; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 11 } + ] + }, + { + code: "class C { [2]; }", + options: [{ ignoreClassFieldInitialValues: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "noMagic", data: { raw: "2" }, line: 1, column: 12 } + ] } ] }); diff --git a/eslint/tests/lib/rules/no-misleading-character-class.js b/eslint/tests/lib/rules/no-misleading-character-class.js index 0aaf34e..5cc5cba 100644 --- a/eslint/tests/lib/rules/no-misleading-character-class.js +++ b/eslint/tests/lib/rules/no-misleading-character-class.js @@ -9,7 +9,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/no-misleading-character-class"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + FlatRuleTester = require("../../../lib/rule-tester/flat-rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -622,3 +623,33 @@ ruleTester.run("no-misleading-character-class", rule, { } ] }); + +const flatRuleTester = new FlatRuleTester(); + +flatRuleTester.run("no-misleading-character-class", rule, { + valid: [], + + invalid: [ + { + code: "var r = /[👍]/", + languageOptions: { + ecmaVersion: 5, + sourceType: "script" + }, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: null // ecmaVersion doesn't support the 'u' flag + }] + }, + { + code: "var r = /[👍]/", + languageOptions: { + ecmaVersion: 2015 + }, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = /[👍]/u" }] + }] + } + ] +}); diff --git a/eslint/tests/lib/rules/no-new-native-nonconstructor.js b/eslint/tests/lib/rules/no-new-native-nonconstructor.js new file mode 100644 index 0000000..9637ad7 --- /dev/null +++ b/eslint/tests/lib/rules/no-new-native-nonconstructor.js @@ -0,0 +1,68 @@ +/** + * @fileoverview Tests for the no-new-native-nonconstructor rule + * @author Sosuke Suzuki + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/no-new-native-nonconstructor"), + { RuleTester } = require("../../../lib/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ env: { es2022: true } }); + +ruleTester.run("no-new-native-nonconstructor", rule, { + valid: [ + + // Symbol + "var foo = Symbol('foo');", + "function bar(Symbol) { var baz = new Symbol('baz');}", + "function Symbol() {} new Symbol();", + "new foo(Symbol);", + "new foo(bar, Symbol);", + + // BigInt + "var foo = BigInt(9007199254740991);", + "function bar(BigInt) { var baz = new BigInt(9007199254740991);}", + "function BigInt() {} new BigInt();", + "new foo(BigInt);", + "new foo(bar, BigInt);" + ], + invalid: [ + + // Symbol + { + code: "var foo = new Symbol('foo');", + errors: [{ + message: "`Symbol` cannot be called as a constructor." + }] + }, + { + code: "function bar() { return function Symbol() {}; } var baz = new Symbol('baz');", + errors: [{ + message: "`Symbol` cannot be called as a constructor." + }] + }, + + // BigInt + { + code: "var foo = new BigInt(9007199254740991);", + errors: [{ + message: "`BigInt` cannot be called as a constructor." + }] + }, + { + code: "function bar() { return function BigInt() {}; } var baz = new BigInt(9007199254740991);", + errors: [{ + message: "`BigInt` cannot be called as a constructor." + }] + } + ] +}); diff --git a/eslint/tests/lib/rules/no-obj-calls.js b/eslint/tests/lib/rules/no-obj-calls.js index 6270e7a..febb13f 100644 --- a/eslint/tests/lib/rules/no-obj-calls.js +++ b/eslint/tests/lib/rules/no-obj-calls.js @@ -45,6 +45,14 @@ ruleTester.run("no-obj-calls", rule, { code: "new Atomics.foo()", env: { es2017: true } }, + { + code: "new Intl.Segmenter()", + env: { browser: true } + }, + { + code: "Intl.foo()", + env: { browser: true } + }, { code: "globalThis.Math();", env: { es6: true } }, { code: "var x = globalThis.Math();", env: { es6: true } }, @@ -58,6 +66,8 @@ ruleTester.run("no-obj-calls", rule, { { code: "/*globals Reflect: true*/ globalThis.Reflect();", env: { es2017: true } }, { code: "var x = globalThis.Atomics();", env: { es2017: true } }, { code: "var x = globalThis.Atomics();", globals: { Atomics: false }, env: { es2017: true } }, + { code: "var x = globalThis.Intl();", env: { browser: true } }, + { code: "var x = globalThis.Intl();", globals: { Intl: false }, env: { browser: true } }, // non-existing variables "/*globals Math: off*/ Math();", @@ -78,6 +88,8 @@ ruleTester.run("no-obj-calls", rule, { code: "Atomics();", env: { es6: true } }, + "Intl()", + "new Intl()", // shadowed variables "var Math; Math();", @@ -119,6 +131,20 @@ ruleTester.run("no-obj-calls", rule, { { code: "var construct = typeof Reflect !== \"undefined\" ? Reflect.construct : undefined; construct();", globals: { Reflect: false } + }, + { + code: "function foo(Intl) { Intl(); }", + env: { browser: true } + }, + { + code: "if (foo) { const Intl = 1; Intl(); }", + parserOptions: { ecmaVersion: 2015 }, + env: { browser: true } + }, + { + code: "if (foo) { const Intl = 1; new Intl(); }", + parserOptions: { ecmaVersion: 2015 }, + env: { browser: true } } ], invalid: [ @@ -225,6 +251,24 @@ ruleTester.run("no-obj-calls", rule, { globals: { Atomics: "writable" }, errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "NewExpression" }] }, + { + code: "var x = Intl();", + env: { browser: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "CallExpression" }] + }, + { + code: "var x = new Intl();", + env: { browser: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "NewExpression" }] + }, + { + code: "/*globals Intl: true*/ Intl();", + errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "CallExpression" }] + }, + { + code: "/*globals Intl: true*/ new Intl();", + errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "NewExpression" }] + }, { code: "var x = globalThis.Math();", env: { es2020: true }, @@ -288,6 +332,21 @@ ruleTester.run("no-obj-calls", rule, { env: { es2020: true }, errors: [{ messageId: "unexpectedCall", data: { name: "Atomics" }, type: "CallExpression" }] }, + { + code: "var x = globalThis.Intl();", + env: { browser: true, es2020: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "CallExpression" }] + }, + { + code: "var x = new globalThis.Intl;", + env: { browser: true, es2020: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "NewExpression" }] + }, + { + code: "/*globals Intl: true*/ Intl();", + env: { browser: true, es2020: true }, + errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "CallExpression" }] + }, { code: "var foo = bar ? baz: JSON; foo();", errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "JSON" }, type: "CallExpression" }] @@ -316,6 +375,16 @@ ruleTester.run("no-obj-calls", rule, { env: { es2020: true, browser: true }, errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "Atomics" }, type: "NewExpression" }] }, + { + code: "var foo = window.Intl; foo();", + env: { es2020: true, browser: true }, + errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "Intl" }, type: "CallExpression" }] + }, + { + code: "var foo = window.Intl; new foo;", + env: { es2020: true, browser: true }, + errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "Intl" }, type: "NewExpression" }] + }, // Optional chaining { diff --git a/eslint/tests/lib/rules/no-restricted-exports.js b/eslint/tests/lib/rules/no-restricted-exports.js index 631fd6f..9505e8f 100644 --- a/eslint/tests/lib/rules/no-restricted-exports.js +++ b/eslint/tests/lib/rules/no-restricted-exports.js @@ -107,7 +107,31 @@ ruleTester.run("no-restricted-exports", rule, { { code: "export default 1;", options: [{ restrictedNamedExports: ["default"] }] }, // "default" does not disallow re-exporting a renamed default export from another module - { code: "export { default as a } from 'foo';", options: [{ restrictedNamedExports: ["default"] }] } + { code: "export { default as a } from 'foo';", options: [{ restrictedNamedExports: ["default"] }] }, + + // restrictDefaultExports.direct option + { code: "export default foo;", options: [{ restrictDefaultExports: { direct: false } }] }, + { code: "export default 42;", options: [{ restrictDefaultExports: { direct: false } }] }, + { code: "export default function foo() {}", options: [{ restrictDefaultExports: { direct: false } }] }, + + // restrictDefaultExports.named option + { code: "const foo = 123;\nexport { foo as default };", options: [{ restrictDefaultExports: { named: false } }] }, + + // restrictDefaultExports.defaultFrom option + { code: "export { default } from 'mod';", options: [{ restrictDefaultExports: { defaultFrom: false } }] }, + { code: "export { default as default } from 'mod';", options: [{ restrictDefaultExports: { defaultFrom: false } }] }, + { code: "export { foo as default } from 'mod';", options: [{ restrictDefaultExports: { defaultFrom: true } }] }, + { code: "export { default } from 'mod';", options: [{ restrictDefaultExports: { named: true, defaultFrom: false } }] }, + { code: "export { 'default' } from 'mod'; ", options: [{ restrictDefaultExports: { defaultFrom: false } }] }, + + // restrictDefaultExports.namedFrom option + { code: "export { foo as default } from 'mod';", options: [{ restrictDefaultExports: { namedFrom: false } }] }, + { code: "export { default as default } from 'mod';", options: [{ restrictDefaultExports: { namedFrom: true } }] }, + { code: "export { default as default } from 'mod';", options: [{ restrictDefaultExports: { namedFrom: false } }] }, + { code: "export { 'default' } from 'mod'; ", options: [{ restrictDefaultExports: { defaultFrom: false, namedFrom: true } }] }, + + // restrictDefaultExports.namespaceFrom option + { code: "export * as default from 'mod';", options: [{ restrictDefaultExports: { namespaceFrom: false } }] } ], invalid: [ @@ -519,6 +543,66 @@ ruleTester.run("no-restricted-exports", rule, { code: "export { default } from 'foo';", options: [{ restrictedNamedExports: ["default"] }], errors: [{ messageId: "restrictedNamed", data: { name: "default" }, type: "Identifier", column: 10 }] + }, + + // restrictDefaultExports.direct option + { + code: "export default foo;", + options: [{ restrictDefaultExports: { direct: true } }], + errors: [{ messageId: "restrictedDefault", type: "ExportDefaultDeclaration", column: 1 }] + }, + { + code: "export default 42;", + options: [{ restrictDefaultExports: { direct: true } }], + errors: [{ messageId: "restrictedDefault", type: "ExportDefaultDeclaration", column: 1 }] + }, + { + code: "export default function foo() {}", + options: [{ restrictDefaultExports: { direct: true } }], + errors: [{ messageId: "restrictedDefault", type: "ExportDefaultDeclaration", column: 1 }] + }, + { + code: "export default foo;", + options: [{ restrictedNamedExports: ["bar"], restrictDefaultExports: { direct: true } }], + errors: [{ messageId: "restrictedDefault", type: "ExportDefaultDeclaration", column: 1 }] + }, + + // restrictDefaultExports.named option + { + code: "const foo = 123;\nexport { foo as default };", + options: [{ restrictDefaultExports: { named: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 2, column: 17 }] + }, + + // restrictDefaultExports.defaultFrom option + { + code: "export { default } from 'mod';", + options: [{ restrictDefaultExports: { defaultFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 10 }] + }, + { + code: "export { default as default } from 'mod';", + options: [{ restrictDefaultExports: { defaultFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 21 }] + }, + { + code: "export { 'default' } from 'mod';", + options: [{ restrictDefaultExports: { defaultFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Literal", line: 1, column: 10 }] + }, + + // restrictDefaultExports.namedFrom option + { + code: "export { foo as default } from 'mod';", + options: [{ restrictDefaultExports: { namedFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 17 }] + }, + + // restrictDefaultExports.namespaceFrom option + { + code: "export * as default from 'mod';", + options: [{ restrictDefaultExports: { namespaceFrom: true } }], + errors: [{ messageId: "restrictedDefault", type: "Identifier", line: 1, column: 13 }] } ] }); diff --git a/eslint/tests/lib/rules/no-return-await.js b/eslint/tests/lib/rules/no-return-await.js index 508d7cb..b9184a0 100644 --- a/eslint/tests/lib/rules/no-return-await.js +++ b/eslint/tests/lib/rules/no-return-await.js @@ -16,8 +16,24 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -// pending https://github.com/eslint/espree/issues/304, the type should be "Keyword" -const errors = [{ messageId: "redundantUseOfAwait", type: "Identifier" }]; +/** + * Creates the list of errors that should be found by this rule + * @param {Object} options Options for creating errors + * @param {string} options.suggestionOutput The suggested output + * @returns {Array} the list of errors + */ +function createErrorList({ suggestionOutput: output } = {}) { + + // pending https://github.com/eslint/espree/issues/304, the type should be "Keyword" + return [{ + messageId: "redundantUseOfAwait", + type: "Identifier", + suggestions: output ? [{ + messageId: "removeAwait", output + }] : [] + }]; +} + const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2017 } }); @@ -138,99 +154,103 @@ ruleTester.run("no-return-await", rule, { invalid: [ { code: "\nasync function foo() {\n\treturn await bar();\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn bar();\n}\n" }) + }, + { + code: "\nasync function foo() {\n\treturn await(bar());\n}\n", + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (a, await bar());\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (a, bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (a, b, await bar());\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (a, b, bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (a && await bar());\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (a && bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (a && b && await bar());\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (a && b && bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (a || await bar());\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (a || bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (a, b, (c, d, await bar()));\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (a, b, (c, d, bar()));\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (a, b, (c && await bar()));\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (a, b, (c && bar()));\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (await baz(), b, await bar());\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (await baz(), b, bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (baz() ? await bar() : b);\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (baz() ? bar() : b);\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (baz() ? a : await bar());\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (baz() ? a : bar());\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (baz() ? (a, await bar()) : b);\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (baz() ? (a, bar()) : b);\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (baz() ? a : (b, await bar()));\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (baz() ? a : (b, bar()));\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (baz() ? (a && await bar()) : b);\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (baz() ? (a && bar()) : b);\n}\n" }) }, { code: "\nasync function foo() {\n\treturn (baz() ? a : (b && await bar()));\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\n\treturn (baz() ? a : (b && bar()));\n}\n" }) }, { code: "\nasync () => { return await bar(); }\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => { return bar(); }\n" }) }, { code: "\nasync () => await bar()\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => bar()\n" }) }, { code: "\nasync () => (a, b, await bar())\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => (a, b, bar())\n" }) }, { code: "\nasync () => (a && await bar())\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => (a && bar())\n" }) }, { code: "\nasync () => (baz() ? await bar() : b)\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => (baz() ? bar() : b)\n" }) }, { code: "\nasync () => (baz() ? a : (b, await bar()))\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => (baz() ? a : (b, bar()))\n" }) }, { code: "\nasync () => (baz() ? a : (b && await bar()))\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => (baz() ? a : (b && bar()))\n" }) }, { code: "\nasync function foo() {\nif (a) {\n\t\tif (b) {\n\t\t\treturn await bar();\n\t\t}\n\t}\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync function foo() {\nif (a) {\n\t\tif (b) {\n\t\t\treturn bar();\n\t\t}\n\t}\n}\n" }) }, { code: "\nasync () => {\nif (a) {\n\t\tif (b) {\n\t\t\treturn await bar();\n\t\t}\n\t}\n}\n", - errors + errors: createErrorList({ suggestionOutput: "\nasync () => {\nif (a) {\n\t\tif (b) {\n\t\t\treturn bar();\n\t\t}\n\t}\n}\n" }) }, { code: ` @@ -241,7 +261,16 @@ ruleTester.run("no-return-await", rule, { } } `, - errors + errors: createErrorList({ + suggestionOutput: ` + async function foo() { + try {} + finally { + return bar(); + } + } + ` + }) }, { code: ` @@ -252,7 +281,16 @@ ruleTester.run("no-return-await", rule, { } } `, - errors + errors: createErrorList({ + suggestionOutput: ` + async function foo() { + try {} + catch (e) { + return bar(); + } + } + ` + }) }, { code: ` @@ -262,7 +300,15 @@ ruleTester.run("no-return-await", rule, { } } catch (e) {} `, - errors + errors: createErrorList({ + suggestionOutput: ` + try { + async function foo() { + return bar(); + } + } catch (e) {} + ` + }) }, { code: ` @@ -270,7 +316,13 @@ ruleTester.run("no-return-await", rule, { async () => await bar(); } catch (e) {} `, - errors + errors: createErrorList({ + suggestionOutput: ` + try { + async () => bar(); + } catch (e) {} + ` + }) }, { code: ` @@ -284,7 +336,64 @@ ruleTester.run("no-return-await", rule, { } } `, - errors + errors: createErrorList({ + suggestionOutput: ` + async function foo() { + try {} + catch (e) { + try {} + catch (e) { + return bar(); + } + } + } + ` + }) + }, + { + code: ` + async function foo() { + return await new Promise(resolve => { + resolve(5); + }); + } + `, + errors: createErrorList({ + suggestionOutput: ` + async function foo() { + return new Promise(resolve => { + resolve(5); + }); + } + ` + }) + }, + { + code: ` + async () => { + return await ( + foo() + ) + }; + `, + errors: createErrorList({ + suggestionOutput: ` + async () => { + return ( + foo() + ) + }; + ` + }) + }, + { + code: ` + async function foo() { + return await // Test + 5; + } + `, + errors: createErrorList() } ] }); diff --git a/eslint/tests/lib/rules/no-underscore-dangle.js b/eslint/tests/lib/rules/no-underscore-dangle.js index f3e11cc..cec2c2a 100644 --- a/eslint/tests/lib/rules/no-underscore-dangle.js +++ b/eslint/tests/lib/rules/no-underscore-dangle.js @@ -70,6 +70,17 @@ ruleTester.run("no-underscore-dangle", rule, { { 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: "const [_foo] = arr", parserOptions: { ecmaVersion: 6 } }, + { code: "const [_foo] = arr", options: [{}], parserOptions: { ecmaVersion: 6 } }, + { code: "const [_foo] = arr", options: [{ allowInArrayDestructuring: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const [foo, ...rest] = [1, 2, 3]", options: [{ allowInArrayDestructuring: false }], parserOptions: { ecmaVersion: 2022 } }, + { code: "const [foo, _bar] = [1, 2, 3]", options: [{ allowInArrayDestructuring: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 2022 } }, + { code: "const { _foo } = obj", parserOptions: { ecmaVersion: 6 } }, + { code: "const { _foo } = obj", options: [{}], parserOptions: { ecmaVersion: 6 } }, + { code: "const { _foo } = obj", options: [{ allowInObjectDestructuring: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const { foo, bar: _bar } = { foo: 1, bar: 2 }", options: [{ allowInObjectDestructuring: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 2022 } }, + { code: "const { foo, _bar } = { foo: 1, _bar: 2 }", options: [{ allowInObjectDestructuring: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 2022 } }, + { code: "const { foo, _bar: bar } = { foo: 1, _bar: 2 }", options: [{ allowInObjectDestructuring: false }], parserOptions: { ecmaVersion: 2022 } }, { code: "class foo { _field; }", parserOptions: { ecmaVersion: 2022 } }, { code: "class foo { _field; }", options: [{ enforceInClassFields: false }], parserOptions: { ecmaVersion: 2022 } }, { code: "class foo { #_field; }", parserOptions: { ecmaVersion: 2022 } }, @@ -103,6 +114,77 @@ ruleTester.run("no-underscore-dangle", rule, { { 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] = [1, 2]", + options: [{ allowInArrayDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" } }] + }, { + code: "const [_foo = 1] = arr", + options: [{ allowInArrayDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }] + }, { + code: "const [foo, ..._rest] = [1, 2, 3]", + options: [{ allowInArrayDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_rest" } }] + }, { + code: "const [foo, [bar_, baz]] = [1, [2, 3]]", + options: [{ allowInArrayDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "bar_" } }] + }, { + code: "const { _foo, bar } = { _foo: 1, bar: 2 }", + options: [{ allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }] + }, { + code: "const { _foo = 1 } = obj", + options: [{ allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }] + }, { + code: "const { bar: _foo = 1 } = obj", + options: [{ allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }] + }, { + code: "const { foo: _foo, bar } = { foo: 1, bar: 2 }", + options: [{ allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" } }] + }, { + code: "const { foo, ..._rest} = { foo: 1, bar: 2, baz: 3 }", + options: [{ allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_rest" } }] + }, { + code: "const { foo: [_bar, { a: _a, b } ] } = { foo: [1, { a: 'a', b: 'b' }] }", + options: [{ allowInArrayDestructuring: false, allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "unexpectedUnderscore", data: { identifier: "_bar" } }, + { messageId: "unexpectedUnderscore", data: { identifier: "_a" } } + ] + }, { + code: "const { foo: [_bar, { a: _a, b } ] } = { foo: [1, { a: 'a', b: 'b' }] }", + options: [{ allowInArrayDestructuring: true, allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_a" } }] + }, { + code: "const [{ foo: [_bar, _, { bar: _baz }] }] = [{ foo: [1, 2, { bar: 'a' }] }]", + options: [{ allowInArrayDestructuring: false, allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "unexpectedUnderscore", data: { identifier: "_bar" } }, + { messageId: "unexpectedUnderscore", data: { identifier: "_baz" } } + ] + }, { + code: "const { foo, bar: { baz, _qux } } = { foo: 1, bar: { baz: 3, _qux: 4 } }", + options: [{ allowInObjectDestructuring: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_qux" } }] + }, { code: "class foo { #_bar() {} }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 2022 }, diff --git a/eslint/tests/lib/rules/no-unused-vars.js b/eslint/tests/lib/rules/no-unused-vars.js index b723a29..3c1997b 100644 --- a/eslint/tests/lib/rules/no-unused-vars.js +++ b/eslint/tests/lib/rules/no-unused-vars.js @@ -18,20 +18,25 @@ const rule = require("../../../lib/rules/no-unused-vars"), const ruleTester = new RuleTester(); -ruleTester.defineRule("use-every-a", context => { +ruleTester.defineRule("use-every-a", { + create(context) { - /** - * Mark a variable as used - * @returns {void} - * @private - */ - function useA() { - context.markVariableAsUsed("a"); + const sourceCode = context.sourceCode; + + /** + * Mark a variable as used + * @param {ASTNode} node The node representing the scope to search + * @returns {void} + * @private + */ + function useA(node) { + sourceCode.markVariableAsUsed("a", node); + } + return { + VariableDeclaration: useA, + ReturnStatement: useA + }; } - return { - VariableDeclaration: useA, - ReturnStatement: useA - }; }); /** diff --git a/eslint/tests/lib/rules/no-var.js b/eslint/tests/lib/rules/no-var.js index 84e14ae..26b0a84 100644 --- a/eslint/tests/lib/rules/no-var.js +++ b/eslint/tests/lib/rules/no-var.js @@ -319,6 +319,74 @@ ruleTester.run("no-var", rule, { code: "function foo() { var { let } = {}; }", output: null, errors: [{ messageId: "unexpectedVar" }] + }, + + // https://github.com/eslint/eslint/issues/16610 + { + code: "var fx = function (i = 0) { if (i < 5) { return fx(i + 1); } console.log(i); }; fx();", + output: "let fx = function (i = 0) { if (i < 5) { return fx(i + 1); } console.log(i); }; fx();", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var foo = function () { foo() };", + output: "let foo = function () { foo() };", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var foo = () => foo();", + output: "let foo = () => foo();", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var foo = (function () { foo(); })();", + output: null, + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var foo = bar(function () { foo(); });", + output: null, + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var bar = foo, foo = function () { foo(); };", + output: null, + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var bar = foo; var foo = function () { foo(); };", + output: "let bar = foo; var foo = function () { foo(); };", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { messageId: "unexpectedVar" }, + { messageId: "unexpectedVar" } + ] + }, + { + code: "var { foo = foo } = function () { foo(); };", + output: null, + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var { bar = foo, foo } = function () { foo(); };", + output: null, + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpectedVar" }] + }, + { + code: "var bar = function () { foo(); }; var foo = function() {};", + output: "let bar = function () { foo(); }; var foo = function() {};", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { messageId: "unexpectedVar" }, + { messageId: "unexpectedVar" } + ] } ] }); diff --git a/eslint/tests/lib/rules/prefer-arrow-callback.js b/eslint/tests/lib/rules/prefer-arrow-callback.js index cb46c4a..eba8c05 100644 --- a/eslint/tests/lib/rules/prefer-arrow-callback.js +++ b/eslint/tests/lib/rules/prefer-arrow-callback.js @@ -205,6 +205,46 @@ ruleTester.run("prefer-arrow-callback", rule, { code: "foo((function() { return this; }?.bind)(this));", output: null, errors + }, + + // https://github.com/eslint/eslint/issues/16718 + { + code: ` + test( + function () + { } + ); + `, + output: ` + test( + () => + { } + ); + `, + errors + }, + { + code: ` + test( + function ( + ...args + ) /* Lorem ipsum + dolor sit amet. */ { + return args; + } + ); + `, + output: ` + test( + ( + ...args + ) => /* Lorem ipsum + dolor sit amet. */ { + return args; + } + ); + `, + errors } ] }); diff --git a/eslint/tests/lib/rules/prefer-const.js b/eslint/tests/lib/rules/prefer-const.js index c65252c..bd00484 100644 --- a/eslint/tests/lib/rules/prefer-const.js +++ b/eslint/tests/lib/rules/prefer-const.js @@ -19,11 +19,17 @@ const rule = require("../../../lib/rules/prefer-const"), const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); -ruleTester.defineRule("use-x", context => ({ - VariableDeclaration() { - context.markVariableAsUsed("x"); +ruleTester.defineRule("use-x", { + create(context) { + const sourceCode = context.sourceCode; + + return { + VariableDeclaration(node) { + sourceCode.markVariableAsUsed("x", node); + } + }; } -})); +}); ruleTester.run("prefer-const", rule, { valid: [ diff --git a/eslint/tests/lib/rules/prefer-named-capture-group.js b/eslint/tests/lib/rules/prefer-named-capture-group.js index 0faf1d6..dad3d7c 100644 --- a/eslint/tests/lib/rules/prefer-named-capture-group.js +++ b/eslint/tests/lib/rules/prefer-named-capture-group.js @@ -82,7 +82,17 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "([0-9]{4})" }, line: 1, column: 1, - endColumn: 13 + endColumn: 13, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?[0-9]{4})/" + }, + { + messageId: "addNonCapture", + output: "/(?:[0-9]{4})/" + } + ] }] }, { @@ -93,7 +103,17 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "([0-9]{4})" }, line: 1, column: 1, - endColumn: 25 + endColumn: 25, + suggestions: [ + { + messageId: "addGroupName", + output: "new RegExp('(?[0-9]{4})')" + }, + { + messageId: "addNonCapture", + output: "new RegExp('(?:[0-9]{4})')" + } + ] }] }, { @@ -104,7 +124,17 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "([0-9]{4})" }, line: 1, column: 1, - endColumn: 21 + endColumn: 21, + suggestions: [ + { + messageId: "addGroupName", + output: "RegExp('(?[0-9]{4})')" + }, + { + messageId: "addNonCapture", + output: "RegExp('(?:[0-9]{4})')" + } + ] }] }, { @@ -112,7 +142,44 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "NewExpression", - data: { group: "(bc)" } + data: { group: "(bc)" }, + suggestions: [ + { + messageId: "addGroupName", + output: "new RegExp(`a(?bc)d`)" + }, + { + messageId: "addNonCapture", + output: "new RegExp(`a(?:bc)d`)" + } + ] + }] + }, + { + code: "new RegExp('\u1234\u5678(?:a)(b)');", + errors: [{ + messageId: "required", + type: "NewExpression", + data: { group: "(b)" }, + suggestions: [ + { + messageId: "addGroupName", + output: "new RegExp('\u1234\u5678(?:a)(?b)');" + }, + { + messageId: "addNonCapture", + output: "new RegExp('\u1234\u5678(?:a)(?:b)');" + } + ] + }] + }, + { + code: "new RegExp('\\u1234\\u5678(?:a)(b)');", + errors: [{ + messageId: "required", + type: "NewExpression", + data: { group: "(b)" }, + suggestions: null }] }, { @@ -124,7 +191,17 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "([0-9]{4})" }, line: 1, column: 1, - endColumn: 21 + endColumn: 21, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?[0-9]{4})-(\\w{5})/" + }, + { + messageId: "addNonCapture", + output: "/(?:[0-9]{4})-(\\w{5})/" + } + ] }, { messageId: "required", @@ -132,7 +209,173 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "(\\w{5})" }, line: 1, column: 1, - endColumn: 21 + endColumn: 21, + suggestions: [ + { + messageId: "addGroupName", + output: "/([0-9]{4})-(?\\w{5})/" + }, + { + messageId: "addNonCapture", + output: "/([0-9]{4})-(?:\\w{5})/" + } + ] + } + ] + }, + { + code: "/([0-9]{4})-(5)/", + errors: [ + { + messageId: "required", + type: "Literal", + data: { group: "([0-9]{4})" }, + line: 1, + column: 1, + endColumn: 17, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?[0-9]{4})-(5)/" + }, + { + messageId: "addNonCapture", + output: "/(?:[0-9]{4})-(5)/" + } + ] + }, + { + messageId: "required", + type: "Literal", + data: { group: "(5)" }, + line: 1, + column: 1, + endColumn: 17, + suggestions: [ + { + messageId: "addGroupName", + output: "/([0-9]{4})-(?5)/" + }, + { + messageId: "addNonCapture", + output: "/([0-9]{4})-(?:5)/" + } + ] + } + ] + }, + { + code: "/(?(a))/", + errors: [ + { + messageId: "required", + type: "Literal", + data: { group: "(a)" }, + line: 1, + column: 1, + endColumn: 16, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?(?a))/" + }, + { + messageId: "addNonCapture", + output: "/(?(?:a))/" + } + ] + } + ] + }, + { + code: "/(?(a)(?b))/", + errors: [ + { + messageId: "required", + type: "Literal", + data: { group: "(a)" }, + line: 1, + column: 1, + endColumn: 27, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?(?a)(?b))/" + }, + { + messageId: "addNonCapture", + output: "/(?(?:a)(?b))/" + } + ] + } + ] + }, + { + code: "/(?[0-9]{4})-(\\w{5})/", + errors: [ + { + messageId: "required", + type: "Literal", + data: { group: "(\\w{5})" }, + line: 1, + column: 1, + endColumn: 29, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?[0-9]{4})-(?\\w{5})/" + }, + { + messageId: "addNonCapture", + output: "/(?[0-9]{4})-(?:\\w{5})/" + } + ] + } + ] + }, + { + code: "/(?[0-9]{4})-(5)/", + errors: [ + { + messageId: "required", + type: "Literal", + data: { group: "(5)" }, + line: 1, + column: 1, + endColumn: 25, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?[0-9]{4})-(?5)/" + }, + { + messageId: "addNonCapture", + output: "/(?[0-9]{4})-(?:5)/" + } + ] + } + ] + }, + { + code: "/(?a)(?a)(a)(?a)/", + errors: [ + { + messageId: "required", + type: "Literal", + data: { group: "(a)" }, + line: 1, + column: 1, + endColumn: 39, + suggestions: [ + { + messageId: "addGroupName", + output: "/(?a)(?a)(?a)(?a)/" + }, + { + messageId: "addNonCapture", + output: "/(?a)(?a)(?:a)(?a)/" + } + ] } ] }, @@ -141,7 +384,8 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "NewExpression", - data: { group: "(a)" } + data: { group: "(a)" }, + suggestions: null }] }, { @@ -149,7 +393,34 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "NewExpression", - data: { group: "(bc)" } + data: { group: "(bc)" }, + suggestions: null + }] + }, + { + code: "new RegExp(\"foo\" + \"(a)\" + \"(b)\");", + errors: [ + { + messageId: "required", + type: "NewExpression", + data: { group: "(a)" }, + suggestions: null + }, + { + messageId: "required", + type: "NewExpression", + data: { group: "(b)" }, + suggestions: null + } + ] + }, + { + code: "new RegExp(\"foo\" + \"(?:a)\" + \"(b)\");", + errors: [{ + messageId: "required", + type: "NewExpression", + data: { group: "(b)" }, + suggestions: null }] }, { @@ -157,7 +428,8 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "CallExpression", - data: { group: "(a)" } + data: { group: "(a)" }, + suggestions: null }] }, { @@ -165,7 +437,8 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "CallExpression", - data: { group: "(ab)" } + data: { group: "(ab)" }, + suggestions: null }] }, { @@ -173,7 +446,8 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "NewExpression", - data: { group: "(ab)" } + data: { group: "(ab)" }, + suggestions: null }] }, { @@ -185,7 +459,17 @@ ruleTester.run("prefer-named-capture-group", rule, { line: 1, column: 1, endLine: 2, - endColumn: 3 + endColumn: 3, + suggestions: [ + { + messageId: "addGroupName", + output: "new RegExp(`(?a)\n`)" + }, + { + messageId: "addNonCapture", + output: "new RegExp(`(?:a)\n`)" + } + ] }] }, { @@ -193,7 +477,17 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "CallExpression", - data: { group: "(b\nc)" } + data: { group: "(b\nc)" }, + suggestions: [ + { + messageId: "addGroupName", + output: "RegExp(`a(?b\nc)d`)" + }, + { + messageId: "addNonCapture", + output: "RegExp(`a(?:b\nc)d`)" + } + ] }] }, { @@ -201,7 +495,8 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "NewExpression", - data: { group: "(b)" } + data: { group: "(b)" }, + suggestions: null }] }, { @@ -209,7 +504,8 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "CallExpression", - data: { group: "(a)" } + data: { group: "(a)" }, + suggestions: null }] }, { @@ -217,7 +513,8 @@ ruleTester.run("prefer-named-capture-group", rule, { errors: [{ messageId: "required", type: "CallExpression", - data: { group: "(b)" } + data: { group: "(b)" }, + suggestions: null }] }, { @@ -229,7 +526,17 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "([0-9]{4})" }, line: 1, column: 1, - endColumn: 36 + endColumn: 36, + suggestions: [ + { + messageId: "addGroupName", + output: "new globalThis.RegExp('(?[0-9]{4})')" + }, + { + messageId: "addNonCapture", + output: "new globalThis.RegExp('(?:[0-9]{4})')" + } + ] }] }, { @@ -241,7 +548,17 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "([0-9]{4})" }, line: 1, column: 1, - endColumn: 32 + endColumn: 32, + suggestions: [ + { + messageId: "addGroupName", + output: "globalThis.RegExp('(?[0-9]{4})')" + }, + { + messageId: "addNonCapture", + output: "globalThis.RegExp('(?:[0-9]{4})')" + } + ] }] }, { @@ -256,7 +573,23 @@ ruleTester.run("prefer-named-capture-group", rule, { data: { group: "([0-9]{4})" }, line: 3, column: 17, - endColumn: 52 + endColumn: 52, + suggestions: [ + { + messageId: "addGroupName", + output: ` + function foo() { var globalThis = bar; } + new globalThis.RegExp('(?[0-9]{4})'); + ` + }, + { + messageId: "addNonCapture", + output: ` + function foo() { var globalThis = bar; } + new globalThis.RegExp('(?:[0-9]{4})'); + ` + } + ] }] } ] diff --git a/eslint/tests/lib/rules/prefer-object-spread.js b/eslint/tests/lib/rules/prefer-object-spread.js index 92bbef4..8ecd377 100644 --- a/eslint/tests/lib/rules/prefer-object-spread.js +++ b/eslint/tests/lib/rules/prefer-object-spread.js @@ -1,7 +1,6 @@ /** * @fileoverview Prefers object spread property over Object.assign * @author Sharmila Jesupaul - * See LICENSE file in root directory for full license. */ "use strict"; diff --git a/eslint/tests/lib/rules/prefer-regex-literals.js b/eslint/tests/lib/rules/prefer-regex-literals.js index f2982cb..054d89b 100644 --- a/eslint/tests/lib/rules/prefer-regex-literals.js +++ b/eslint/tests/lib/rules/prefer-regex-literals.js @@ -10,7 +10,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/prefer-regex-literals"); -const { RuleTester } = require("../../../lib/rule-tester"); +const { RuleTester } = require("../../../lib/rule-tester"), + FlatRuleTester = require("../../../lib/rule-tester/flat-rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -575,7 +576,13 @@ ruleTester.run("prefer-regex-literals", rule, { messageId: "unexpectedRedundantRegExp", type: "NewExpression", line: 1, - column: 1 + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteral", + output: "/a/;" + } + ] } ] }, @@ -591,7 +598,187 @@ ruleTester.run("prefer-regex-literals", rule, { messageId: "unexpectedRedundantRegExpWithFlags", type: "NewExpression", line: 1, - column: 1 + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/u;", + data: { + flags: "u" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/g, '');", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/;", + data: { + flags: "" + } + }, + { + messageId: "replaceWithIntendedLiteralAndFlags", + output: "/a/g;", + data: { + flags: "g" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/g, 'g');", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/g;", + data: { + flags: "g" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/ig, 'g');", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/g;", + data: { + flags: "g" + } + }, + { + messageId: "replaceWithIntendedLiteralAndFlags", + output: "/a/ig;", + data: { + flags: "ig" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/g, 'ig');", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/ig;", + data: { + flags: "ig" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/i, 'g');", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/g;", + data: { + flags: "g" + } + }, + { + messageId: "replaceWithIntendedLiteralAndFlags", + output: "/a/ig;", + data: { + flags: "ig" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/i, 'i');", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/i;", + data: { + flags: "i" + } + } + ] } ] }, @@ -607,12 +794,21 @@ ruleTester.run("prefer-regex-literals", rule, { messageId: "unexpectedRedundantRegExpWithFlags", type: "NewExpression", line: 1, - column: 1 + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/u;", + data: { + flags: "u" + } + } + ] } ] }, { - code: "new RegExp(/a/, String.raw`u`);", + code: "new RegExp(/a/, `gi`);", options: [ { disallowRedundantWrapping: true @@ -623,7 +819,16 @@ ruleTester.run("prefer-regex-literals", rule, { messageId: "unexpectedRedundantRegExpWithFlags", type: "NewExpression", line: 1, - column: 1 + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/gi;", + data: { + flags: "gi" + } + } + ] } ] }, @@ -649,6 +854,138 @@ ruleTester.run("prefer-regex-literals", rule, { } ] }, + { + code: "new RegExp(/a/, String.raw`u`);", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/u;", + data: { + flags: "u" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/ /* comment */);", + options: [ + { + disallowRedundantWrapping: true + } + ], + errors: [ + { + messageId: "unexpectedRedundantRegExp", + type: "NewExpression", + line: 1, + column: 1, + suggestions: null + } + ] + }, + { + code: "new RegExp(/a/, 'd');", + options: [ + { + disallowRedundantWrapping: true + } + ], + parserOptions: { + ecmaVersion: 2021 + }, + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 1, + column: 1, + suggestions: null + } + ] + }, + { + code: "(a)\nnew RegExp(/b/);", + options: [{ + disallowRedundantWrapping: true + }], + errors: [ + { + messageId: "unexpectedRedundantRegExp", + type: "NewExpression", + line: 2, + column: 1, + suggestions: null + } + ] + }, + { + code: "(a)\nnew RegExp(/b/, 'g');", + options: [{ + disallowRedundantWrapping: true + }], + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + type: "NewExpression", + line: 2, + column: 1, + suggestions: null + } + ] + }, + { + code: "a/RegExp(/foo/);", + options: [{ + disallowRedundantWrapping: true + }], + errors: [ + { + messageId: "unexpectedRedundantRegExp", + type: "CallExpression", + line: 1, + column: 3, + suggestions: [ + { + messageId: "replaceWithLiteral", + output: "a/ /foo/;" + } + ] + } + ] + }, + { + code: "RegExp(/foo/)in a;", + options: [{ + disallowRedundantWrapping: true + }], + errors: [ + { + messageId: "unexpectedRedundantRegExp", + type: "CallExpression", + line: 1, + column: 1, + suggestions: [ + { + messageId: "replaceWithLiteral", + output: "/foo/ in a;" + } + ] + } + ] + }, { code: "new RegExp((String?.raw)`a`);", errors: [ @@ -2474,3 +2811,27 @@ ruleTester.run("prefer-regex-literals", rule, { } ] }); + +const flatRuleTester = new FlatRuleTester(); + +flatRuleTester.run("prefer-regex-literals", rule, { + valid: [], + + invalid: [ + { + code: "var regex = new RegExp('foo', 'u');", + languageOptions: { + ecmaVersion: 2015 + }, + errors: [{ + messageId: "unexpectedRegExp", + suggestions: [ + { + messageId: "replaceWithLiteral", + output: "var regex = /foo/u;" + } + ] + }] + } + ] +}); diff --git a/eslint/tests/lib/rules/require-unicode-regexp.js b/eslint/tests/lib/rules/require-unicode-regexp.js index 16b6be4..a75f686 100644 --- a/eslint/tests/lib/rules/require-unicode-regexp.js +++ b/eslint/tests/lib/rules/require-unicode-regexp.js @@ -25,8 +25,11 @@ ruleTester.run("require-unicode-regexp", rule, { "/foo/u", "/foo/gimuy", "RegExp('', 'u')", + "RegExp('', `u`)", "new RegExp('', 'u')", "RegExp('', 'gimuy')", + "RegExp('', `gimuy`)", + "RegExp(...patternAndFlags)", "new RegExp('', 'gimuy')", "const flags = 'u'; new RegExp('', flags)", "const flags = 'g'; new RegExp('', flags + 'u')", @@ -34,6 +37,7 @@ ruleTester.run("require-unicode-regexp", rule, { "new RegExp('', flags)", "function f(flags) { return new RegExp('', flags) }", "function f(RegExp) { return new RegExp('foo') }", + "function f(patternAndFlags) { return new RegExp(...patternAndFlags) }", { code: "new globalThis.RegExp('foo')", env: { es6: true } }, { code: "new globalThis.RegExp('foo')", env: { es2017: true } }, { code: "new globalThis.RegExp('foo', 'u')", env: { es2020: true } }, @@ -44,60 +48,246 @@ ruleTester.run("require-unicode-regexp", rule, { { code: "class C { #RegExp; foo() { new globalThis.#RegExp('foo') } }", parserOptions: { ecmaVersion: 2022 }, env: { es2020: true } } ], invalid: [ + { + code: "/\\a/", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, { code: "/foo/", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "/foo/u" + } + ] + }] }, { code: "/foo/gimy", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "/foo/gimyu" + } + ] + }] + }, + { + code: "RegExp()", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] }, { code: "RegExp('foo')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', \"u\")" + } + ] + }] + }, + { + code: "RegExp('\\\\a')", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] }, { code: "RegExp('foo', '')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', 'u')" + } + ] + }] }, { code: "RegExp('foo', 'gimy')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', 'gimyu')" + } + ] + }] + }, + { + code: "RegExp('foo', `gimy`)", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', `gimyu`)" + } + ] + }] }, { code: "new RegExp('foo')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp('foo', \"u\")" + } + ] + }] + }, + { + code: "new RegExp('foo', false)", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, + { + code: "new RegExp('foo', 1)", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] }, { code: "new RegExp('foo', '')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp('foo', 'u')" + } + ] + }] }, { code: "new RegExp('foo', 'gimy')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp('foo', 'gimyu')" + } + ] + }] + }, + { + code: "new RegExp(('foo'))", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp(('foo'), \"u\")" + } + ] + }] + }, + { + code: "new RegExp(('unrelated', 'foo'))", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp(('unrelated', 'foo'), \"u\")" + } + ] + }] }, { code: "const flags = 'gi'; new RegExp('foo', flags)", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, + { + code: "const flags = 'gi'; new RegExp('foo', ('unrelated', flags))", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, + { + code: "let flags; new RegExp('foo', flags = 'g')", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, + { + code: "const flags = `gi`; new RegExp(`foo`, (`unrelated`, flags))", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] }, { code: "const flags = 'gimu'; new RegExp('foo', flags[0])", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] }, { code: "new window.RegExp('foo')", env: { browser: true }, - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new window.RegExp('foo', \"u\")" + } + ] + }] }, { code: "new global.RegExp('foo')", env: { node: true }, - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new global.RegExp('foo', \"u\")" + } + ] + }] }, { code: "new globalThis.RegExp('foo')", env: { es2020: true }, - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new globalThis.RegExp('foo', \"u\")" + } + ] + }] } ] }); diff --git a/eslint/tests/lib/rules/semi.js b/eslint/tests/lib/rules/semi.js index d873821..1811e14 100644 --- a/eslint/tests/lib/rules/semi.js +++ b/eslint/tests/lib/rules/semi.js @@ -111,6 +111,81 @@ ruleTester.run("semi", rule, { { 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 } }, + // omitLastInOneLineClassBody: true + { + code: ` + export class SomeClass{ + logType(){ + console.log(this.type); + } + } + + export class Variant1 extends SomeClass{type=1} + export class Variant2 extends SomeClass{type=2} + export class Variant3 extends SomeClass{type=3} + export class Variant4 extends SomeClass{type=4} + export class Variant5 extends SomeClass{type=5} + `, + options: ["always", { omitLastInOneLineClassBody: true }], + parserOptions: { ecmaVersion: 2022, sourceType: "module" } + }, + { + code: ` + export class SomeClass{ + logType(){ + console.log(this.type); + console.log(this.anotherType); + } + } + + export class Variant1 extends SomeClass{type=1; anotherType=2} + `, + options: ["always", { omitLastInOneLineClassBody: true }], + parserOptions: { ecmaVersion: 2022, sourceType: "module" } + }, + { + code: ` + export class SomeClass{ + logType(){ + console.log(this.type); + } + } + + export class Variant1 extends SomeClass{type=1;} + export class Variant2 extends SomeClass{type=2;} + export class Variant3 extends SomeClass{type=3;} + export class Variant4 extends SomeClass{type=4;} + export class Variant5 extends SomeClass{type=5;} + `, + options: ["always", { omitLastInOneLineClassBody: false }], + parserOptions: { ecmaVersion: 2022, sourceType: "module" } + }, + { + code: "class C {\nfoo;}", + options: ["always", { omitLastInOneLineClassBody: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {foo;\n}", + options: ["always", { omitLastInOneLineClassBody: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {foo;\nbar;}", + options: ["always", { omitLastInOneLineClassBody: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "{ foo; }", + options: ["always", { omitLastInOneLineClassBody: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C\n{ foo }", + options: ["always", { omitLastInOneLineClassBody: true }], + parserOptions: { ecmaVersion: 2022 } + }, + // 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 } }, @@ -2309,6 +2384,134 @@ ruleTester.run("semi", rule, { endLine: 1, endColumn: 18 }] + }, + + // omitLastInOneLineClassBody + { + code: ` + export class SomeClass{ + logType(){ + console.log(this.type); + } + } + + export class Variant1 extends SomeClass{type=1} + `, + output: ` + export class SomeClass{ + logType(){ + console.log(this.type); + } + } + + export class Variant1 extends SomeClass{type=1;} + `, + options: ["always", { omitLastInOneLineClassBody: false }], + parserOptions: { ecmaVersion: 2022, sourceType: "module" }, + errors: [ + { + messageId: "missingSemi", + line: 8, + column: 63, + endLine: 8, + endColumn: 64 + } + ] + }, + { + code: ` + export class SomeClass{ + logType(){ + console.log(this.type); + } + } + + export class Variant1 extends SomeClass{type=1} + `, + output: ` + export class SomeClass{ + logType(){ + console.log(this.type); + } + } + + export class Variant1 extends SomeClass{type=1;} + `, + options: ["always", { omitLastInOneLineClassBody: false, omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 2022, sourceType: "module" }, + errors: [ + { + messageId: "missingSemi", + line: 8, + column: 63, + endLine: 8, + endColumn: 64 + } + ] + }, + { + code: ` + export class SomeClass{ + logType(){ + console.log(this.type); + } + } + + export class Variant1 extends SomeClass{type=1;} + `, + output: ` + export class SomeClass{ + logType(){ + console.log(this.type); + } + } + + export class Variant1 extends SomeClass{type=1} + `, + options: ["always", { omitLastInOneLineClassBody: true, omitLastInOneLineBlock: false }], + parserOptions: { ecmaVersion: 2022, sourceType: "module" }, + errors: [ + { + messageId: "extraSemi", + line: 8, + column: 63, + endLine: 8, + endColumn: 64 + } + ] + }, + { + code: ` + export class SomeClass{ + logType(){ + console.log(this.type); + console.log(this.anotherType); + } + } + + export class Variant1 extends SomeClass{type=1; anotherType=2} + `, + output: ` + export class SomeClass{ + logType(){ + console.log(this.type); + console.log(this.anotherType); + } + } + + export class Variant1 extends SomeClass{type=1; anotherType=2;} + `, + options: ["always", { omitLastInOneLineClassBody: false, omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 2022, sourceType: "module" }, + errors: [ + { + messageId: "missingSemi", + line: 9, + column: 78, + endLine: 9, + endColumn: 79 + } + ] } ] }); diff --git a/eslint/tests/lib/rules/strict.js b/eslint/tests/lib/rules/strict.js index e3ea705..2798bc8 100644 --- a/eslint/tests/lib/rules/strict.js +++ b/eslint/tests/lib/rules/strict.js @@ -10,7 +10,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/strict"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + FlatRuleTester = require("../../../lib/rule-tester/flat-rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -659,3 +660,46 @@ ruleTester.run("strict", rule, { } ] }); + +const flatRuleTester = new FlatRuleTester(); + +// TODO: merge these tests into `ruleTester.run` once we switch to FlatRuleTester (when FlatRuleTester becomes RuleTester). +flatRuleTester.run("strict", rule, { + valid: [ + { + code: "'use strict'; module.exports = function identity (value) { return value; }", + languageOptions: { + sourceType: "commonjs" + } + }, + { + code: "'use strict'; module.exports = function identity (value) { return value; }", + options: ["safe"], + languageOptions: { + sourceType: "commonjs" + } + } + ], + + invalid: [ + { + code: "module.exports = function identity (value) { return value; }", + options: ["safe"], + languageOptions: { + sourceType: "commonjs" + }, + errors: [ + { messageId: "global", line: 1 } + ] + }, + { + code: "module.exports = function identity (value) { return value; }", + languageOptions: { + sourceType: "commonjs" + }, + errors: [ + { messageId: "global", line: 1 } + ] + } + ] +}); diff --git a/eslint/tests/lib/rules/utils/ast-utils.js b/eslint/tests/lib/rules/utils/ast-utils.js index c36a9ac..c2a9201 100644 --- a/eslint/tests/lib/rules/utils/ast-utils.js +++ b/eslint/tests/lib/rules/utils/ast-utils.js @@ -61,22 +61,26 @@ describe("ast-utils", () => { describe("isTokenOnSameLine", () => { it("should return false if the tokens are not on the same line", () => { - linter.defineRule("checker", mustCall(context => ({ - BlockStatement: mustCall(node => { - assert.isFalse(astUtils.isTokenOnSameLine(context.getTokenBefore(node), node)); - }) - }))); + linter.defineRule("checker", { + create: mustCall(context => ({ + BlockStatement: mustCall(node => { + assert.isFalse(astUtils.isTokenOnSameLine(context.sourceCode.getTokenBefore(node), node)); + }) + })) + }); linter.verify("if(a)\n{}", { rules: { checker: "error" } }); }); it("should return true if the tokens are on the same line", () => { - linter.defineRule("checker", mustCall(context => ({ - BlockStatement: mustCall(node => { - assert.isTrue(astUtils.isTokenOnSameLine(context.getTokenBefore(node), node)); - }) - }))); + linter.defineRule("checker", { + create: mustCall(context => ({ + BlockStatement: mustCall(node => { + assert.isTrue(astUtils.isTokenOnSameLine(context.sourceCode.getTokenBefore(node), node)); + }) + })) + }); linter.verify("if(a){}", { rules: { checker: "error" } }); }); @@ -116,64 +120,74 @@ describe("ast-utils", () => { // catch it("should return true if reference is assigned for catch", () => { - linter.defineRule("checker", mustCall(context => ({ - CatchClause: mustCall(node => { - const variables = context.getDeclaredVariables(node); + linter.defineRule("checker", { + create: mustCall(context => ({ + CatchClause: mustCall(node => { + const variables = context.sourceCode.getDeclaredVariables(node); - assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1); - }) - }))); + assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1); + }) + })) + }); linter.verify("try { } catch (e) { e = 10; }", { rules: { checker: "error" } }); }); // const it("should return true if reference is assigned for const", () => { - linter.defineRule("checker", mustCall(context => ({ - VariableDeclaration: mustCall(node => { - const variables = context.getDeclaredVariables(node); + linter.defineRule("checker", { + create: mustCall(context => ({ + VariableDeclaration: mustCall(node => { + const variables = context.sourceCode.getDeclaredVariables(node); - assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1); - }) - }))); + assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1); + }) + })) + }); linter.verify("const a = 1; a = 2;", { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); }); it("should return false if reference is not assigned for const", () => { - linter.defineRule("checker", mustCall(context => ({ - VariableDeclaration: mustCall(node => { - const variables = context.getDeclaredVariables(node); + linter.defineRule("checker", { + create: mustCall(context => ({ + VariableDeclaration: mustCall(node => { + const variables = context.sourceCode.getDeclaredVariables(node); - assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 0); - }) - }))); + assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 0); + }) + })) + }); linter.verify("const a = 1; c = 2;", { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); }); // class it("should return true if reference is assigned for class", () => { - linter.defineRule("checker", mustCall(context => ({ - ClassDeclaration: mustCall(node => { - const variables = context.getDeclaredVariables(node); + linter.defineRule("checker", { + create: mustCall(context => ({ + ClassDeclaration: mustCall(node => { + const variables = context.sourceCode.getDeclaredVariables(node); - assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1); - assert.lengthOf(astUtils.getModifyingReferences(variables[1].references), 0); - }) - }))); + assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 1); + assert.lengthOf(astUtils.getModifyingReferences(variables[1].references), 0); + }) + })) + }); linter.verify("class A { }\n A = 1;", { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); }); it("should return false if reference is not assigned for class", () => { - linter.defineRule("checker", mustCall(context => ({ - ClassDeclaration: mustCall(node => { - const variables = context.getDeclaredVariables(node); + linter.defineRule("checker", { + create: mustCall(context => ({ + ClassDeclaration: mustCall(node => { + const variables = context.sourceCode.getDeclaredVariables(node); - assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 0); - }) - }))); + assert.lengthOf(astUtils.getModifyingReferences(variables[0].references), 0); + }) + })) + }); linter.verify("class A { } foo(A);", { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); }); @@ -372,11 +386,13 @@ describe("ast-utils", () => { function assertNodeTypeInLoop(code, nodeType, expectedInLoop) { const results = []; - linter.defineRule("checker", mustCall(() => ({ - [nodeType]: mustCall(node => { - results.push(astUtils.isInLoop(node)); - }) - }))); + linter.defineRule("checker", { + create: mustCall(() => ({ + [nodeType]: mustCall(node => { + results.push(astUtils.isInLoop(node)); + }) + })) + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); assert.lengthOf(results, 1); @@ -897,14 +913,16 @@ describe("ast-utils", () => { Object.keys(expectedResults).forEach(key => { it(`should return "${expectedResults[key]}" for "${key}".`, () => { - linter.defineRule("checker", mustCall(() => ({ - ":function": mustCall(node => { - assert.strictEqual( - astUtils.getFunctionNameWithKind(node), - expectedResults[key] - ); - }) - }))); + linter.defineRule("checker", { + create: mustCall(() => ({ + ":function": mustCall(node => { + assert.strictEqual( + astUtils.getFunctionNameWithKind(node), + expectedResults[key] + ); + }) + })) + }); linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 13 } }); }); @@ -975,14 +993,16 @@ describe("ast-utils", () => { }; it(`should return "${JSON.stringify(expectedLoc)}" for "${key}".`, () => { - linter.defineRule("checker", mustCall(() => ({ - ":function": mustCall(node => { - assert.deepStrictEqual( - astUtils.getFunctionHeadLoc(node, linter.getSourceCode()), - expectedLoc - ); - }) - }))); + linter.defineRule("checker", { + create: mustCall(() => ({ + ":function": mustCall(node => { + assert.deepStrictEqual( + astUtils.getFunctionHeadLoc(node, linter.getSourceCode()), + expectedLoc + ); + }) + })) + }); linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 13 } }, "test.js", true); }); diff --git a/eslint/tests/lib/rules/valid-jsdoc.js b/eslint/tests/lib/rules/valid-jsdoc.js index 3b9d9d0..325064f 100644 --- a/eslint/tests/lib/rules/valid-jsdoc.js +++ b/eslint/tests/lib/rules/valid-jsdoc.js @@ -541,7 +541,7 @@ ruleTester.run("valid-jsdoc", rule, { options: [{ requireReturn: false }] }, - // https://github.com/eslint/eslint/issues/9412 - different orders for jsodc tags + // https://github.com/eslint/eslint/issues/9412 - different orders for jsdoc tags { code: "/**\n" + diff --git a/eslint/tests/lib/shared/config-validator.js b/eslint/tests/lib/shared/config-validator.js index 61d616b..d54d26e 100644 --- a/eslint/tests/lib/shared/config-validator.js +++ b/eslint/tests/lib/shared/config-validator.js @@ -32,59 +32,48 @@ const assert = require("chai").assert, const linter = new Linter(); -/** - * Fake a rule object - * @param {Object} context context passed to the rules by eslint - * @returns {Object} mocked rule listeners - * @private - */ -function mockRule(context) { - return { - Program(node) { - context.report(node, "Expected a validation error."); - } - }; -} - -mockRule.schema = [ - { - enum: ["first", "second"] +const mockRule = { + meta: { + schema: [{ + enum: ["first", "second"] + }] + }, + create(context) { + return { + Program(node) { + context.report(node, "Expected a validation error."); + } + }; } -]; - -/** - * Fake a rule object - * @param {Object} context context passed to the rules by eslint - * @returns {Object} mocked rule listeners - * @private - */ -function mockObjectRule(context) { - return { - Program(node) { - context.report(node, "Expected a validation error."); - } - }; -} - -mockObjectRule.schema = { - enum: ["first", "second"] }; -/** - * Fake a rule with no options - * @param {Object} context context passed to the rules by eslint - * @returns {Object} mocked rule listeners - * @private - */ -function mockNoOptionsRule(context) { - return { - Program(node) { - context.report(node, "Expected a validation error."); +const mockObjectRule = { + meta: { + schema: { + enum: ["first", "second"] } - }; -} + }, + create(context) { + return { + Program(node) { + context.report(node, "Expected a validation error."); + } + }; + } +}; -mockNoOptionsRule.schema = []; +const mockNoOptionsRule = { + meta: { + schema: [] + }, + create(context) { + return { + Program(node) { + context.report(node, "Expected a validation error."); + } + }; + } +}; const mockRequiredOptionsRule = { meta: { diff --git a/eslint/tests/lib/shared/runtime-info.js b/eslint/tests/lib/shared/runtime-info.js index efe57bb..ac549c0 100644 --- a/eslint/tests/lib/shared/runtime-info.js +++ b/eslint/tests/lib/shared/runtime-info.js @@ -206,7 +206,7 @@ describe("RuntimeInfo", () => { spawnSyncStubArgs[2] = "This is not JSON"; setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); - assert.throws(RuntimeInfo.environment, "Unexpected token T in JSON at position 0"); + assert.throws(RuntimeInfo.environment, /^Unexpected token .*T.* JSON/u); assert.strictEqual(logErrorStub.args[0][0], "Error finding eslint version running the command `npm ls --depth=0 --json eslint`"); }); @@ -214,7 +214,7 @@ describe("RuntimeInfo", () => { spawnSyncStubArgs[4] = "This is not JSON"; setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); - assert.throws(RuntimeInfo.environment, "Unexpected token T in JSON at position 0"); + assert.throws(RuntimeInfo.environment, /^Unexpected token .*T.* JSON/u); assert.strictEqual(logErrorStub.args[0][0], "Error finding eslint version running the command `npm ls --depth=0 --json eslint -g`"); }); }); diff --git a/eslint/tests/lib/shared/string-utils.js b/eslint/tests/lib/shared/string-utils.js index bc48afa..6682f14 100644 --- a/eslint/tests/lib/shared/string-utils.js +++ b/eslint/tests/lib/shared/string-utils.js @@ -11,7 +11,23 @@ const assert = require("chai").assert; -const { upperCaseFirst } = require("../../../lib/shared/string-utils"); +const { upperCaseFirst, getGraphemeCount } = require("../../../lib/shared/string-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Replaces raw control characters with the `\xXX` form. + * @param {string} text The text to process. + * @returns {string} `text` with escaped control characters. + */ +function escapeControlCharacters(text) { + return text.replace( + /[\u0000-\u001F\u007F-\u009F]/gu, // eslint-disable-line no-control-regex -- intentionally including control characters + c => `\\x${c.codePointAt(0).toString(16).padStart(2, "0")}` + ); +} //------------------------------------------------------------------------------ // Tests @@ -39,3 +55,35 @@ describe("upperCaseFirst", () => { assert(upperCaseFirst("") === ""); }); }); + +describe("getGraphemeCount", () => { + /* eslint-disable quote-props -- Make consistent here for readability */ + const expectedResults = { + "": 0, + "a": 1, + "ab": 2, + "aa": 2, + "123": 3, + "cccc": 4, + [Array.from({ length: 128 }, (_, i) => String.fromCharCode(i)).join("")]: 128, // all ASCII characters + "👍": 1, // 1 grapheme, 1 code point, 2 code units + "👍👍": 2, + "👍9👍": 3, + "a👍b": 3, + "👶🏽": 1, // 1 grapheme, 2 code points, 4 code units + "👨‍👩‍👦": 1, // 1 grapheme, 5 code points, 8 code units + "👨‍👩‍👦👨‍👩‍👦": 2, + "👨‍👩‍👦a👨‍👩‍👦": 3, + "a👨‍👩‍👦b👨‍👩‍👦c": 5, + "👨‍👩‍👦👍": 2, + "👶🏽👨‍👩‍👦": 2, + "👩‍🦰👩‍👩‍👦‍👦🏳️‍🌈": 3 // 3 grapheme, 14 code points, 22 code units + }; + /* eslint-enable quote-props -- Make consistent here for readability */ + + Object.entries(expectedResults).forEach(([key, value]) => { + it(`should return ${value} for ${escapeControlCharacters(key)}`, () => { + assert.strictEqual(getGraphemeCount(key), value); + }); + }); +}); diff --git a/eslint/tests/lib/source-code/source-code.js b/eslint/tests/lib/source-code/source-code.js index c44d4a2..dae9bcf 100644 --- a/eslint/tests/lib/source-code/source-code.js +++ b/eslint/tests/lib/source-code/source-code.js @@ -29,9 +29,23 @@ const DEFAULT_CONFIG = { loc: true }; const linter = new Linter(); +const flatLinter = new Linter({ configType: "flat" }); const AST = espree.parse("let foo = bar;", DEFAULT_CONFIG), TEST_CODE = "var answer = 6 * 7;", SHEBANG_TEST_CODE = `#!/usr/bin/env node\n${TEST_CODE}`; +const filename = "foo.js"; + +/** + * Get variables in the current scope + * @param {Object} scope current scope + * @param {string} name name of the variable to look for + * @returns {ASTNode|null} The variable object + * @private + */ +function getVariable(scope, name) { + return scope.variables.find(v => v.name === name) || null; +} + //------------------------------------------------------------------------------ // Tests @@ -253,7 +267,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); @@ -281,7 +301,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); @@ -311,7 +337,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledTwice, "Event handler should be called twice."); @@ -343,7 +375,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -371,7 +409,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); @@ -400,7 +444,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); @@ -431,7 +481,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); @@ -462,7 +518,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); @@ -492,7 +554,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); @@ -524,7 +592,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -555,7 +629,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -586,7 +666,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ ArrowFunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + ArrowFunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -615,7 +701,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -648,7 +740,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledTwice, "Event handler should be called."); }); @@ -680,7 +778,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledTwice, "Event handler should be called."); }); @@ -710,7 +814,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -748,7 +858,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" } }); assert.isTrue(spy.calledTwice, "Event handler should be called."); }); @@ -777,7 +893,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ ClassExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + ClassExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -806,7 +928,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ ClassDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + ClassDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -835,7 +963,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -868,7 +1002,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionExpression: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionExpression: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -899,7 +1039,13 @@ describe("SourceCode", () => { const spy = sinon.spy(assertJSDoc); - linter.defineRule("checker", () => ({ FunctionDeclaration: spy })); + linter.defineRule("checker", { + create() { + return ({ + FunctionDeclaration: spy + }); + } + }); linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }); assert.isTrue(spy.calledOnce, "Event handler should be called."); }); @@ -956,13 +1102,17 @@ describe("SourceCode", () => { "/* Trailing comment for VariableDeclaration */" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - VariableDeclaration: assertCommentCount(1, 1), - VariableDeclarator: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - Literal: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + VariableDeclaration: assertCommentCount(1, 1), + VariableDeclarator: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + Literal: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -975,13 +1125,17 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - ExpressionStatement: assertCommentCount(0, 1), - CallExpression: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + ExpressionStatement: assertCommentCount(0, 1), + CallExpression: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -992,12 +1146,16 @@ describe("SourceCode", () => { "if (/* Leading comment for Identifier */ a) {}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - IfStatement: assertCommentCount(1, 0), - Identifier: assertCommentCount(1, 0), - BlockStatement: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + IfStatement: assertCommentCount(1, 0), + Identifier: assertCommentCount(1, 0), + BlockStatement: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1012,15 +1170,19 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - VariableDeclaration: assertCommentCount(0, 0), - VariableDeclarator: assertCommentCount(0, 0), - ObjectExpression: assertCommentCount(0, 1), - ReturnStatement: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + VariableDeclaration: assertCommentCount(0, 0), + VariableDeclarator: assertCommentCount(0, 0), + ObjectExpression: assertCommentCount(0, 1), + ReturnStatement: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1034,15 +1196,19 @@ describe("SourceCode", () => { "var baz;" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - VariableDeclaration: assertCommentCount(0, 0), - VariableDeclarator: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - ObjectExpression: assertCommentCount(0, 0), - Property: assertCommentCount(0, 1), - Literal: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + VariableDeclaration: assertCommentCount(0, 0), + VariableDeclarator: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + ObjectExpression: assertCommentCount(0, 0), + Property: assertCommentCount(0, 1), + Literal: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1061,16 +1227,20 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - ExportDefaultDeclaration: assertCommentCount(1, 0), - ClassDeclaration: assertCommentCount(0, 0), - ClassBody: assertCommentCount(0, 0), - MethodDefinition: assertCommentCount(1, 0), - Identifier: assertCommentCount(0, 0), - FunctionExpression: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + ExportDefaultDeclaration: assertCommentCount(1, 0), + ClassDeclaration: assertCommentCount(0, 0), + ClassBody: assertCommentCount(0, 0), + MethodDefinition: assertCommentCount(1, 0), + Identifier: assertCommentCount(0, 0), + FunctionExpression: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1084,19 +1254,25 @@ describe("SourceCode", () => { ].join("\n"); let varDeclCount = 0; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - VariableDeclaration(node) { - if (varDeclCount === 0) { - assertCommentCount(1, 1)(node); - } else { - assertCommentCount(1, 0)(node); - } - varDeclCount++; - }, - VariableDeclarator: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + + VariableDeclaration(node) { + if (varDeclCount === 0) { + assertCommentCount(1, 1)(node); + } else { + assertCommentCount(1, 0)(node); + } + varDeclCount++; + }, + + VariableDeclarator: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1110,18 +1286,24 @@ describe("SourceCode", () => { ].join("\n"); let varDeclCount = 0; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - VariableDeclaration(node) { - if (varDeclCount === 0) { - assertCommentCount(1, 1)(node); - } else { - assertCommentCount(1, 0)(node); - } - varDeclCount++; - }, - Identifier: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + + VariableDeclaration(node) { + if (varDeclCount === 0) { + assertCommentCount(1, 1)(node); + } else { + assertCommentCount(1, 0)(node); + } + varDeclCount++; + }, + + Identifier: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1129,7 +1311,13 @@ describe("SourceCode", () => { it("should include shebang comment when program only contains shebang", () => { const code = "#!/usr/bin/env node"; - linter.defineRule("checker", () => ({ Program: assertCommentCount(1, 0) })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(1, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1141,13 +1329,17 @@ describe("SourceCode", () => { "// Trailing comment for VariableDeclaration" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - VariableDeclaration: assertCommentCount(1, 1), - VariableDeclarator: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 1), - Literal: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + VariableDeclaration: assertCommentCount(1, 1), + VariableDeclarator: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 1), + Literal: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1161,14 +1353,18 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - ExpressionStatement: assertCommentCount(1, 1), - CallExpression: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + ExpressionStatement: assertCommentCount(1, 1), + CallExpression: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1182,13 +1378,17 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - DebuggerStatement: assertCommentCount(1, 1) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + DebuggerStatement: assertCommentCount(1, 1) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1202,13 +1402,17 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - ReturnStatement: assertCommentCount(1, 1) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + ReturnStatement: assertCommentCount(1, 1) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1222,13 +1426,17 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - ThrowStatement: assertCommentCount(1, 1) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + ThrowStatement: assertCommentCount(1, 1) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1243,16 +1451,20 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - WhileStatement: assertCommentCount(1, 1), - Literal: assertCommentCount(0, 0), - VariableDeclaration: assertCommentCount(1, 0), - VariableDeclarator: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + WhileStatement: assertCommentCount(1, 1), + Literal: assertCommentCount(0, 0), + VariableDeclaration: assertCommentCount(1, 0), + VariableDeclarator: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1271,24 +1483,30 @@ describe("SourceCode", () => { ].join("\n"); let switchCaseCount = 0; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - SwitchStatement: assertCommentCount(0, 0), - SwitchCase: node => { - if (switchCaseCount === 0) { - assertCommentCount(1, 1)(node); - } else { - assertCommentCount(1, 0)(node); - } - switchCaseCount++; - }, - Literal: assertCommentCount(0, 0), - ExpressionStatement: assertCommentCount(0, 0), - CallExpression: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + SwitchStatement: assertCommentCount(0, 0), + + SwitchCase(node) { + if (switchCaseCount === 0) { + assertCommentCount(1, 1)(node); + } else { + assertCommentCount(1, 0)(node); + } + switchCaseCount++; + }, + + Literal: assertCommentCount(0, 0), + ExpressionStatement: assertCommentCount(0, 0), + CallExpression: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1305,21 +1523,27 @@ describe("SourceCode", () => { ].join("\n"); let switchCaseCount = 0; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - SwitchStatement: assertCommentCount(0, 0), - SwitchCase: node => { - if (switchCaseCount === 0) { - assertCommentCount(1, 1)(node); - } else { - assertCommentCount(1, 0)(node); - } - switchCaseCount++; - }, - Literal: assertCommentCount(0, 0), - ExpressionStatement: assertCommentCount(0, 0), - CallExpression: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + SwitchStatement: assertCommentCount(0, 0), + + SwitchCase(node) { + if (switchCaseCount === 0) { + assertCommentCount(1, 1)(node); + } else { + assertCommentCount(1, 0)(node); + } + switchCaseCount++; + }, + + Literal: assertCommentCount(0, 0), + ExpressionStatement: assertCommentCount(0, 0), + CallExpression: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1338,23 +1562,29 @@ describe("SourceCode", () => { ].join("\n"); let breakStatementCount = 0; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - SwitchStatement: assertCommentCount(0, 0), - SwitchCase: node => { - if (breakStatementCount === 0) { - assertCommentCount(0, 0)(node); - } else { - assertCommentCount(0, 1)(node); - } - breakStatementCount++; - }, - BreakStatement: assertCommentCount(0, 0), - Literal: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + SwitchStatement: assertCommentCount(0, 0), + + SwitchCase(node) { + if (breakStatementCount === 0) { + assertCommentCount(0, 0)(node); + } else { + assertCommentCount(0, 1)(node); + } + breakStatementCount++; + }, + + BreakStatement: assertCommentCount(0, 0), + Literal: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1368,14 +1598,18 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - SwitchStatement: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - SwitchCase: assertCommentCount(0, 1), - BreakStatement: assertCommentCount(0, 0), - Literal: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + SwitchStatement: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + SwitchCase: assertCommentCount(0, 1), + BreakStatement: assertCommentCount(0, 0), + Literal: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1394,22 +1628,26 @@ describe("SourceCode", () => { "};" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - ExpressionStatement: assertCommentCount(0, 0), - AssignmentExpression: assertCommentCount(0, 0), - MemberExpression: assertCommentCount(0, 0), - Identifier: assertCommentCount(0, 0), - FunctionExpression: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(0, 0), - SwitchStatement: assertCommentCount(0, 0), - SwitchCase: assertCommentCount(0, 1), - ReturnStatement: assertCommentCount(0, 0), - CallExpression: assertCommentCount(0, 0), - BinaryExpression: assertCommentCount(0, 0), - Literal: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + ExpressionStatement: assertCommentCount(0, 0), + AssignmentExpression: assertCommentCount(0, 0), + MemberExpression: assertCommentCount(0, 0), + Identifier: assertCommentCount(0, 0), + FunctionExpression: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(0, 0), + SwitchStatement: assertCommentCount(0, 0), + SwitchCase: assertCommentCount(0, 1), + ReturnStatement: assertCommentCount(0, 0), + CallExpression: assertCommentCount(0, 0), + BinaryExpression: assertCommentCount(0, 0), + Literal: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1420,7 +1658,13 @@ describe("SourceCode", () => { "/*another comment*/" ].join("\n"); - linter.defineRule("checker", () => ({ Program: assertCommentCount(2, 0) })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(2, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1433,10 +1677,14 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - BlockStatement: assertCommentCount(0, 2) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + BlockStatement: assertCommentCount(0, 2) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1449,11 +1697,15 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - ClassDeclaration: assertCommentCount(0, 0), - ClassBody: assertCommentCount(0, 2) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + ClassDeclaration: assertCommentCount(0, 0), + ClassBody: assertCommentCount(0, 2) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1466,11 +1718,15 @@ describe("SourceCode", () => { "})" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - ExpressionStatement: assertCommentCount(0, 0), - ObjectExpression: assertCommentCount(0, 2) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + ExpressionStatement: assertCommentCount(0, 0), + ObjectExpression: assertCommentCount(0, 2) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1483,11 +1739,15 @@ describe("SourceCode", () => { "]" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - ExpressionStatement: assertCommentCount(0, 0), - ArrayExpression: assertCommentCount(0, 2) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + ExpressionStatement: assertCommentCount(0, 0), + ArrayExpression: assertCommentCount(0, 2) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1500,11 +1760,15 @@ describe("SourceCode", () => { "}" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - SwitchStatement: assertCommentCount(0, 2), - Identifier: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + SwitchStatement: assertCommentCount(0, 2), + Identifier: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1519,21 +1783,27 @@ describe("SourceCode", () => { ].join("\n"); let varDeclCount = 0; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - VariableDeclaration: assertCommentCount(1, 2), - VariableDeclarator: node => { - if (varDeclCount === 0) { - assertCommentCount(0, 0)(node); - } else if (varDeclCount === 1) { - assertCommentCount(1, 0)(node); - } else { - assertCommentCount(1, 0)(node); - } - varDeclCount++; - }, - Identifier: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + VariableDeclaration: assertCommentCount(1, 2), + + VariableDeclarator(node) { + if (varDeclCount === 0) { + assertCommentCount(0, 0)(node); + } else if (varDeclCount === 1) { + assertCommentCount(1, 0)(node); + } else { + assertCommentCount(1, 0)(node); + } + varDeclCount++; + }, + + Identifier: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1545,12 +1815,16 @@ describe("SourceCode", () => { " a;" ].join("\n"); - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - VariableDeclaration: assertCommentCount(0, 0), - VariableDeclarator: assertCommentCount(2, 0), - Identifier: assertCommentCount(0, 0) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + VariableDeclaration: assertCommentCount(0, 0), + VariableDeclarator: assertCommentCount(2, 0), + Identifier: assertCommentCount(0, 0) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1558,12 +1832,16 @@ describe("SourceCode", () => { it("should return attached comments between tokens to the correct nodes for empty function declarations", () => { const code = "/* 1 */ function /* 2 */ foo(/* 3 */) /* 4 */ { /* 5 */ } /* 6 */"; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - FunctionDeclaration: assertCommentCount(1, 1), - Identifier: assertCommentCount(1, 0), - BlockStatement: assertCommentCount(1, 1) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + FunctionDeclaration: assertCommentCount(1, 1), + Identifier: assertCommentCount(1, 0), + BlockStatement: assertCommentCount(1, 1) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1572,19 +1850,25 @@ describe("SourceCode", () => { const code = "/* 1 */ class /* 2 */ Foo /* 3 */ extends /* 4 */ Bar /* 5 */ { /* 6 */ } /* 7 */"; let idCount = 0; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - ClassDeclaration: assertCommentCount(1, 1), - Identifier: node => { - if (idCount === 0) { - assertCommentCount(1, 1)(node); - } else { - assertCommentCount(1, 1)(node); - } - idCount++; - }, - ClassBody: assertCommentCount(1, 1) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + ClassDeclaration: assertCommentCount(1, 1), + + Identifier(node) { + if (idCount === 0) { + assertCommentCount(1, 1)(node); + } else { + assertCommentCount(1, 1)(node); + } + idCount++; + }, + + ClassBody: assertCommentCount(1, 1) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1592,11 +1876,15 @@ describe("SourceCode", () => { it("should return attached comments between tokens to the correct nodes for empty switch statements", () => { const code = "/* 1 */ switch /* 2 */ (/* 3 */ foo /* 4 */) /* 5 */ { /* 6 */ } /* 7 */"; - linter.defineRule("checker", () => ({ - Program: assertCommentCount(0, 0), - SwitchStatement: assertCommentCount(1, 6), - Identifier: assertCommentCount(1, 1) - })); + linter.defineRule("checker", { + create() { + return ({ + Program: assertCommentCount(0, 0), + SwitchStatement: assertCommentCount(1, 6), + Identifier: assertCommentCount(1, 1) + }); + } + }); assert.isEmpty(linter.verify(code, config)); }); @@ -1735,7 +2023,6 @@ describe("SourceCode", () => { }); - describe("getNodeByRangeIndex()", () => { let sourceCode; @@ -2737,4 +3024,767 @@ describe("SourceCode", () => { assert.strictEqual(sourceCode.getIndexFromLoc({ line: 8, column: 0 }), CODE.length); }); }); + + describe("getScope()", () => { + + it("should throw an error when argument is missing", () => { + + linter.defineRule("get-scope", { + create: context => ({ + Program() { + context.sourceCode.getScope(); + } + }) + }); + + assert.throws(() => { + linter.verify( + "foo", + { + rules: { "get-scope": 2 } + } + ); + }, /Missing required argument: node/u); + + }); + + /** + * Get the scope on the node `astSelector` specified. + * @param {string} code The source code to verify. + * @param {string} astSelector The AST selector to get scope. + * @param {number} [ecmaVersion=5] The ECMAScript version. + * @returns {{node: ASTNode, scope: escope.Scope}} Gotten scope. + */ + function getScope(code, astSelector, ecmaVersion = 5) { + let node, scope; + + linter.defineRule("get-scope", { + create: context => ({ + [astSelector](node0) { + node = node0; + scope = context.sourceCode.getScope(node); + } + }) + }); + linter.verify( + code, + { + parserOptions: { ecmaVersion }, + rules: { "get-scope": 2 } + } + ); + + return { node, scope }; + } + + it("should return 'function' scope on FunctionDeclaration (ES5)", () => { + const { node, scope } = getScope("function f() {}", "FunctionDeclaration"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node); + }); + + it("should return 'function' scope on FunctionExpression (ES5)", () => { + const { node, scope } = getScope("!function f() {}", "FunctionExpression"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node); + }); + + it("should return 'function' scope on the body of FunctionDeclaration (ES5)", () => { + const { node, scope } = getScope("function f() {}", "BlockStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent); + }); + + it("should return 'function' scope on the body of FunctionDeclaration (ES2015)", () => { + const { node, scope } = getScope("function f() {}", "BlockStatement", 2015); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent); + }); + + it("should return 'function' scope on BlockStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { { var b; } }", "BlockStatement > BlockStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); + }); + + it("should return 'block' scope on BlockStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { { let a; var b; } }", "BlockStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "function"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]); + assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "b"]); + }); + + it("should return 'block' scope on nested BlockStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { { let a; { let b; var c; } } }", "BlockStatement > BlockStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "block"); + assert.strictEqual(scope.upper.upper.type, "function"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); + assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["a"]); + assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "c"]); + }); + + it("should return 'function' scope on SwitchStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); + }); + + it("should return 'switch' scope on SwitchStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchStatement", 2015); + + assert.strictEqual(scope.type, "switch"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); + }); + + it("should return 'function' scope on SwitchCase in functions (ES5)", () => { + const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchCase"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); + }); + + it("should return 'switch' scope on SwitchCase in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchCase", 2015); + + assert.strictEqual(scope.type, "switch"); + assert.strictEqual(scope.block, node.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); + }); + + it("should return 'catch' scope on CatchClause in functions (ES5)", () => { + const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause"); + + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); + }); + + it("should return 'catch' scope on CatchClause in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause", 2015); + + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); + }); + + it("should return 'catch' scope on the block of CatchClause in functions (ES5)", () => { + const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause > BlockStatement"); + + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block, node.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); + }); + + it("should return 'block' scope on the block of CatchClause in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]); + }); + + it("should return 'function' scope on ForStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]); + }); + + it("should return 'for' scope on ForStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement", 2015); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["i"]); + }); + + it("should return 'function' scope on the block body of ForStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]); + }); + + it("should return 'block' scope on the block body of ForStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), []); + assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["i"]); + }); + + it("should return 'function' scope on ForInStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]); + }); + + it("should return 'for' scope on ForInStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement", 2015); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["key"]); + }); + + it("should return 'function' scope on the block body of ForInStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement > BlockStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]); + }); + + it("should return 'block' scope on the block body of ForInStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), []); + assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["key"]); + }); + + it("should return 'for' scope on ForOfStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement", 2015); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["x"]); + }); + + it("should return 'block' scope on the block body of ForOfStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), []); + assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["x"]); + }); + + it("should shadow the same name variable by the iteration variable.", () => { + const { node, scope } = getScope("let x; for (let x of x) {}", "ForOfStatement", 2015); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.upper.type, "global"); + assert.strictEqual(scope.block, node); + assert.strictEqual(scope.upper.variables[0].references.length, 0); + assert.strictEqual(scope.references[0].identifier, node.left.declarations[0].id); + assert.strictEqual(scope.references[1].identifier, node.right); + assert.strictEqual(scope.references[1].resolved, scope.variables[0]); + }); + }); + + describe("getAncestors()", () => { + const code = TEST_CODE; + + it("should retrieve all ancestors when used", () => { + + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const sourceCode = context.sourceCode; + const ancestors = sourceCode.getAncestors(node); + + assert.strictEqual(ancestors.length, 3); + }); + return { BinaryExpression: spy }; + } + } + } + } + }, + rules: { "test/checker": "error" } + }; + + flatLinter.verify(code, config, filename, true); + assert(spy && spy.calledOnce, "Spy was not called."); + }); + + it("should retrieve empty ancestors for root node", () => { + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const sourceCode = context.sourceCode; + const ancestors = sourceCode.getAncestors(node); + + assert.strictEqual(ancestors.length, 0); + }); + + return { Program: spy }; + } + } + } + } + }, + rules: { "test/checker": "error" } + }; + + flatLinter.verify(code, config); + assert(spy && spy.calledOnce, "Spy was not called."); + }); + + it("should throw an error when the argument is missing", () => { + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(() => { + const sourceCode = context.sourceCode; + + assert.throws(() => { + sourceCode.getAncestors(); + }, /Missing required argument: node/u); + + }); + + return { Program: spy }; + } + } + } + } + }, + rules: { "test/checker": "error" } + }; + + flatLinter.verify(code, config); + assert(spy && spy.calledOnce, "Spy was not called."); + }); + }); + + + describe("getDeclaredVariables(node)", () => { + + /** + * Assert `sourceCode.getDeclaredVariables(node)` is valid. + * @param {string} code A code to check. + * @param {string} type A type string of ASTNode. This method checks variables on the node of the type. + * @param {Array>} expectedNamesList An array of expected variable names. The expected variable names is an array of string. + * @returns {void} + */ + function verify(code, type, expectedNamesList) { + linter.defineRules({ + test: { + create(context) { + + const sourceCode = context.sourceCode; + + /** + * Assert `sourceCode.getDeclaredVariables(node)` is empty. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function checkEmpty(node) { + assert.strictEqual(0, sourceCode.getDeclaredVariables(node).length); + } + const rule = { + Program: checkEmpty, + EmptyStatement: checkEmpty, + BlockStatement: checkEmpty, + ExpressionStatement: checkEmpty, + LabeledStatement: checkEmpty, + BreakStatement: checkEmpty, + ContinueStatement: checkEmpty, + WithStatement: checkEmpty, + SwitchStatement: checkEmpty, + ReturnStatement: checkEmpty, + ThrowStatement: checkEmpty, + TryStatement: checkEmpty, + WhileStatement: checkEmpty, + DoWhileStatement: checkEmpty, + ForStatement: checkEmpty, + ForInStatement: checkEmpty, + DebuggerStatement: checkEmpty, + ThisExpression: checkEmpty, + ArrayExpression: checkEmpty, + ObjectExpression: checkEmpty, + Property: checkEmpty, + SequenceExpression: checkEmpty, + UnaryExpression: checkEmpty, + BinaryExpression: checkEmpty, + AssignmentExpression: checkEmpty, + UpdateExpression: checkEmpty, + LogicalExpression: checkEmpty, + ConditionalExpression: checkEmpty, + CallExpression: checkEmpty, + NewExpression: checkEmpty, + MemberExpression: checkEmpty, + SwitchCase: checkEmpty, + Identifier: checkEmpty, + Literal: checkEmpty, + ForOfStatement: checkEmpty, + ArrowFunctionExpression: checkEmpty, + YieldExpression: checkEmpty, + TemplateLiteral: checkEmpty, + TaggedTemplateExpression: checkEmpty, + TemplateElement: checkEmpty, + ObjectPattern: checkEmpty, + ArrayPattern: checkEmpty, + RestElement: checkEmpty, + AssignmentPattern: checkEmpty, + ClassBody: checkEmpty, + MethodDefinition: checkEmpty, + MetaProperty: checkEmpty + }; + + rule[type] = function(node) { + const expectedNames = expectedNamesList.shift(); + const variables = sourceCode.getDeclaredVariables(node); + + assert(Array.isArray(expectedNames)); + assert(Array.isArray(variables)); + assert.strictEqual(expectedNames.length, variables.length); + for (let i = variables.length - 1; i >= 0; i--) { + assert.strictEqual(expectedNames[i], variables[i].name); + } + }; + return rule; + } + } + }); + linter.verify(code, { + rules: { test: 2 }, + parserOptions: { + ecmaVersion: 6, + sourceType: "module" + } + }); + + // Check all expected names are asserted. + assert.strictEqual(0, expectedNamesList.length); + } + + it("VariableDeclaration", () => { + const code = "\n var {a, x: [b], y: {c = 0}} = foo;\n let {d, x: [e], y: {f = 0}} = foo;\n const {g, x: [h], y: {i = 0}} = foo, {j, k = function(z) { let l; }} = bar;\n "; + const namesList = [ + ["a", "b", "c"], + ["d", "e", "f"], + ["g", "h", "i", "j", "k"], + ["l"] + ]; + + verify(code, "VariableDeclaration", namesList); + }); + + it("VariableDeclaration (on for-in/of loop)", () => { + + // TDZ scope is created here, so tests to exclude those. + const code = "\n for (var {a, x: [b], y: {c = 0}} in foo) {\n let g;\n }\n for (let {d, x: [e], y: {f = 0}} of foo) {\n let h;\n }\n "; + const namesList = [ + ["a", "b", "c"], + ["g"], + ["d", "e", "f"], + ["h"] + ]; + + verify(code, "VariableDeclaration", namesList); + }); + + it("VariableDeclarator", () => { + + // TDZ scope is created here, so tests to exclude those. + const code = "\n var {a, x: [b], y: {c = 0}} = foo;\n let {d, x: [e], y: {f = 0}} = foo;\n const {g, x: [h], y: {i = 0}} = foo, {j, k = function(z) { let l; }} = bar;\n "; + const namesList = [ + ["a", "b", "c"], + ["d", "e", "f"], + ["g", "h", "i"], + ["j", "k"], + ["l"] + ]; + + verify(code, "VariableDeclarator", namesList); + }); + + it("FunctionDeclaration", () => { + const code = "\n function foo({a, x: [b], y: {c = 0}}, [d, e]) {\n let z;\n }\n function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) {\n let z;\n }\n "; + const namesList = [ + ["foo", "a", "b", "c", "d", "e"], + ["bar", "f", "g", "h", "i", "j"] + ]; + + verify(code, "FunctionDeclaration", namesList); + }); + + it("FunctionExpression", () => { + const code = "\n (function foo({a, x: [b], y: {c = 0}}, [d, e]) {\n let z;\n });\n (function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) {\n let z;\n });\n "; + const namesList = [ + ["foo", "a", "b", "c", "d", "e"], + ["bar", "f", "g", "h", "i", "j"], + ["q"] + ]; + + verify(code, "FunctionExpression", namesList); + }); + + it("ArrowFunctionExpression", () => { + const code = "\n (({a, x: [b], y: {c = 0}}, [d, e]) => {\n let z;\n });\n (({f, x: [g], y: {h = 0}}, [i, j]) => {\n let z;\n });\n "; + const namesList = [ + ["a", "b", "c", "d", "e"], + ["f", "g", "h", "i", "j"] + ]; + + verify(code, "ArrowFunctionExpression", namesList); + }); + + it("ClassDeclaration", () => { + const code = "\n class A { foo(x) { let y; } }\n class B { foo(x) { let y; } }\n "; + const namesList = [ + ["A", "A"], // outer scope's and inner scope's. + ["B", "B"] + ]; + + verify(code, "ClassDeclaration", namesList); + }); + + it("ClassExpression", () => { + const code = "\n (class A { foo(x) { let y; } });\n (class B { foo(x) { let y; } });\n "; + const namesList = [ + ["A"], + ["B"] + ]; + + verify(code, "ClassExpression", namesList); + }); + + it("CatchClause", () => { + const code = "\n try {} catch ({a, b}) {\n let x;\n try {} catch ({c, d}) {\n let y;\n }\n }\n "; + const namesList = [ + ["a", "b"], + ["c", "d"] + ]; + + verify(code, "CatchClause", namesList); + }); + + it("ImportDeclaration", () => { + const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; + const namesList = [ + [], + ["a"], + ["b", "c", "d"] + ]; + + verify(code, "ImportDeclaration", namesList); + }); + + it("ImportSpecifier", () => { + const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; + const namesList = [ + ["c"], + ["d"] + ]; + + verify(code, "ImportSpecifier", namesList); + }); + + it("ImportDefaultSpecifier", () => { + const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; + const namesList = [ + ["b"] + ]; + + verify(code, "ImportDefaultSpecifier", namesList); + }); + + it("ImportNamespaceSpecifier", () => { + const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; + const namesList = [ + ["a"] + ]; + + verify(code, "ImportNamespaceSpecifier", namesList); + }); + }); + + describe("markVariableAsUsed()", () => { + + it("should mark variables in current scope as used", () => { + const code = "var a = 1, b = 2;"; + let spy; + + linter.defineRule("checker", { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(() => { + assert.isTrue(sourceCode.markVariableAsUsed("a")); + + const scope = context.getScope(); + + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); + + return { "Program:exit": spy }; + } + }); + + linter.verify(code, { rules: { checker: "error" } }); + assert(spy && spy.calledOnce); + }); + + it("should mark variables in function args as used", () => { + const code = "function abc(a, b) { return 1; }"; + let spy; + + linter.defineRule("checker", { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(node => { + assert.isTrue(sourceCode.markVariableAsUsed("a", node)); + + const scope = context.getScope(); + + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); + + return { ReturnStatement: spy }; + } + }); + + linter.verify(code, { rules: { checker: "error" } }); + assert(spy && spy.calledOnce); + }); + + it("should mark variables in higher scopes as used", () => { + const code = "var a, b; function abc() { return 1; }"; + let returnSpy, exitSpy; + + linter.defineRule("checker", { + create(context) { + const sourceCode = context.sourceCode; + + returnSpy = sinon.spy(node => { + assert.isTrue(sourceCode.markVariableAsUsed("a", node)); + }); + exitSpy = sinon.spy(() => { + const scope = context.getScope(); + + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); + + return { ReturnStatement: returnSpy, "Program:exit": exitSpy }; + } + }); + + linter.verify(code, { rules: { checker: "error" } }); + assert(returnSpy && returnSpy.calledOnce); + assert(exitSpy && exitSpy.calledOnce); + }); + + it("should mark variables in Node.js environment as used", () => { + const code = "var a = 1, b = 2;"; + let spy; + + linter.defineRule("checker", { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(() => { + const globalScope = context.getScope(), + childScope = globalScope.childScopes[0]; + + assert.isTrue(sourceCode.markVariableAsUsed("a")); + + assert.isTrue(getVariable(childScope, "a").eslintUsed); + assert.isUndefined(getVariable(childScope, "b").eslintUsed); + }); + + return { "Program:exit": spy }; + } + }); + + linter.verify(code, { rules: { checker: "error" }, env: { node: true } }); + assert(spy && spy.calledOnce); + }); + + it("should mark variables in modules as used", () => { + const code = "var a = 1, b = 2;"; + let spy; + + linter.defineRule("checker", { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(() => { + const globalScope = context.getScope(), + childScope = globalScope.childScopes[0]; + + assert.isTrue(sourceCode.markVariableAsUsed("a")); + + assert.isTrue(getVariable(childScope, "a").eslintUsed); + assert.isUndefined(getVariable(childScope, "b").eslintUsed); + }); + + return { "Program:exit": spy }; + } + }); + + linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }, filename, true); + assert(spy && spy.calledOnce); + }); + + it("should return false if the given variable is not found", () => { + const code = "var a = 1, b = 2;"; + let spy; + + linter.defineRule("checker", { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(() => { + assert.isFalse(sourceCode.markVariableAsUsed("c")); + }); + + return { "Program:exit": spy }; + } + }); + + linter.verify(code, { rules: { checker: "error" } }); + assert(spy && spy.calledOnce); + }); + + }); }); diff --git a/eslint/tests/lib/source-code/token-store.js b/eslint/tests/lib/source-code/token-store.js index ff54a6a..3b9db7c 100644 --- a/eslint/tests/lib/source-code/token-store.js +++ b/eslint/tests/lib/source-code/token-store.js @@ -627,8 +627,8 @@ describe("TokenStore", () => { const tokenStore = new TokenStore(ast.tokens, ast.comments); /* - * Actually, the first of nodes is always tokens, not comments. - * But I think this test case is needed for completeness. + * A node must not start with a token: it can start with a comment or be empty. + * This test case is needed for completeness. */ const token = tokenStore.getFirstToken( { range: [ast.comments[0].range[0], ast.tokens[5].range[1]] }, @@ -644,8 +644,8 @@ describe("TokenStore", () => { const tokenStore = new TokenStore(ast.tokens, ast.comments); /* - * Actually, the first of nodes is always tokens, not comments. - * But I think this test case is needed for completeness. + * A node must not start with a token: it can start with a comment or be empty. + * This test case is needed for completeness. */ const token = tokenStore.getFirstToken( { range: [ast.comments[0].range[0], ast.tokens[5].range[1]] } @@ -654,6 +654,38 @@ describe("TokenStore", () => { assert.strictEqual(token.value, "c"); }); + it("should retrieve the first token if the root node contains a trailing comment", () => { + const parser = require("../../fixtures/parsers/all-comments-parser"); + const code = "foo // comment"; + const ast = parser.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getFirstToken(ast); + + assert.strictEqual(token, ast.tokens[0]); + }); + + it("should return null if the source contains only comments", () => { + const code = "// comment"; + const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getFirstToken(ast, { + filter() { + assert.fail("Unexpected call to filter callback"); + } + }); + + assert.strictEqual(token, null); + }); + + it("should return null if the source is empty", () => { + const code = ""; + const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getFirstToken(ast); + + assert.strictEqual(token, null); + }); + }); describe("when calling getLastTokens", () => { @@ -814,8 +846,8 @@ describe("TokenStore", () => { const tokenStore = new TokenStore(ast.tokens, ast.comments); /* - * Actually, the last of nodes is always tokens, not comments. - * But I think this test case is needed for completeness. + * A node must not end with a token: it can end with a comment or be empty. + * This test case is needed for completeness. */ const token = tokenStore.getLastToken( { range: [ast.tokens[0].range[0], ast.comments[0].range[1]] }, @@ -831,8 +863,8 @@ describe("TokenStore", () => { const tokenStore = new TokenStore(ast.tokens, ast.comments); /* - * Actually, the last of nodes is always tokens, not comments. - * But I think this test case is needed for completeness. + * A node must not end with a token: it can end with a comment or be empty. + * This test case is needed for completeness. */ const token = tokenStore.getLastToken( { range: [ast.tokens[0].range[0], ast.comments[0].range[1]] } @@ -841,6 +873,38 @@ describe("TokenStore", () => { assert.strictEqual(token.value, "b"); }); + it("should retrieve the last token if the root node contains a trailing comment", () => { + const parser = require("../../fixtures/parsers/all-comments-parser"); + const code = "foo // comment"; + const ast = parser.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getLastToken(ast); + + assert.strictEqual(token, ast.tokens[0]); + }); + + it("should return null if the source contains only comments", () => { + const code = "// comment"; + const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getLastToken(ast, { + filter() { + assert.fail("Unexpected call to filter callback"); + } + }); + + assert.strictEqual(token, null); + }); + + it("should return null if the source is empty", () => { + const code = ""; + const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getLastToken(ast); + + assert.strictEqual(token, null); + }); + }); describe("when calling getFirstTokensBetween", () => { diff --git a/eslint/tests/lib/unsupported-api.js b/eslint/tests/lib/unsupported-api.js index 53b466a..3a65ba2 100644 --- a/eslint/tests/lib/unsupported-api.js +++ b/eslint/tests/lib/unsupported-api.js @@ -27,6 +27,10 @@ describe("unsupported-api", () => { assert.isFunction(api.FlatESLint); }); + it("should have shouldUseFlatConfig exposed", () => { + assert.isFunction(api.shouldUseFlatConfig); + }); + it("should have FlatRuleTester exposed", () => { assert.isFunction(api.FlatRuleTester); }); diff --git a/eslint/tests/performance/jshint.js b/eslint/tests/performance/jshint.js index f79d6ed..553f274 100644 --- a/eslint/tests/performance/jshint.js +++ b/eslint/tests/performance/jshint.js @@ -1070,10 +1070,12 @@ module.exports=require('E/GbHF'); (function(){/*! * JSHint, by JSHint Community. * - * This file (and this file only) is licensed under the same slightly modified - * MIT license that JSLint is. It stops evil-doers everywhere: + * This file (and this file only) was licensed under the same slightly modified + * MIT license that JSLint is. After a relicensing in 2020 this is now MIT License (Expat). + * Relicensing: https://jshint.com/relicensing-2020/ + * License-Url: https://github.com/jshint/jshint/blob/main/LICENSE * - * Copyright (c) 2002 Douglas Crockford (www.JSLint.com) + * Copyright 2012 Anton Kovalyov (http://jshint.com) * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the "Software"), @@ -1085,8 +1087,6 @@ module.exports=require('E/GbHF'); * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * - * The Software shall be used for Good, not Evil. - * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -11290,4 +11290,4 @@ SlowBuffer.prototype.writeDoubleBE = Buffer.prototype.writeDoubleBE; },{}]},{},["E/GbHF"]) ; JSHINT = require('jshint').JSHINT; -}()); \ No newline at end of file +}()); diff --git a/eslint/tools/commit-readme.sh b/eslint/tools/commit-readme.sh new file mode 100644 index 0000000..f2341f0 --- /dev/null +++ b/eslint/tools/commit-readme.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +#------------------------------------------------------------------------------ +# Commits the data files if any have changed +#------------------------------------------------------------------------------ + +if [ -z "$(git status --porcelain)" ]; then + echo "Data did not change." +else + echo "Data changed!" + + # commit the result + git add README.md + git commit -m "docs: Update README" + + # push back to source control + git push origin HEAD +fi diff --git a/eslint/tools/fetch-docs-links.js b/eslint/tools/fetch-docs-links.js index 3d92633..9cdd05a 100644 --- a/eslint/tools/fetch-docs-links.js +++ b/eslint/tools/fetch-docs-links.js @@ -7,7 +7,7 @@ * * To fetch info for just selected files (for use with lint-staged): * - * node tools/fetch-docs-links.js docs/src/user-guide/index.md + * node tools/fetch-docs-links.js docs/src/use/index.md * * @author Nicholas C. Zakas */ @@ -99,7 +99,7 @@ async function fetchLinkMeta(url) { console.error("Could not fetch data for", url); console.error(ex.message); console.error(ex.stack); - process.exit(1); // eslint-disable-line n/no-process-exit -- used in tools + process.exit(1); } } } diff --git a/eslint/tools/fuzzer-runner.js b/eslint/tools/fuzzer-runner.js index b056c70..805cc02 100644 --- a/eslint/tools/fuzzer-runner.js +++ b/eslint/tools/fuzzer-runner.js @@ -58,7 +58,7 @@ function run({ amount = 300, fuzzBrokenAutofixes = true } = {}) { linter, count: crashTestCount, checkAutofixes: false, - progressCallback: elapsedErrors => { + progressCallback(elapsedErrors) { progressBar.tick(1, { elapsedErrors }); progressBar.render(); } @@ -68,7 +68,7 @@ function run({ amount = 300, fuzzBrokenAutofixes = true } = {}) { linter, count: autofixTestCount, checkAutofixes: true, - progressCallback: elapsedErrors => { + progressCallback(elapsedErrors) { progressBar.tick(ESTIMATED_CRASH_AUTOFIX_PERFORMANCE_RATIO, { elapsedErrors: crashTestResults.length + elapsedErrors }); progressBar.render(); } diff --git a/eslint/tools/rule-types.json b/eslint/tools/rule-types.json index d690284..f3fe8f8 100644 --- a/eslint/tools/rule-types.json +++ b/eslint/tools/rule-types.json @@ -59,6 +59,7 @@ "lines-around-comment": "layout", "lines-around-directive": "layout", "lines-between-class-members": "layout", + "logical-assignment-operators": "suggestion", "max-classes-per-file": "suggestion", "max-depth": "suggestion", "max-len": "layout", @@ -109,6 +110,7 @@ "no-empty-character-class": "problem", "no-empty-function": "suggestion", "no-empty-pattern": "problem", + "no-empty-static-block": "suggestion", "no-eq-null": "suggestion", "no-eval": "suggestion", "no-ex-assign": "problem", @@ -153,6 +155,7 @@ "no-nested-ternary": "suggestion", "no-new": "suggestion", "no-new-func": "suggestion", + "no-new-native-nonconstructor": "problem", "no-new-object": "suggestion", "no-new-require": "suggestion", "no-new-symbol": "problem", diff --git a/eslint/tools/update-eslint-all.js b/eslint/tools/update-eslint-all.js new file mode 100644 index 0000000..b964876 --- /dev/null +++ b/eslint/tools/update-eslint-all.js @@ -0,0 +1,42 @@ +/** + * @fileoverview Script to update the eslint:all configuration. + * @author Nicholas C. Zakas + */ + +"use strict"; + +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + +const fs = require("fs"); +const builtInRules = require("../lib/rules"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const allRules = {}; + +for (const [ruleId, rule] of builtInRules) { + if (!rule.meta.deprecated) { + allRules[ruleId] = "error"; + } +} + +//----------------------------------------------------------------------------- +// Main +//----------------------------------------------------------------------------- + +const code = `/* + * WARNING: This file is autogenerated using the tools/update-eslint-all.js + * script. Do not edit manually. + */ +"use strict"; + +/* eslint quote-props: off -- autogenerated so don't lint */ + +module.exports = Object.freeze(${JSON.stringify({ rules: allRules }, null, 4)}); +`; + +fs.writeFileSync("./packages/js/src/configs/eslint-all.js", code, "utf8"); diff --git a/eslint/tools/update-readme.js b/eslint/tools/update-readme.js index 0704cef..4564df0 100644 --- a/eslint/tools/update-readme.js +++ b/eslint/tools/update-readme.js @@ -13,20 +13,19 @@ // Requirements //----------------------------------------------------------------------------- -const path = require("path"); const fs = require("fs"); const { stripIndents } = require("common-tags"); const ejs = require("ejs"); +const got = require("got"); //----------------------------------------------------------------------------- // Data //----------------------------------------------------------------------------- -const README_FILE_PATH = path.resolve(__dirname, "../README.md"); -const WEBSITE_DATA_PATH = path.resolve(__dirname, "../../website/_data"); +const SPONSORS_URL = "https://raw.githubusercontent.com/eslint/eslint.org/main/src/_data/sponsors.json"; +const TEAM_URL = "https://raw.githubusercontent.com/eslint/eslint.org/main/src/_data/team.json"; +const README_FILE_PATH = "./README.md"; -const team = JSON.parse(fs.readFileSync(path.join(WEBSITE_DATA_PATH, "team.json"))); -const allSponsors = JSON.parse(fs.readFileSync(path.join(WEBSITE_DATA_PATH, "sponsors.json"))); const readme = fs.readFileSync(README_FILE_PATH, "utf8"); const heights = { @@ -35,13 +34,31 @@ const heights = { bronze: 32 }; -// remove backers from sponsors list - not shown on readme -delete allSponsors.backers; - //----------------------------------------------------------------------------- // Helpers //----------------------------------------------------------------------------- +/** + * Fetches the latest sponsors data from the website. + * @returns {Object} The sponsors data object. + */ +async function fetchSponsorsData() { + const data = await got(SPONSORS_URL).json(); + + // remove backers from sponsors list - not shown on readme + delete data.backers; + + return data; +} + +/** + * Fetches the latest team data from the website. + * @returns {Object} The sponsors data object. + */ +async function fetchTeamData() { + return got(TEAM_URL).json(); +} + /** * Formats an array of team members for inclusion in the readme. * @param {Array} members The array of members to format. @@ -54,7 +71,7 @@ function formatTeamMembers(members) { members.map((member, index) => `
    ${(index + 1) % 9 === 0 ? "" : ""}`).join("") }
    `; @@ -74,7 +91,7 @@ function formatSponsors(sponsors) { ${ nonEmptySponsors.map(tier => `

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

    ${ - sponsors[tier].map(sponsor => `${sponsor.name}`).join(" ") + sponsors[tier].map(sponsor => `${sponsor.name}`).join(" ") }

    `).join("") } `; @@ -111,20 +128,38 @@ const HTML_TEMPLATE = stripIndents` <%- formatTeamMembers(team.committers) %> + <% } %> + + <% if (team.website.length > 0) { %> + ### Website Team + + Team members who focus specifically on eslint.org + + <%- formatTeamMembers(team.website) %> + <% } %> `; -// replace all of the section -let newReadme = readme.replace(/[\w\W]*?/u, ejs.render(HTML_TEMPLATE, { - team, - formatTeamMembers -})); +(async () => { -newReadme = newReadme.replace(/[\w\W]*?/u, formatSponsors(allSponsors)); + const [allSponsors, team] = await Promise.all([ + fetchSponsorsData(), + fetchTeamData() + ]); -// replace multiple consecutive blank lines with just one blank line -newReadme = newReadme.replace(/(?<=^|\n)\n{2,}/gu, "\n"); + // replace all of the section + let newReadme = readme.replace(/[\w\W]*?/u, ejs.render(HTML_TEMPLATE, { + team, + formatTeamMembers + })); -// output to the file -fs.writeFileSync(README_FILE_PATH, newReadme, "utf8"); + newReadme = newReadme.replace(/[\w\W]*?/u, formatSponsors(allSponsors)); + + // replace multiple consecutive blank lines with just one blank line + newReadme = newReadme.replace(/(?<=^|\n)\n{2,}/gu, "\n"); + + // output to the file + fs.writeFileSync(README_FILE_PATH, newReadme, "utf8"); + +})();