From 1f6301e474aa868f72890ac61d68754e34a3708a Mon Sep 17 00:00:00 2001 From: marco-ippolito Date: Sun, 14 Apr 2024 11:31:08 +0200 Subject: [PATCH 1/2] test: fix eslint update script --- tools/dep_updaters/update-eslint.sh | 1 + tools/dep_updaters/update-minimatch.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/dep_updaters/update-eslint.sh b/tools/dep_updaters/update-eslint.sh index 60292fb505d293..66b47596551c1f 100755 --- a/tools/dep_updaters/update-eslint.sh +++ b/tools/dep_updaters/update-eslint.sh @@ -58,6 +58,7 @@ rm -rf ../node_modules/eslint --no-save \ --omit=dev \ --omit=peer \ + --legacy-peer-deps \ eslint-plugin-jsdoc \ eslint-plugin-markdown \ @babel/core \ diff --git a/tools/dep_updaters/update-minimatch.sh b/tools/dep_updaters/update-minimatch.sh index 2e6fdd0c11890b..d487eb760632cb 100755 --- a/tools/dep_updaters/update-minimatch.sh +++ b/tools/dep_updaters/update-minimatch.sh @@ -54,7 +54,7 @@ cd package "$NODE" "$NPM" install esbuild --save-dev -"$NODE" "$NPM" pkg set scripts.node-build="esbuild ./dist/cjs/index.js --bundle --platform=node --outfile=index.js" +"$NODE" "$NPM" pkg set scripts.node-build="esbuild ./dist/commonjs/index.js --bundle --platform=node --outfile=index.js" "$NODE" "$NPM" run node-build From ca6544fc52dffed37c2119913c746ecd2df4f8b4 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Sun, 14 Apr 2024 09:35:19 +0000 Subject: [PATCH 2/2] tools: update eslint to 9.0.0 --- tools/node_modules/eslint/bin/eslint.js | 15 +- .../node_modules/eslint/conf/config-schema.js | 93 -- .../node_modules/eslint/conf/ecma-version.js | 16 + .../eslint/conf/rule-type-list.json | 4 +- tools/node_modules/eslint/lib/api.js | 18 +- .../eslint/lib/cli-engine/cli-engine.js | 17 +- .../lib/cli-engine/formatters/checkstyle.js | 60 - .../lib/cli-engine/formatters/compact.js | 60 - .../formatters/formatters-meta.json | 30 +- .../lib/cli-engine/formatters/jslint-xml.js | 41 - .../eslint/lib/cli-engine/formatters/junit.js | 82 -- .../eslint/lib/cli-engine/formatters/tap.js | 95 -- .../eslint/lib/cli-engine/formatters/unix.js | 58 - .../lib/cli-engine/formatters/visualstudio.js | 63 - .../lib/cli-engine/lint-result-cache.js | 4 +- .../eslint/lib/cli-engine/xml-escape.js | 34 - tools/node_modules/eslint/lib/cli.js | 112 +- .../eslint/lib/config/default-config.js | 3 + .../eslint/lib/config/flat-config-array.js | 20 - .../eslint/lib/config/flat-config-helpers.js | 61 +- .../eslint/lib/config/flat-config-schema.js | 8 +- .../eslint/lib/config/rule-validator.js | 48 +- .../eslint/lib/eslint/eslint-helpers.js | 50 +- .../node_modules/eslint/lib/eslint/eslint.js | 1265 +++++++++++----- .../eslint/lib/eslint/flat-eslint.js | 1155 --------------- tools/node_modules/eslint/lib/eslint/index.js | 4 +- .../eslint/lib/eslint/legacy-eslint.js | 728 +++++++++ .../lib/linter/apply-disable-directives.js | 42 +- .../code-path-analysis/code-path-analyzer.js | 1 - .../linter/code-path-analysis/code-path.js | 62 +- .../linter/code-path-analysis/fork-context.js | 2 +- .../lib/linter/config-comment-parser.js | 19 +- tools/node_modules/eslint/lib/linter/index.js | 4 +- .../eslint/lib/linter/interpolate.js | 26 +- .../node_modules/eslint/lib/linter/linter.js | 549 +++++-- .../eslint/lib/linter/report-translator.js | 6 +- tools/node_modules/eslint/lib/linter/rules.js | 21 +- .../eslint/lib/linter/source-code-fixer.js | 2 +- .../node_modules/eslint/lib/linter/timing.js | 24 +- tools/node_modules/eslint/lib/options.js | 34 +- .../lib/rule-tester/flat-rule-tester.js | 1131 -------------- .../eslint/lib/rule-tester/index.js | 4 +- .../eslint/lib/rule-tester/rule-tester.js | 771 +++++----- .../eslint/lib/rules/array-bracket-newline.js | 2 +- .../eslint/lib/rules/array-bracket-spacing.js | 2 +- .../eslint/lib/rules/block-scoped-var.js | 2 +- .../eslint/lib/rules/callback-return.js | 4 +- .../eslint/lib/rules/camelcase.js | 8 +- .../eslint/lib/rules/comma-dangle.js | 2 +- .../eslint/lib/rules/comma-style.js | 4 +- .../eslint/lib/rules/complexity.js | 15 +- .../eslint/lib/rules/constructor-super.js | 199 ++- .../eslint/lib/rules/default-case.js | 2 +- .../node_modules/eslint/lib/rules/eol-last.js | 4 +- .../lib/rules/function-paren-newline.js | 4 +- .../eslint/lib/rules/indent-legacy.js | 10 +- tools/node_modules/eslint/lib/rules/indent.js | 10 +- tools/node_modules/eslint/lib/rules/index.js | 3 +- .../eslint/lib/rules/key-spacing.js | 4 +- .../eslint/lib/rules/line-comment-position.js | 2 +- .../lib/rules/lines-around-directive.js | 4 +- .../eslint/lib/rules/max-depth.js | 2 +- .../node_modules/eslint/lib/rules/max-len.js | 6 +- .../eslint/lib/rules/max-lines.js | 6 +- .../eslint/lib/rules/max-nested-callbacks.js | 2 +- .../eslint/lib/rules/max-params.js | 2 +- .../eslint/lib/rules/max-statements.js | 2 +- .../lib/rules/multiline-comment-style.js | 14 +- .../node_modules/eslint/lib/rules/new-cap.js | 2 +- .../eslint/lib/rules/newline-after-var.js | 2 +- .../eslint/lib/rules/newline-before-return.js | 2 +- .../rules/no-constant-binary-expression.js | 15 +- .../eslint/lib/rules/no-constructor-return.js | 4 +- .../eslint/lib/rules/no-dupe-class-members.js | 4 +- .../eslint/lib/rules/no-else-return.js | 2 +- .../eslint/lib/rules/no-empty-function.js | 4 +- .../eslint/lib/rules/no-empty-static-block.js | 2 +- .../eslint/lib/rules/no-extend-native.js | 3 +- .../eslint/lib/rules/no-extra-semi.js | 2 +- .../eslint/lib/rules/no-fallthrough.js | 57 +- .../eslint/lib/rules/no-implicit-coercion.js | 90 +- .../eslint/lib/rules/no-inner-declarations.js | 25 +- .../eslint/lib/rules/no-invalid-regexp.js | 2 +- .../eslint/lib/rules/no-invalid-this.js | 2 +- .../eslint/lib/rules/no-lone-blocks.js | 6 +- .../eslint/lib/rules/no-loss-of-precision.js | 2 +- .../rules/no-misleading-character-class.js | 294 +++- .../lib/rules/no-mixed-spaces-and-tabs.js | 2 +- .../lib/rules/no-multiple-empty-lines.js | 2 +- .../lib/rules/no-new-native-nonconstructor.js | 2 +- .../eslint/lib/rules/no-new-symbol.js | 9 +- .../eslint/lib/rules/no-restricted-globals.js | 2 +- .../eslint/lib/rules/no-restricted-imports.js | 226 ++- .../eslint/lib/rules/no-restricted-modules.js | 4 +- .../eslint/lib/rules/no-return-await.js | 2 +- .../eslint/lib/rules/no-sequences.js | 1 + .../eslint/lib/rules/no-this-before-super.js | 58 +- .../eslint/lib/rules/no-trailing-spaces.js | 5 +- .../eslint/lib/rules/no-unneeded-ternary.js | 2 +- .../lib/rules/no-unsafe-optional-chaining.js | 2 +- .../rules/no-unused-private-class-members.js | 2 +- .../eslint/lib/rules/no-unused-vars.js | 233 ++- .../eslint/lib/rules/no-useless-assignment.js | 566 +++++++ .../lib/rules/no-useless-backreference.js | 2 +- .../lib/rules/no-useless-computed-key.js | 4 +- .../eslint/lib/rules/no-useless-return.js | 9 +- .../eslint/lib/rules/object-curly-spacing.js | 6 +- .../lib/rules/object-property-newline.js | 2 +- .../node_modules/eslint/lib/rules/one-var.js | 10 +- .../eslint/lib/rules/padded-blocks.js | 14 +- .../eslint/lib/rules/prefer-arrow-callback.js | 6 +- .../eslint/lib/rules/prefer-reflect.js | 2 +- .../eslint/lib/rules/prefer-regex-literals.js | 2 +- .../eslint/lib/rules/prefer-template.js | 2 +- tools/node_modules/eslint/lib/rules/radix.js | 4 +- .../eslint/lib/rules/require-jsdoc.js | 122 -- .../eslint/lib/rules/semi-style.js | 2 +- .../eslint/lib/rules/sort-imports.js | 2 +- .../eslint/lib/rules/sort-keys.js | 2 +- .../eslint/lib/rules/sort-vars.js | 2 +- .../eslint/lib/rules/space-unary-ops.js | 2 +- tools/node_modules/eslint/lib/rules/strict.js | 2 +- .../eslint/lib/rules/use-isnan.js | 108 +- .../eslint/lib/rules/utils/ast-utils.js | 23 +- .../eslint/lib/rules/utils/char-source.js | 240 +++ .../lib/rules/utils/lazy-loading-rule-map.js | 2 +- .../eslint/lib/rules/utils/unicode/index.js | 13 +- .../eslint/lib/rules/valid-jsdoc.js | 516 ------- .../eslint/lib/rules/yield-star-spacing.js | 2 +- .../eslint/lib/shared/config-validator.js | 347 ----- .../eslint/lib/shared/deprecation-warnings.js | 58 - .../lib/shared/relative-module-resolver.js | 50 - .../eslint/lib/shared/runtime-info.js | 1 + .../eslint/lib/shared/serialization.js | 55 + tools/node_modules/eslint/lib/shared/stats.js | 30 + tools/node_modules/eslint/lib/shared/types.js | 36 +- .../eslint/lib/source-code/index.js | 4 +- .../eslint/lib/source-code/source-code.js | 254 ++-- .../token-store/backward-token-cursor.js | 6 +- .../lib/source-code/token-store/cursors.js | 6 +- .../forward-token-comment-cursor.js | 6 +- .../token-store/forward-token-cursor.js | 6 +- .../lib/source-code/token-store/index.js | 4 +- .../eslint/lib/unsupported-api.js | 8 +- .../eslint/messages/plugin-conflict.js | 2 +- .../eslint/messages/plugin-invalid.js | 2 +- .../eslint/messages/plugin-missing.js | 2 +- .../eslint/node_modules/.bin/eslint | 1 - .../eslint/node_modules/.bin/rimraf | 1 - .../@ampproject/remapping/dist/remapping.mjs | 34 +- .../remapping/dist/remapping.umd.js | 32 +- .../@ampproject/remapping/package.json | 6 +- .../@babel/code-frame/lib/index.js | 37 +- .../node_modules/ansi-styles/index.js | 165 --- .../node_modules/ansi-styles/license | 9 - .../node_modules/ansi-styles/package.json | 56 - .../node_modules/ansi-styles/readme.md | 147 -- .../code-frame/node_modules/chalk/index.js | 228 --- .../node_modules/chalk/index.js.flow | 93 -- .../code-frame/node_modules/chalk/license | 9 - .../node_modules/chalk/package.json | 71 - .../code-frame/node_modules/chalk/readme.md | 314 ---- .../node_modules/chalk/templates.js | 128 -- .../node_modules/color-convert/LICENSE | 21 - .../node_modules/color-convert/conversions.js | 868 ----------- .../node_modules/color-convert/index.js | 78 - .../node_modules/color-convert/package.json | 46 - .../node_modules/color-convert/route.js | 97 -- .../node_modules/color-name/LICENSE | 8 - .../node_modules/color-name/index.js | 152 -- .../node_modules/color-name/package.json | 25 - .../escape-string-regexp/index.js | 11 - .../node_modules/escape-string-regexp/license | 21 - .../escape-string-regexp/package.json | 41 - .../escape-string-regexp/readme.md | 27 - .../code-frame/node_modules/has-flag/index.js | 8 - .../code-frame/node_modules/has-flag/license | 9 - .../node_modules/has-flag/package.json | 44 - .../node_modules/has-flag/readme.md | 70 - .../node_modules/supports-color/browser.js | 5 - .../node_modules/supports-color/index.js | 131 -- .../node_modules/supports-color/license | 9 - .../node_modules/supports-color/package.json | 53 - .../node_modules/supports-color/readme.md | 66 - .../@babel/code-frame/package.json | 6 +- .../compat-data/data/overlapping-plugins.json | 6 +- .../compat-data/data/plugin-bugfixes.json | 12 + .../@babel/compat-data/data/plugins.json | 12 + .../@babel/compat-data/package.json | 2 +- .../node_modules/@babel/core/cjs-proxy.cjs | 8 +- .../@babel/core/lib/config/config-chain.js | 2 +- .../core/lib/config/files/module-types.js | 33 +- .../core/lib/config/helpers/config-api.js | 2 +- .../config/validation/option-assertions.js | 2 +- .../core/lib/config/validation/options.js | 7 +- .../node_modules/@babel/core/lib/index.js | 2 +- .../@babel/core/lib/parser/index.js | 2 +- .../lib/parser/util/missing-plugin-helper.js | 12 +- .../lib/transformation/block-hoist-plugin.js | 39 +- .../core/lib/transformation/file/file.js | 33 +- .../node_modules/@babel/core/package.json | 30 +- .../eslint-parser/lib/analyze-scope.cjs | 17 +- .../@babel/eslint-parser/lib/client.cjs | 96 +- .../eslint-parser/lib/experimental-worker.cjs | 2 +- .../@babel/eslint-parser/lib/index.cjs | 2 +- .../@babel/eslint-parser/lib/parse.cjs | 2 +- .../@babel/eslint-parser/package.json | 4 +- .../generator/lib/generators/typescript.js | 11 +- .../@babel/generator/package.json | 12 +- .../lib/import-injector.js | 100 +- .../@babel/helper-module-imports/package.json | 8 +- .../@babel/helper-plugin-utils/lib/index.js | 8 +- .../@babel/helper-plugin-utils/package.json | 2 +- .../@babel/helper-string-parser/package.json | 2 +- .../@babel/helpers/lib/helpers-generated.js | 31 +- .../@babel/helpers/lib/helpers.js | 49 +- .../helpers/lib/helpers/applyDecs2311.js | 236 +++ .../helpers/lib/helpers/assertClassBrand.js | 14 + .../lib/helpers/classPrivateFieldGet2.js | 12 + .../lib/helpers/classPrivateFieldSet2.js | 13 + .../helpers/lib/helpers/classPrivateGetter.js | 12 + .../helpers/lib/helpers/classPrivateSetter.js | 13 + .../@babel/helpers/lib/helpers/toSetter.js | 18 + .../node_modules/@babel/helpers/lib/index.js | 4 +- .../node_modules/@babel/helpers/package.json | 14 +- .../helpers/scripts/generate-helpers.js | 12 +- .../@babel/highlight/lib/index.js | 64 +- .../@babel/highlight/package.json | 5 +- .../node_modules/@babel/parser/lib/index.js | 20 +- .../node_modules/@babel/parser/package.json | 8 +- .../package.json | 6 +- .../@babel/template/lib/literal.js | 2 +- .../@babel/template/lib/populate.js | 2 +- .../node_modules/@babel/template/package.json | 6 +- .../@babel/traverse/lib/context.js | 9 +- .../@babel/traverse/lib/path/evaluation.js | 2 +- .../@babel/traverse/lib/path/introspection.js | 12 + .../@babel/traverse/lib/scope/index.js | 20 +- .../node_modules/@babel/traverse/package.json | 14 +- .../@babel/types/lib/clone/cloneNode.js | 24 +- .../types/lib/definitions/placeholders.js | 2 +- .../types/lib/definitions/typescript.js | 6 +- .../@babel/types/lib/index.js.flow | 1 + .../node_modules/@babel/types/package.json | 4 +- .../node_modules/eslint-visitor-keys/LICENSE} | 5 +- .../dist/eslint-visitor-keys.cjs | 384 +++++ .../dist/eslint-visitor-keys.d.cts | 27 + .../eslint-visitor-keys/lib/index.js | 65 + .../eslint-visitor-keys/lib/visitor-keys.js | 315 ++++ .../eslint-visitor-keys/package.json | 74 + .../eslintrc/dist/eslintrc-universal.cjs | 75 +- .../@eslint/eslintrc/dist/eslintrc.cjs | 94 +- .../eslintrc/lib/config-array/config-array.js | 19 +- .../eslintrc/lib/shared/config-validator.js | 81 +- .../@eslint/eslintrc/package.json | 8 +- .../node_modules/@eslint/js/package.json | 4 +- .../@eslint/js/src/configs/eslint-all.js | 2 +- .../js/src/configs/eslint-recommended.js | 9 +- .../@humanwhocodes/config-array/api.js | 108 +- .../@humanwhocodes/config-array/package.json | 8 +- .../@humanwhocodes/object-schema/package.json | 7 +- .../gen-mapping/dist/gen-mapping.mjs | 27 +- .../gen-mapping/dist/gen-mapping.umd.js | 24 +- .../@jridgewell/gen-mapping/package.json | 6 +- .../@jridgewell/set-array/dist/set-array.mjs | 85 +- .../set-array/dist/set-array.umd.js | 87 +- .../@jridgewell/set-array/package.json | 7 +- .../trace-mapping/dist/trace-mapping.mjs | 55 +- .../trace-mapping/dist/trace-mapping.umd.js | 54 +- .../@jridgewell/trace-mapping/package.json | 2 +- .../@ungap/structured-clone/LICENSE | 15 - .../structured-clone/cjs/deserialize.js | 78 - .../@ungap/structured-clone/cjs/index.js | 27 - .../@ungap/structured-clone/cjs/json.js | 24 - .../@ungap/structured-clone/cjs/package.json | 1 - .../@ungap/structured-clone/cjs/serialize.js | 160 -- .../@ungap/structured-clone/cjs/types.js | 22 - .../structured-clone/esm/deserialize.js | 79 - .../@ungap/structured-clone/esm/index.js | 25 - .../@ungap/structured-clone/esm/json.js | 21 - .../@ungap/structured-clone/esm/serialize.js | 161 -- .../@ungap/structured-clone/esm/types.js | 11 - .../@ungap/structured-clone/package.json | 53 - .../structured-clone/structured-json.js | 1 - .../node_modules/caniuse-lite/data/agents.js | 2 +- .../caniuse-lite/data/browserVersions.js | 2 +- .../caniuse-lite/data/features.js | 2 +- .../caniuse-lite/data/features/aac.js | 2 +- .../data/features/abortcontroller.js | 2 +- .../caniuse-lite/data/features/ac3-ec3.js | 2 +- .../data/features/accelerometer.js | 2 +- .../data/features/addeventlistener.js | 2 +- .../data/features/alternate-stylesheet.js | 2 +- .../data/features/ambient-light.js | 2 +- .../caniuse-lite/data/features/apng.js | 2 +- .../data/features/array-find-index.js | 2 +- .../caniuse-lite/data/features/array-find.js | 2 +- .../caniuse-lite/data/features/array-flat.js | 2 +- .../data/features/array-includes.js | 2 +- .../data/features/arrow-functions.js | 2 +- .../caniuse-lite/data/features/asmjs.js | 2 +- .../data/features/async-clipboard.js | 2 +- .../data/features/async-functions.js | 2 +- .../caniuse-lite/data/features/atob-btoa.js | 2 +- .../caniuse-lite/data/features/audio-api.js | 2 +- .../caniuse-lite/data/features/audio.js | 2 +- .../caniuse-lite/data/features/audiotracks.js | 2 +- .../caniuse-lite/data/features/autofocus.js | 2 +- .../caniuse-lite/data/features/auxclick.js | 2 +- .../caniuse-lite/data/features/av1.js | 2 +- .../caniuse-lite/data/features/avif.js | 2 +- .../data/features/background-attachment.js | 2 +- .../data/features/background-clip-text.js | 2 +- .../data/features/background-img-opts.js | 2 +- .../data/features/background-position-x-y.js | 2 +- .../features/background-repeat-round-space.js | 2 +- .../data/features/background-sync.js | 2 +- .../data/features/battery-status.js | 2 +- .../caniuse-lite/data/features/beacon.js | 2 +- .../data/features/beforeafterprint.js | 2 +- .../caniuse-lite/data/features/bigint.js | 2 +- .../caniuse-lite/data/features/blobbuilder.js | 2 +- .../caniuse-lite/data/features/bloburls.js | 2 +- .../data/features/border-image.js | 2 +- .../data/features/border-radius.js | 2 +- .../data/features/broadcastchannel.js | 2 +- .../caniuse-lite/data/features/brotli.js | 2 +- .../caniuse-lite/data/features/calc.js | 2 +- .../data/features/canvas-blending.js | 2 +- .../caniuse-lite/data/features/canvas-text.js | 2 +- .../caniuse-lite/data/features/canvas.js | 2 +- .../caniuse-lite/data/features/ch-unit.js | 2 +- .../data/features/chacha20-poly1305.js | 2 +- .../data/features/channel-messaging.js | 2 +- .../data/features/childnode-remove.js | 2 +- .../caniuse-lite/data/features/classlist.js | 2 +- .../client-hints-dpr-width-viewport.js | 2 +- .../caniuse-lite/data/features/clipboard.js | 2 +- .../caniuse-lite/data/features/colr-v1.js | 2 +- .../caniuse-lite/data/features/colr.js | 2 +- .../data/features/comparedocumentposition.js | 2 +- .../data/features/console-basic.js | 2 +- .../data/features/console-time.js | 2 +- .../caniuse-lite/data/features/const.js | 2 +- .../data/features/constraint-validation.js | 2 +- .../data/features/contenteditable.js | 2 +- .../data/features/contentsecuritypolicy.js | 2 +- .../data/features/contentsecuritypolicy2.js | 2 +- .../data/features/cookie-store-api.js | 2 +- .../caniuse-lite/data/features/cors.js | 2 +- .../data/features/createimagebitmap.js | 2 +- .../data/features/credential-management.js | 2 +- .../data/features/cryptography.js | 2 +- .../caniuse-lite/data/features/css-all.js | 2 +- .../data/features/css-anchor-positioning.js | 2 +- .../data/features/css-animation.js | 2 +- .../data/features/css-any-link.js | 2 +- .../data/features/css-appearance.js | 2 +- .../data/features/css-at-counter-style.js | 2 +- .../data/features/css-autofill.js | 2 +- .../data/features/css-backdrop-filter.js | 2 +- .../data/features/css-background-offsets.js | 2 +- .../data/features/css-backgroundblendmode.js | 2 +- .../data/features/css-boxdecorationbreak.js | 2 +- .../data/features/css-boxshadow.js | 2 +- .../caniuse-lite/data/features/css-canvas.js | 2 +- .../data/features/css-caret-color.js | 2 +- .../data/features/css-cascade-layers.js | 2 +- .../data/features/css-cascade-scope.js | 2 +- .../data/features/css-case-insensitive.js | 2 +- .../data/features/css-clip-path.js | 2 +- .../data/features/css-color-adjust.js | 2 +- .../data/features/css-color-function.js | 2 +- .../data/features/css-conic-gradients.js | 2 +- .../features/css-container-queries-style.js | 2 +- .../data/features/css-container-queries.js | 2 +- .../features/css-container-query-units.js | 2 +- .../data/features/css-containment.js | 2 +- .../data/features/css-content-visibility.js | 2 +- .../data/features/css-counters.js | 2 +- .../data/features/css-crisp-edges.js | 2 +- .../data/features/css-cross-fade.js | 2 +- .../data/features/css-default-pseudo.js | 2 +- .../data/features/css-descendant-gtgt.js | 2 +- .../data/features/css-deviceadaptation.js | 2 +- .../data/features/css-dir-pseudo.js | 2 +- .../data/features/css-display-contents.js | 2 +- .../data/features/css-element-function.js | 2 +- .../data/features/css-env-function.js | 2 +- .../data/features/css-exclusions.js | 2 +- .../data/features/css-featurequeries.js | 2 +- .../data/features/css-file-selector-button.js | 2 +- .../data/features/css-filter-function.js | 2 +- .../caniuse-lite/data/features/css-filters.js | 2 +- .../data/features/css-first-letter.js | 2 +- .../data/features/css-first-line.js | 2 +- .../caniuse-lite/data/features/css-fixed.js | 2 +- .../data/features/css-focus-visible.js | 2 +- .../data/features/css-focus-within.js | 2 +- .../data/features/css-font-palette.js | 2 +- .../features/css-font-rendering-controls.js | 2 +- .../data/features/css-font-stretch.js | 2 +- .../data/features/css-gencontent.js | 2 +- .../data/features/css-gradients.js | 2 +- .../data/features/css-grid-animation.js | 2 +- .../caniuse-lite/data/features/css-grid.js | 2 +- .../data/features/css-hanging-punctuation.js | 2 +- .../caniuse-lite/data/features/css-has.js | 2 +- .../caniuse-lite/data/features/css-hyphens.js | 2 +- .../data/features/css-image-orientation.js | 2 +- .../data/features/css-image-set.js | 2 +- .../data/features/css-in-out-of-range.js | 2 +- .../data/features/css-indeterminate-pseudo.js | 2 +- .../data/features/css-initial-letter.js | 2 +- .../data/features/css-initial-value.js | 2 +- .../caniuse-lite/data/features/css-lch-lab.js | 2 +- .../data/features/css-letter-spacing.js | 2 +- .../data/features/css-line-clamp.js | 2 +- .../data/features/css-logical-props.js | 2 +- .../data/features/css-marker-pseudo.js | 2 +- .../caniuse-lite/data/features/css-masks.js | 2 +- .../data/features/css-matches-pseudo.js | 2 +- .../data/features/css-math-functions.js | 2 +- .../data/features/css-media-interaction.js | 2 +- .../data/features/css-media-range-syntax.js | 2 +- .../data/features/css-media-resolution.js | 2 +- .../data/features/css-media-scripting.js | 2 +- .../data/features/css-mediaqueries.js | 2 +- .../data/features/css-mixblendmode.js | 2 +- .../data/features/css-module-scripts.js | 1 + .../data/features/css-motion-paths.js | 2 +- .../data/features/css-namespaces.js | 2 +- .../caniuse-lite/data/features/css-nesting.js | 2 +- .../data/features/css-not-sel-list.js | 2 +- .../data/features/css-nth-child-of.js | 2 +- .../caniuse-lite/data/features/css-opacity.js | 2 +- .../data/features/css-optional-pseudo.js | 2 +- .../data/features/css-overflow-anchor.js | 2 +- .../data/features/css-overflow-overlay.js | 2 +- .../data/features/css-overflow.js | 2 +- .../data/features/css-overscroll-behavior.js | 2 +- .../data/features/css-page-break.js | 2 +- .../data/features/css-paged-media.js | 2 +- .../data/features/css-paint-api.js | 2 +- .../data/features/css-placeholder-shown.js | 2 +- .../data/features/css-placeholder.js | 2 +- .../data/features/css-print-color-adjust.js | 2 +- .../data/features/css-read-only-write.js | 2 +- .../data/features/css-rebeccapurple.js | 2 +- .../data/features/css-reflections.js | 2 +- .../caniuse-lite/data/features/css-regions.js | 2 +- .../data/features/css-relative-colors.js | 2 +- .../data/features/css-repeating-gradients.js | 2 +- .../caniuse-lite/data/features/css-resize.js | 2 +- .../data/features/css-revert-value.js | 2 +- .../data/features/css-rrggbbaa.js | 2 +- .../data/features/css-scroll-behavior.js | 2 +- .../data/features/css-scroll-timeline.js | 2 +- .../data/features/css-scrollbar.js | 2 +- .../caniuse-lite/data/features/css-sel2.js | 2 +- .../caniuse-lite/data/features/css-sel3.js | 2 +- .../data/features/css-selection.js | 2 +- .../caniuse-lite/data/features/css-shapes.js | 2 +- .../data/features/css-snappoints.js | 2 +- .../caniuse-lite/data/features/css-sticky.js | 2 +- .../caniuse-lite/data/features/css-subgrid.js | 2 +- .../data/features/css-supports-api.js | 2 +- .../caniuse-lite/data/features/css-table.js | 2 +- .../data/features/css-text-align-last.js | 2 +- .../data/features/css-text-box-trim.js | 2 +- .../data/features/css-text-indent.js | 2 +- .../data/features/css-text-justify.js | 2 +- .../data/features/css-text-orientation.js | 2 +- .../data/features/css-text-spacing.js | 2 +- .../data/features/css-text-wrap-balance.js | 2 +- .../data/features/css-textshadow.js | 2 +- .../data/features/css-touch-action.js | 2 +- .../data/features/css-transitions.js | 2 +- .../data/features/css-unicode-bidi.js | 2 +- .../data/features/css-unset-value.js | 2 +- .../data/features/css-variables.js | 2 +- .../data/features/css-when-else.js | 2 +- .../data/features/css-widows-orphans.js | 2 +- .../data/features/css-width-stretch.js | 2 +- .../data/features/css-writing-mode.js | 2 +- .../caniuse-lite/data/features/css-zoom.js | 2 +- .../caniuse-lite/data/features/css3-attr.js | 2 +- .../data/features/css3-boxsizing.js | 2 +- .../caniuse-lite/data/features/css3-colors.js | 2 +- .../data/features/css3-cursors-grab.js | 2 +- .../data/features/css3-cursors-newer.js | 2 +- .../data/features/css3-cursors.js | 2 +- .../data/features/css3-tabsize.js | 2 +- .../data/features/currentcolor.js | 2 +- .../data/features/custom-elements.js | 2 +- .../data/features/custom-elementsv1.js | 2 +- .../caniuse-lite/data/features/customevent.js | 2 +- .../caniuse-lite/data/features/datalist.js | 2 +- .../caniuse-lite/data/features/dataset.js | 2 +- .../caniuse-lite/data/features/datauri.js | 2 +- .../data/features/date-tolocaledatestring.js | 2 +- .../data/features/declarative-shadow-dom.js | 2 +- .../caniuse-lite/data/features/decorators.js | 2 +- .../caniuse-lite/data/features/details.js | 2 +- .../data/features/deviceorientation.js | 2 +- .../data/features/devicepixelratio.js | 2 +- .../caniuse-lite/data/features/dialog.js | 2 +- .../data/features/dispatchevent.js | 2 +- .../caniuse-lite/data/features/dnssec.js | 2 +- .../data/features/do-not-track.js | 2 +- .../data/features/document-currentscript.js | 2 +- .../data/features/document-evaluate-xpath.js | 2 +- .../data/features/document-execcommand.js | 2 +- .../data/features/document-policy.js | 2 +- .../features/document-scrollingelement.js | 2 +- .../data/features/documenthead.js | 2 +- .../data/features/dom-manip-convenience.js | 2 +- .../caniuse-lite/data/features/dom-range.js | 2 +- .../data/features/domcontentloaded.js | 2 +- .../caniuse-lite/data/features/dommatrix.js | 2 +- .../caniuse-lite/data/features/download.js | 2 +- .../caniuse-lite/data/features/dragndrop.js | 2 +- .../data/features/element-closest.js | 2 +- .../data/features/element-from-point.js | 2 +- .../data/features/element-scroll-methods.js | 2 +- .../caniuse-lite/data/features/eme.js | 2 +- .../caniuse-lite/data/features/eot.js | 2 +- .../caniuse-lite/data/features/es5.js | 2 +- .../caniuse-lite/data/features/es6-class.js | 2 +- .../data/features/es6-generators.js | 2 +- .../features/es6-module-dynamic-import.js | 2 +- .../caniuse-lite/data/features/es6-module.js | 2 +- .../caniuse-lite/data/features/es6-number.js | 2 +- .../data/features/es6-string-includes.js | 2 +- .../caniuse-lite/data/features/es6.js | 2 +- .../caniuse-lite/data/features/eventsource.js | 2 +- .../data/features/extended-system-fonts.js | 2 +- .../data/features/feature-policy.js | 2 +- .../caniuse-lite/data/features/fetch.js | 2 +- .../data/features/fieldset-disabled.js | 2 +- .../caniuse-lite/data/features/fileapi.js | 2 +- .../caniuse-lite/data/features/filereader.js | 2 +- .../data/features/filereadersync.js | 2 +- .../caniuse-lite/data/features/filesystem.js | 2 +- .../caniuse-lite/data/features/flac.js | 2 +- .../caniuse-lite/data/features/flexbox-gap.js | 2 +- .../caniuse-lite/data/features/flexbox.js | 2 +- .../caniuse-lite/data/features/flow-root.js | 2 +- .../data/features/focusin-focusout-events.js | 2 +- .../data/features/font-family-system-ui.js | 2 +- .../data/features/font-feature.js | 2 +- .../data/features/font-kerning.js | 2 +- .../data/features/font-loading.js | 2 +- .../data/features/font-size-adjust.js | 2 +- .../caniuse-lite/data/features/font-smooth.js | 2 +- .../data/features/font-unicode-range.js | 2 +- .../data/features/font-variant-alternates.js | 2 +- .../data/features/font-variant-numeric.js | 2 +- .../caniuse-lite/data/features/fontface.js | 2 +- .../data/features/form-attribute.js | 2 +- .../data/features/form-submit-attributes.js | 2 +- .../data/features/form-validation.js | 2 +- .../caniuse-lite/data/features/forms.js | 2 +- .../caniuse-lite/data/features/fullscreen.js | 2 +- .../caniuse-lite/data/features/gamepad.js | 2 +- .../caniuse-lite/data/features/geolocation.js | 2 +- .../data/features/getboundingclientrect.js | 2 +- .../data/features/getcomputedstyle.js | 2 +- .../data/features/getelementsbyclassname.js | 2 +- .../data/features/getrandomvalues.js | 2 +- .../caniuse-lite/data/features/gyroscope.js | 2 +- .../data/features/hardwareconcurrency.js | 2 +- .../caniuse-lite/data/features/hashchange.js | 2 +- .../caniuse-lite/data/features/heif.js | 2 +- .../caniuse-lite/data/features/hevc.js | 2 +- .../caniuse-lite/data/features/hidden.js | 2 +- .../data/features/high-resolution-time.js | 2 +- .../caniuse-lite/data/features/history.js | 2 +- .../data/features/html-media-capture.js | 2 +- .../data/features/html5semantic.js | 2 +- .../data/features/http-live-streaming.js | 2 +- .../caniuse-lite/data/features/http2.js | 2 +- .../caniuse-lite/data/features/http3.js | 2 +- .../data/features/iframe-sandbox.js | 2 +- .../data/features/iframe-seamless.js | 2 +- .../data/features/iframe-srcdoc.js | 2 +- .../data/features/imagecapture.js | 2 +- .../caniuse-lite/data/features/ime.js | 2 +- .../img-naturalwidth-naturalheight.js | 2 +- .../caniuse-lite/data/features/import-maps.js | 2 +- .../caniuse-lite/data/features/imports.js | 2 +- .../data/features/indeterminate-checkbox.js | 2 +- .../caniuse-lite/data/features/indexeddb.js | 2 +- .../caniuse-lite/data/features/indexeddb2.js | 2 +- .../data/features/inline-block.js | 2 +- .../caniuse-lite/data/features/innertext.js | 2 +- .../data/features/input-autocomplete-onoff.js | 2 +- .../caniuse-lite/data/features/input-color.js | 2 +- .../data/features/input-datetime.js | 2 +- .../data/features/input-email-tel-url.js | 2 +- .../caniuse-lite/data/features/input-event.js | 2 +- .../data/features/input-file-accept.js | 2 +- .../data/features/input-file-directory.js | 2 +- .../data/features/input-file-multiple.js | 2 +- .../data/features/input-inputmode.js | 2 +- .../data/features/input-minlength.js | 2 +- .../data/features/input-number.js | 2 +- .../data/features/input-pattern.js | 2 +- .../data/features/input-placeholder.js | 2 +- .../caniuse-lite/data/features/input-range.js | 2 +- .../data/features/input-search.js | 2 +- .../data/features/input-selection.js | 2 +- .../data/features/insert-adjacent.js | 2 +- .../data/features/insertadjacenthtml.js | 2 +- .../data/features/internationalization.js | 2 +- .../data/features/intersectionobserver-v2.js | 2 +- .../data/features/intersectionobserver.js | 2 +- .../data/features/intl-pluralrules.js | 2 +- .../data/features/intrinsic-width.js | 2 +- .../caniuse-lite/data/features/jpeg2000.js | 2 +- .../caniuse-lite/data/features/jpegxl.js | 2 +- .../caniuse-lite/data/features/jpegxr.js | 2 +- .../data/features/js-regexp-lookbehind.js | 2 +- .../caniuse-lite/data/features/json.js | 2 +- .../features/justify-content-space-evenly.js | 2 +- .../data/features/kerning-pairs-ligatures.js | 2 +- .../data/features/keyboardevent-charcode.js | 2 +- .../data/features/keyboardevent-code.js | 2 +- .../keyboardevent-getmodifierstate.js | 2 +- .../data/features/keyboardevent-key.js | 2 +- .../data/features/keyboardevent-location.js | 2 +- .../data/features/keyboardevent-which.js | 2 +- .../caniuse-lite/data/features/lazyload.js | 2 +- .../caniuse-lite/data/features/let.js | 2 +- .../data/features/link-icon-png.js | 2 +- .../data/features/link-icon-svg.js | 2 +- .../data/features/link-rel-dns-prefetch.js | 2 +- .../data/features/link-rel-modulepreload.js | 2 +- .../data/features/link-rel-preconnect.js | 2 +- .../data/features/link-rel-prefetch.js | 2 +- .../data/features/link-rel-preload.js | 2 +- .../data/features/link-rel-prerender.js | 2 +- .../data/features/loading-lazy-attr.js | 2 +- .../data/features/localecompare.js | 2 +- .../data/features/magnetometer.js | 2 +- .../data/features/matchesselector.js | 2 +- .../caniuse-lite/data/features/matchmedia.js | 2 +- .../caniuse-lite/data/features/mathml.js | 2 +- .../caniuse-lite/data/features/maxlength.js | 2 +- .../mdn-css-backdrop-pseudo-element.js | 2 +- .../mdn-css-unicode-bidi-isolate-override.js | 2 +- .../features/mdn-css-unicode-bidi-isolate.js | 2 +- .../mdn-css-unicode-bidi-plaintext.js | 2 +- .../features/mdn-text-decoration-color.js | 2 +- .../data/features/mdn-text-decoration-line.js | 2 +- .../features/mdn-text-decoration-shorthand.js | 2 +- .../features/mdn-text-decoration-style.js | 2 +- .../data/features/media-fragments.js | 2 +- .../data/features/mediacapture-fromelement.js | 2 +- .../data/features/mediarecorder.js | 2 +- .../caniuse-lite/data/features/mediasource.js | 2 +- .../caniuse-lite/data/features/menu.js | 2 +- .../data/features/meta-theme-color.js | 2 +- .../caniuse-lite/data/features/meter.js | 2 +- .../caniuse-lite/data/features/midi.js | 2 +- .../caniuse-lite/data/features/minmaxwh.js | 2 +- .../caniuse-lite/data/features/mp3.js | 2 +- .../caniuse-lite/data/features/mpeg-dash.js | 2 +- .../caniuse-lite/data/features/mpeg4.js | 2 +- .../data/features/multibackgrounds.js | 2 +- .../caniuse-lite/data/features/multicolumn.js | 2 +- .../data/features/mutation-events.js | 2 +- .../data/features/mutationobserver.js | 2 +- .../data/features/namevalue-storage.js | 2 +- .../data/features/native-filesystem-api.js | 2 +- .../caniuse-lite/data/features/nav-timing.js | 2 +- .../caniuse-lite/data/features/netinfo.js | 2 +- .../data/features/notifications.js | 2 +- .../data/features/object-entries.js | 2 +- .../caniuse-lite/data/features/object-fit.js | 2 +- .../data/features/object-observe.js | 2 +- .../data/features/object-values.js | 2 +- .../caniuse-lite/data/features/objectrtc.js | 2 +- .../data/features/offline-apps.js | 2 +- .../data/features/offscreencanvas.js | 2 +- .../caniuse-lite/data/features/ogg-vorbis.js | 2 +- .../caniuse-lite/data/features/ogv.js | 2 +- .../caniuse-lite/data/features/ol-reversed.js | 2 +- .../data/features/once-event-listener.js | 2 +- .../data/features/online-status.js | 2 +- .../caniuse-lite/data/features/opus.js | 2 +- .../data/features/orientation-sensor.js | 2 +- .../caniuse-lite/data/features/outline.js | 2 +- .../data/features/pad-start-end.js | 2 +- .../data/features/page-transition-events.js | 2 +- .../data/features/pagevisibility.js | 2 +- .../data/features/passive-event-listener.js | 2 +- .../caniuse-lite/data/features/passkeys.js | 2 +- .../data/features/passwordrules.js | 2 +- .../caniuse-lite/data/features/path2d.js | 2 +- .../data/features/payment-request.js | 2 +- .../caniuse-lite/data/features/pdf-viewer.js | 2 +- .../data/features/permissions-api.js | 2 +- .../data/features/permissions-policy.js | 2 +- .../data/features/picture-in-picture.js | 2 +- .../caniuse-lite/data/features/picture.js | 2 +- .../caniuse-lite/data/features/ping.js | 2 +- .../caniuse-lite/data/features/png-alpha.js | 2 +- .../data/features/pointer-events.js | 2 +- .../caniuse-lite/data/features/pointer.js | 2 +- .../caniuse-lite/data/features/pointerlock.js | 2 +- .../caniuse-lite/data/features/portals.js | 2 +- .../data/features/prefers-color-scheme.js | 2 +- .../data/features/prefers-reduced-motion.js | 2 +- .../caniuse-lite/data/features/progress.js | 2 +- .../data/features/promise-finally.js | 2 +- .../caniuse-lite/data/features/promises.js | 2 +- .../caniuse-lite/data/features/proximity.js | 2 +- .../caniuse-lite/data/features/proxy.js | 2 +- .../data/features/publickeypinning.js | 2 +- .../caniuse-lite/data/features/push-api.js | 2 +- .../data/features/queryselector.js | 2 +- .../data/features/readonly-attr.js | 2 +- .../data/features/referrer-policy.js | 2 +- .../data/features/registerprotocolhandler.js | 2 +- .../data/features/rel-noopener.js | 2 +- .../data/features/rel-noreferrer.js | 2 +- .../caniuse-lite/data/features/rellist.js | 2 +- .../caniuse-lite/data/features/rem.js | 2 +- .../data/features/requestanimationframe.js | 2 +- .../data/features/requestidlecallback.js | 2 +- .../data/features/resizeobserver.js | 2 +- .../data/features/resource-timing.js | 2 +- .../data/features/rest-parameters.js | 2 +- .../data/features/rtcpeerconnection.js | 2 +- .../caniuse-lite/data/features/ruby.js | 2 +- .../caniuse-lite/data/features/run-in.js | 2 +- .../features/same-site-cookie-attribute.js | 2 +- .../data/features/screen-orientation.js | 2 +- .../data/features/script-async.js | 2 +- .../data/features/script-defer.js | 2 +- .../data/features/scrollintoview.js | 2 +- .../data/features/scrollintoviewifneeded.js | 2 +- .../caniuse-lite/data/features/sdch.js | 2 +- .../data/features/selection-api.js | 2 +- .../caniuse-lite/data/features/selectlist.js | 2 +- .../data/features/server-timing.js | 2 +- .../data/features/serviceworkers.js | 2 +- .../data/features/setimmediate.js | 2 +- .../caniuse-lite/data/features/shadowdom.js | 2 +- .../caniuse-lite/data/features/shadowdomv1.js | 2 +- .../data/features/sharedarraybuffer.js | 2 +- .../data/features/sharedworkers.js | 2 +- .../caniuse-lite/data/features/sni.js | 2 +- .../caniuse-lite/data/features/spdy.js | 2 +- .../data/features/speech-recognition.js | 2 +- .../data/features/speech-synthesis.js | 2 +- .../data/features/spellcheck-attribute.js | 2 +- .../caniuse-lite/data/features/sql-storage.js | 2 +- .../caniuse-lite/data/features/srcset.js | 2 +- .../caniuse-lite/data/features/stream.js | 2 +- .../caniuse-lite/data/features/streams.js | 2 +- .../data/features/stricttransportsecurity.js | 2 +- .../data/features/style-scoped.js | 2 +- .../data/features/subresource-bundling.js | 2 +- .../data/features/subresource-integrity.js | 2 +- .../caniuse-lite/data/features/svg-css.js | 2 +- .../caniuse-lite/data/features/svg-filters.js | 2 +- .../caniuse-lite/data/features/svg-fonts.js | 2 +- .../data/features/svg-fragment.js | 2 +- .../caniuse-lite/data/features/svg-html.js | 2 +- .../caniuse-lite/data/features/svg-html5.js | 2 +- .../caniuse-lite/data/features/svg-img.js | 2 +- .../caniuse-lite/data/features/svg-smil.js | 2 +- .../caniuse-lite/data/features/svg.js | 2 +- .../caniuse-lite/data/features/sxg.js | 2 +- .../data/features/tabindex-attr.js | 2 +- .../data/features/template-literals.js | 2 +- .../caniuse-lite/data/features/template.js | 2 +- .../caniuse-lite/data/features/temporal.js | 2 +- .../caniuse-lite/data/features/testfeat.js | 2 +- .../data/features/text-decoration.js | 2 +- .../data/features/text-emphasis.js | 2 +- .../data/features/text-overflow.js | 2 +- .../data/features/text-size-adjust.js | 2 +- .../caniuse-lite/data/features/text-stroke.js | 2 +- .../caniuse-lite/data/features/textcontent.js | 2 +- .../caniuse-lite/data/features/textencoder.js | 2 +- .../caniuse-lite/data/features/tls1-1.js | 2 +- .../caniuse-lite/data/features/tls1-2.js | 2 +- .../caniuse-lite/data/features/tls1-3.js | 2 +- .../caniuse-lite/data/features/touch.js | 2 +- .../data/features/transforms2d.js | 2 +- .../data/features/transforms3d.js | 2 +- .../data/features/trusted-types.js | 2 +- .../caniuse-lite/data/features/ttf.js | 2 +- .../caniuse-lite/data/features/typedarrays.js | 2 +- .../caniuse-lite/data/features/u2f.js | 2 +- .../data/features/unhandledrejection.js | 2 +- .../data/features/upgradeinsecurerequests.js | 2 +- .../features/url-scroll-to-text-fragment.js | 2 +- .../caniuse-lite/data/features/url.js | 2 +- .../data/features/urlsearchparams.js | 2 +- .../caniuse-lite/data/features/use-strict.js | 2 +- .../data/features/user-select-none.js | 2 +- .../caniuse-lite/data/features/user-timing.js | 2 +- .../data/features/variable-fonts.js | 2 +- .../data/features/vector-effect.js | 2 +- .../caniuse-lite/data/features/vibration.js | 2 +- .../caniuse-lite/data/features/video.js | 2 +- .../caniuse-lite/data/features/videotracks.js | 2 +- .../data/features/view-transitions.js | 2 +- .../data/features/viewport-unit-variants.js | 2 +- .../data/features/viewport-units.js | 2 +- .../caniuse-lite/data/features/wai-aria.js | 2 +- .../caniuse-lite/data/features/wake-lock.js | 2 +- .../caniuse-lite/data/features/wasm-bigint.js | 1 + .../data/features/wasm-bulk-memory.js | 1 + .../data/features/wasm-extended-const.js | 1 + .../caniuse-lite/data/features/wasm-gc.js | 1 + .../data/features/wasm-multi-memory.js | 1 + .../data/features/wasm-multi-value.js | 1 + .../data/features/wasm-mutable-globals.js | 1 + .../data/features/wasm-nontrapping-fptoint.js | 1 + .../data/features/wasm-reference-types.js | 2 +- .../data/features/wasm-relaxed-simd.js | 1 + .../data/features/wasm-signext.js | 1 + .../caniuse-lite/data/features/wasm-simd.js | 1 + .../data/features/wasm-tail-calls.js | 1 + .../data/features/wasm-threads.js | 1 + .../caniuse-lite/data/features/wasm.js | 2 +- .../caniuse-lite/data/features/wav.js | 2 +- .../caniuse-lite/data/features/wbr-element.js | 2 +- .../data/features/web-animation.js | 2 +- .../data/features/web-app-manifest.js | 2 +- .../data/features/web-bluetooth.js | 2 +- .../caniuse-lite/data/features/web-serial.js | 2 +- .../caniuse-lite/data/features/web-share.js | 2 +- .../caniuse-lite/data/features/webauthn.js | 2 +- .../caniuse-lite/data/features/webcodecs.js | 2 +- .../caniuse-lite/data/features/webgl.js | 2 +- .../caniuse-lite/data/features/webgl2.js | 2 +- .../caniuse-lite/data/features/webgpu.js | 2 +- .../caniuse-lite/data/features/webhid.js | 2 +- .../data/features/webkit-user-drag.js | 2 +- .../caniuse-lite/data/features/webm.js | 2 +- .../caniuse-lite/data/features/webnfc.js | 2 +- .../caniuse-lite/data/features/webp.js | 2 +- .../caniuse-lite/data/features/websockets.js | 2 +- .../data/features/webtransport.js | 2 +- .../caniuse-lite/data/features/webusb.js | 2 +- .../caniuse-lite/data/features/webvr.js | 2 +- .../caniuse-lite/data/features/webvtt.js | 2 +- .../caniuse-lite/data/features/webworkers.js | 2 +- .../caniuse-lite/data/features/webxr.js | 2 +- .../caniuse-lite/data/features/will-change.js | 2 +- .../caniuse-lite/data/features/woff.js | 2 +- .../caniuse-lite/data/features/woff2.js | 2 +- .../caniuse-lite/data/features/word-break.js | 2 +- .../caniuse-lite/data/features/wordwrap.js | 2 +- .../data/features/x-doc-messaging.js | 2 +- .../data/features/x-frame-options.js | 2 +- .../caniuse-lite/data/features/xhr2.js | 2 +- .../caniuse-lite/data/features/xhtml.js | 2 +- .../caniuse-lite/data/features/xhtmlsmil.js | 2 +- .../data/features/xml-serializer.js | 2 +- .../caniuse-lite/data/features/zstd.js | 2 +- .../caniuse-lite/data/regions/AD.js | 2 +- .../caniuse-lite/data/regions/AE.js | 2 +- .../caniuse-lite/data/regions/AF.js | 2 +- .../caniuse-lite/data/regions/AG.js | 2 +- .../caniuse-lite/data/regions/AI.js | 2 +- .../caniuse-lite/data/regions/AL.js | 2 +- .../caniuse-lite/data/regions/AM.js | 2 +- .../caniuse-lite/data/regions/AO.js | 2 +- .../caniuse-lite/data/regions/AR.js | 2 +- .../caniuse-lite/data/regions/AS.js | 2 +- .../caniuse-lite/data/regions/AT.js | 2 +- .../caniuse-lite/data/regions/AU.js | 2 +- .../caniuse-lite/data/regions/AW.js | 2 +- .../caniuse-lite/data/regions/AX.js | 2 +- .../caniuse-lite/data/regions/AZ.js | 2 +- .../caniuse-lite/data/regions/BA.js | 2 +- .../caniuse-lite/data/regions/BB.js | 2 +- .../caniuse-lite/data/regions/BD.js | 2 +- .../caniuse-lite/data/regions/BE.js | 2 +- .../caniuse-lite/data/regions/BF.js | 2 +- .../caniuse-lite/data/regions/BG.js | 2 +- .../caniuse-lite/data/regions/BH.js | 2 +- .../caniuse-lite/data/regions/BI.js | 2 +- .../caniuse-lite/data/regions/BJ.js | 2 +- .../caniuse-lite/data/regions/BM.js | 2 +- .../caniuse-lite/data/regions/BN.js | 2 +- .../caniuse-lite/data/regions/BO.js | 2 +- .../caniuse-lite/data/regions/BR.js | 2 +- .../caniuse-lite/data/regions/BS.js | 2 +- .../caniuse-lite/data/regions/BT.js | 2 +- .../caniuse-lite/data/regions/BW.js | 2 +- .../caniuse-lite/data/regions/BY.js | 2 +- .../caniuse-lite/data/regions/BZ.js | 2 +- .../caniuse-lite/data/regions/CA.js | 2 +- .../caniuse-lite/data/regions/CD.js | 2 +- .../caniuse-lite/data/regions/CF.js | 2 +- .../caniuse-lite/data/regions/CG.js | 2 +- .../caniuse-lite/data/regions/CH.js | 2 +- .../caniuse-lite/data/regions/CI.js | 2 +- .../caniuse-lite/data/regions/CK.js | 2 +- .../caniuse-lite/data/regions/CL.js | 2 +- .../caniuse-lite/data/regions/CM.js | 2 +- .../caniuse-lite/data/regions/CN.js | 2 +- .../caniuse-lite/data/regions/CO.js | 2 +- .../caniuse-lite/data/regions/CR.js | 2 +- .../caniuse-lite/data/regions/CU.js | 2 +- .../caniuse-lite/data/regions/CV.js | 2 +- .../caniuse-lite/data/regions/CX.js | 2 +- .../caniuse-lite/data/regions/CY.js | 2 +- .../caniuse-lite/data/regions/CZ.js | 2 +- .../caniuse-lite/data/regions/DE.js | 2 +- .../caniuse-lite/data/regions/DJ.js | 2 +- .../caniuse-lite/data/regions/DK.js | 2 +- .../caniuse-lite/data/regions/DM.js | 2 +- .../caniuse-lite/data/regions/DO.js | 2 +- .../caniuse-lite/data/regions/DZ.js | 2 +- .../caniuse-lite/data/regions/EC.js | 2 +- .../caniuse-lite/data/regions/EE.js | 2 +- .../caniuse-lite/data/regions/EG.js | 2 +- .../caniuse-lite/data/regions/ER.js | 2 +- .../caniuse-lite/data/regions/ES.js | 2 +- .../caniuse-lite/data/regions/ET.js | 2 +- .../caniuse-lite/data/regions/FI.js | 2 +- .../caniuse-lite/data/regions/FJ.js | 2 +- .../caniuse-lite/data/regions/FK.js | 2 +- .../caniuse-lite/data/regions/FM.js | 2 +- .../caniuse-lite/data/regions/FO.js | 2 +- .../caniuse-lite/data/regions/FR.js | 2 +- .../caniuse-lite/data/regions/GA.js | 2 +- .../caniuse-lite/data/regions/GB.js | 2 +- .../caniuse-lite/data/regions/GD.js | 2 +- .../caniuse-lite/data/regions/GE.js | 2 +- .../caniuse-lite/data/regions/GF.js | 2 +- .../caniuse-lite/data/regions/GG.js | 2 +- .../caniuse-lite/data/regions/GH.js | 2 +- .../caniuse-lite/data/regions/GI.js | 2 +- .../caniuse-lite/data/regions/GL.js | 2 +- .../caniuse-lite/data/regions/GM.js | 2 +- .../caniuse-lite/data/regions/GN.js | 2 +- .../caniuse-lite/data/regions/GP.js | 2 +- .../caniuse-lite/data/regions/GQ.js | 2 +- .../caniuse-lite/data/regions/GR.js | 2 +- .../caniuse-lite/data/regions/GT.js | 2 +- .../caniuse-lite/data/regions/GU.js | 2 +- .../caniuse-lite/data/regions/GW.js | 2 +- .../caniuse-lite/data/regions/GY.js | 2 +- .../caniuse-lite/data/regions/HK.js | 2 +- .../caniuse-lite/data/regions/HN.js | 2 +- .../caniuse-lite/data/regions/HR.js | 2 +- .../caniuse-lite/data/regions/HT.js | 2 +- .../caniuse-lite/data/regions/HU.js | 2 +- .../caniuse-lite/data/regions/ID.js | 2 +- .../caniuse-lite/data/regions/IE.js | 2 +- .../caniuse-lite/data/regions/IL.js | 2 +- .../caniuse-lite/data/regions/IM.js | 2 +- .../caniuse-lite/data/regions/IN.js | 2 +- .../caniuse-lite/data/regions/IQ.js | 2 +- .../caniuse-lite/data/regions/IR.js | 2 +- .../caniuse-lite/data/regions/IS.js | 2 +- .../caniuse-lite/data/regions/IT.js | 2 +- .../caniuse-lite/data/regions/JE.js | 2 +- .../caniuse-lite/data/regions/JM.js | 2 +- .../caniuse-lite/data/regions/JO.js | 2 +- .../caniuse-lite/data/regions/JP.js | 2 +- .../caniuse-lite/data/regions/KE.js | 2 +- .../caniuse-lite/data/regions/KG.js | 2 +- .../caniuse-lite/data/regions/KH.js | 2 +- .../caniuse-lite/data/regions/KI.js | 2 +- .../caniuse-lite/data/regions/KM.js | 2 +- .../caniuse-lite/data/regions/KN.js | 2 +- .../caniuse-lite/data/regions/KP.js | 2 +- .../caniuse-lite/data/regions/KR.js | 2 +- .../caniuse-lite/data/regions/KW.js | 2 +- .../caniuse-lite/data/regions/KY.js | 2 +- .../caniuse-lite/data/regions/KZ.js | 2 +- .../caniuse-lite/data/regions/LA.js | 2 +- .../caniuse-lite/data/regions/LB.js | 2 +- .../caniuse-lite/data/regions/LC.js | 2 +- .../caniuse-lite/data/regions/LI.js | 2 +- .../caniuse-lite/data/regions/LK.js | 2 +- .../caniuse-lite/data/regions/LR.js | 2 +- .../caniuse-lite/data/regions/LS.js | 2 +- .../caniuse-lite/data/regions/LT.js | 2 +- .../caniuse-lite/data/regions/LU.js | 2 +- .../caniuse-lite/data/regions/LV.js | 2 +- .../caniuse-lite/data/regions/LY.js | 2 +- .../caniuse-lite/data/regions/MA.js | 2 +- .../caniuse-lite/data/regions/MC.js | 2 +- .../caniuse-lite/data/regions/MD.js | 2 +- .../caniuse-lite/data/regions/ME.js | 2 +- .../caniuse-lite/data/regions/MG.js | 2 +- .../caniuse-lite/data/regions/MH.js | 2 +- .../caniuse-lite/data/regions/MK.js | 2 +- .../caniuse-lite/data/regions/ML.js | 2 +- .../caniuse-lite/data/regions/MM.js | 2 +- .../caniuse-lite/data/regions/MN.js | 2 +- .../caniuse-lite/data/regions/MO.js | 2 +- .../caniuse-lite/data/regions/MP.js | 2 +- .../caniuse-lite/data/regions/MQ.js | 2 +- .../caniuse-lite/data/regions/MR.js | 2 +- .../caniuse-lite/data/regions/MS.js | 2 +- .../caniuse-lite/data/regions/MT.js | 2 +- .../caniuse-lite/data/regions/MU.js | 2 +- .../caniuse-lite/data/regions/MV.js | 2 +- .../caniuse-lite/data/regions/MW.js | 2 +- .../caniuse-lite/data/regions/MX.js | 2 +- .../caniuse-lite/data/regions/MY.js | 2 +- .../caniuse-lite/data/regions/MZ.js | 2 +- .../caniuse-lite/data/regions/NA.js | 2 +- .../caniuse-lite/data/regions/NC.js | 2 +- .../caniuse-lite/data/regions/NE.js | 2 +- .../caniuse-lite/data/regions/NF.js | 2 +- .../caniuse-lite/data/regions/NG.js | 2 +- .../caniuse-lite/data/regions/NI.js | 2 +- .../caniuse-lite/data/regions/NL.js | 2 +- .../caniuse-lite/data/regions/NO.js | 2 +- .../caniuse-lite/data/regions/NP.js | 2 +- .../caniuse-lite/data/regions/NR.js | 2 +- .../caniuse-lite/data/regions/NU.js | 2 +- .../caniuse-lite/data/regions/NZ.js | 2 +- .../caniuse-lite/data/regions/OM.js | 2 +- .../caniuse-lite/data/regions/PA.js | 2 +- .../caniuse-lite/data/regions/PE.js | 2 +- .../caniuse-lite/data/regions/PF.js | 2 +- .../caniuse-lite/data/regions/PG.js | 2 +- .../caniuse-lite/data/regions/PH.js | 2 +- .../caniuse-lite/data/regions/PK.js | 2 +- .../caniuse-lite/data/regions/PL.js | 2 +- .../caniuse-lite/data/regions/PM.js | 2 +- .../caniuse-lite/data/regions/PN.js | 2 +- .../caniuse-lite/data/regions/PR.js | 2 +- .../caniuse-lite/data/regions/PS.js | 2 +- .../caniuse-lite/data/regions/PT.js | 2 +- .../caniuse-lite/data/regions/PW.js | 2 +- .../caniuse-lite/data/regions/PY.js | 2 +- .../caniuse-lite/data/regions/QA.js | 2 +- .../caniuse-lite/data/regions/RE.js | 2 +- .../caniuse-lite/data/regions/RO.js | 2 +- .../caniuse-lite/data/regions/RS.js | 2 +- .../caniuse-lite/data/regions/RU.js | 2 +- .../caniuse-lite/data/regions/RW.js | 2 +- .../caniuse-lite/data/regions/SA.js | 2 +- .../caniuse-lite/data/regions/SB.js | 2 +- .../caniuse-lite/data/regions/SC.js | 2 +- .../caniuse-lite/data/regions/SD.js | 2 +- .../caniuse-lite/data/regions/SE.js | 2 +- .../caniuse-lite/data/regions/SG.js | 2 +- .../caniuse-lite/data/regions/SH.js | 2 +- .../caniuse-lite/data/regions/SI.js | 2 +- .../caniuse-lite/data/regions/SK.js | 2 +- .../caniuse-lite/data/regions/SL.js | 2 +- .../caniuse-lite/data/regions/SM.js | 2 +- .../caniuse-lite/data/regions/SN.js | 2 +- .../caniuse-lite/data/regions/SO.js | 2 +- .../caniuse-lite/data/regions/SR.js | 2 +- .../caniuse-lite/data/regions/ST.js | 2 +- .../caniuse-lite/data/regions/SV.js | 2 +- .../caniuse-lite/data/regions/SY.js | 2 +- .../caniuse-lite/data/regions/SZ.js | 2 +- .../caniuse-lite/data/regions/TC.js | 2 +- .../caniuse-lite/data/regions/TD.js | 2 +- .../caniuse-lite/data/regions/TG.js | 2 +- .../caniuse-lite/data/regions/TH.js | 2 +- .../caniuse-lite/data/regions/TJ.js | 2 +- .../caniuse-lite/data/regions/TL.js | 2 +- .../caniuse-lite/data/regions/TM.js | 2 +- .../caniuse-lite/data/regions/TN.js | 2 +- .../caniuse-lite/data/regions/TO.js | 2 +- .../caniuse-lite/data/regions/TR.js | 2 +- .../caniuse-lite/data/regions/TT.js | 2 +- .../caniuse-lite/data/regions/TV.js | 2 +- .../caniuse-lite/data/regions/TW.js | 2 +- .../caniuse-lite/data/regions/TZ.js | 2 +- .../caniuse-lite/data/regions/UA.js | 2 +- .../caniuse-lite/data/regions/UG.js | 2 +- .../caniuse-lite/data/regions/US.js | 2 +- .../caniuse-lite/data/regions/UY.js | 2 +- .../caniuse-lite/data/regions/UZ.js | 2 +- .../caniuse-lite/data/regions/VA.js | 2 +- .../caniuse-lite/data/regions/VC.js | 2 +- .../caniuse-lite/data/regions/VE.js | 2 +- .../caniuse-lite/data/regions/VG.js | 2 +- .../caniuse-lite/data/regions/VI.js | 2 +- .../caniuse-lite/data/regions/VN.js | 2 +- .../caniuse-lite/data/regions/VU.js | 2 +- .../caniuse-lite/data/regions/WF.js | 2 +- .../caniuse-lite/data/regions/WS.js | 2 +- .../caniuse-lite/data/regions/YE.js | 2 +- .../caniuse-lite/data/regions/YT.js | 2 +- .../caniuse-lite/data/regions/ZA.js | 2 +- .../caniuse-lite/data/regions/ZM.js | 2 +- .../caniuse-lite/data/regions/ZW.js | 2 +- .../caniuse-lite/data/regions/alt-af.js | 2 +- .../caniuse-lite/data/regions/alt-an.js | 2 +- .../caniuse-lite/data/regions/alt-as.js | 2 +- .../caniuse-lite/data/regions/alt-eu.js | 2 +- .../caniuse-lite/data/regions/alt-na.js | 2 +- .../caniuse-lite/data/regions/alt-oc.js | 2 +- .../caniuse-lite/data/regions/alt-sa.js | 2 +- .../caniuse-lite/data/regions/alt-ww.js | 2 +- .../node_modules/caniuse-lite/package.json | 2 +- .../eslint/node_modules/doctrine/LICENSE | 177 --- .../node_modules/doctrine/LICENSE.esprima | 19 - .../node_modules/doctrine/lib/doctrine.js | 898 ------------ .../eslint/node_modules/doctrine/lib/typed.js | 1305 ----------------- .../node_modules/doctrine/lib/utility.js | 35 - .../eslint/node_modules/doctrine/package.json | 58 - .../electron-to-chromium/chromium-versions.js | 3 +- .../chromium-versions.json | 2 +- .../full-chromium-versions.js | 109 +- .../full-chromium-versions.json | 2 +- .../electron-to-chromium/full-versions.js | 71 +- .../electron-to-chromium/full-versions.json | 2 +- .../electron-to-chromium/package.json | 2 +- .../electron-to-chromium/versions.js | 6 +- .../electron-to-chromium/versions.json | 2 +- tools/node_modules/eslint/node_modules/eslint | 1 - .../dist/defaultTagOrder.cjs | 2 +- .../eslint-plugin-jsdoc/dist/exportParser.cjs | 6 +- .../dist/rules/validTypes.cjs | 3 +- .../eslint-plugin-jsdoc/dist/tagNames.cjs | 2 + .../eslint-plugin-jsdoc/package.json | 2 +- .../src/defaultTagOrder.js | 1 + .../eslint-plugin-jsdoc/src/exportParser.js | 15 +- .../src/rules/validTypes.js | 12 +- .../eslint-plugin-jsdoc/src/tagNames.js | 3 + .../eslint-plugin-markdown/lib/index.js | 91 +- .../eslint-plugin-markdown/lib/processor.js | 32 +- .../eslint-plugin-markdown/package.json | 29 +- .../eslint-scope/dist/eslint-scope.cjs | 142 +- .../node_modules/eslint-scope/lib/index.js | 3 - .../eslint-scope/lib/pattern-visitor.js | 7 +- .../eslint-scope/lib/referencer.js | 24 +- .../eslint-scope/lib/scope-manager.js | 10 +- .../node_modules/eslint-scope/lib/scope.js | 107 +- .../node_modules/eslint-scope/lib/version.js | 2 +- .../node_modules/eslint-scope/package.json | 40 +- .../eslint-visitor-keys/package.json | 4 +- .../node_modules/espree/dist/espree.cjs | 2 +- .../eslint/node_modules/espree/lib/version.js | 2 +- .../eslint/node_modules/espree/package.json | 38 +- .../node_modules/file-entry-cache/LICENSE | 2 +- .../file-entry-cache/changelog.md | 163 -- .../file-entry-cache/package.json | 62 +- .../node_modules/flat-cache/changelog.md | 360 ++--- .../node_modules/flat-cache/package.json | 26 +- .../node_modules/flat-cache/src/cache.js | 48 +- .../eslint/node_modules/flat-cache/src/del.js | 39 +- .../node_modules/flat-cache/src/utils.js | 80 +- .../eslint/node_modules/fs.realpath/LICENSE | 43 - .../eslint/node_modules/fs.realpath/index.js | 66 - .../eslint/node_modules/fs.realpath/old.js | 303 ---- .../node_modules/fs.realpath/package.json | 26 - .../eslint/node_modules/glob/LICENSE | 21 - .../eslint/node_modules/glob/common.js | 238 --- .../eslint/node_modules/glob/glob.js | 790 ---------- .../eslint/node_modules/glob/package.json | 55 - .../eslint/node_modules/glob/sync.js | 486 ------ .../eslint/node_modules/globals/globals.json | 48 +- .../eslint/node_modules/globals/package.json | 16 +- .../eslint/node_modules/inflight/LICENSE | 15 - .../eslint/node_modules/inflight/inflight.js | 54 - .../eslint/node_modules/inflight/package.json | 29 - .../eslint/node_modules/inherits/LICENSE | 16 - .../eslint/node_modules/inherits/inherits.js | 9 - .../node_modules/inherits/inherits_browser.js | 27 - .../eslint/node_modules/inherits/package.json | 29 - .../eslint/node_modules/once/LICENSE | 15 - .../eslint/node_modules/once/once.js | 42 - .../eslint/node_modules/once/package.json | 33 - .../node_modules/path-is-absolute/index.js | 20 - .../node_modules/path-is-absolute/license | 21 - .../path-is-absolute/package.json | 43 - .../node_modules/path-is-absolute/readme.md | 59 - .../eslint/node_modules/rimraf/LICENSE | 15 - .../eslint/node_modules/rimraf/bin.js | 68 - .../eslint/node_modules/rimraf/package.json | 32 - .../eslint/node_modules/rimraf/rimraf.js | 360 ----- .../eslint/node_modules/type-fest/license | 9 - .../node_modules/type-fest/package.json | 58 - .../eslint/node_modules/type-fest/readme.md | 658 --------- .../eslint/node_modules/wrappy/LICENSE | 15 - .../eslint/node_modules/wrappy/package.json | 29 - .../eslint/node_modules/wrappy/wrappy.js | 33 - tools/node_modules/eslint/package.json | 46 +- 1192 files changed, 9091 insertions(+), 17736 deletions(-) delete mode 100644 tools/node_modules/eslint/conf/config-schema.js create mode 100644 tools/node_modules/eslint/conf/ecma-version.js delete mode 100644 tools/node_modules/eslint/lib/cli-engine/formatters/checkstyle.js delete mode 100644 tools/node_modules/eslint/lib/cli-engine/formatters/compact.js delete mode 100644 tools/node_modules/eslint/lib/cli-engine/formatters/jslint-xml.js delete mode 100644 tools/node_modules/eslint/lib/cli-engine/formatters/junit.js delete mode 100644 tools/node_modules/eslint/lib/cli-engine/formatters/tap.js delete mode 100644 tools/node_modules/eslint/lib/cli-engine/formatters/unix.js delete mode 100644 tools/node_modules/eslint/lib/cli-engine/formatters/visualstudio.js delete mode 100644 tools/node_modules/eslint/lib/cli-engine/xml-escape.js delete mode 100644 tools/node_modules/eslint/lib/eslint/flat-eslint.js create mode 100644 tools/node_modules/eslint/lib/eslint/legacy-eslint.js delete mode 100644 tools/node_modules/eslint/lib/rule-tester/flat-rule-tester.js create mode 100644 tools/node_modules/eslint/lib/rules/no-useless-assignment.js delete mode 100644 tools/node_modules/eslint/lib/rules/require-jsdoc.js create mode 100644 tools/node_modules/eslint/lib/rules/utils/char-source.js delete mode 100644 tools/node_modules/eslint/lib/rules/valid-jsdoc.js delete mode 100644 tools/node_modules/eslint/lib/shared/config-validator.js delete mode 100644 tools/node_modules/eslint/lib/shared/deprecation-warnings.js delete mode 100644 tools/node_modules/eslint/lib/shared/relative-module-resolver.js create mode 100644 tools/node_modules/eslint/lib/shared/serialization.js create mode 100644 tools/node_modules/eslint/lib/shared/stats.js delete mode 120000 tools/node_modules/eslint/node_modules/.bin/eslint delete mode 120000 tools/node_modules/eslint/node_modules/.bin/rimraf delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/index.js delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/license delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/package.json delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/readme.md delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/index.js delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/index.js.flow delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/license delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/package.json delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/readme.md delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/templates.js delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/color-convert/LICENSE delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/color-convert/conversions.js delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/color-convert/index.js delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/color-convert/package.json delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/color-convert/route.js delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/color-name/LICENSE delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/color-name/index.js delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/color-name/package.json delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/escape-string-regexp/index.js delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/escape-string-regexp/license delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/escape-string-regexp/package.json delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/escape-string-regexp/readme.md delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/has-flag/index.js delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/has-flag/license delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/has-flag/package.json delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/has-flag/readme.md delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/supports-color/browser.js delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/supports-color/index.js delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/supports-color/license delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/supports-color/package.json delete mode 100644 tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/supports-color/readme.md create mode 100644 tools/node_modules/eslint/node_modules/@babel/helpers/lib/helpers/applyDecs2311.js create mode 100644 tools/node_modules/eslint/node_modules/@babel/helpers/lib/helpers/assertClassBrand.js create mode 100644 tools/node_modules/eslint/node_modules/@babel/helpers/lib/helpers/classPrivateFieldGet2.js create mode 100644 tools/node_modules/eslint/node_modules/@babel/helpers/lib/helpers/classPrivateFieldSet2.js create mode 100644 tools/node_modules/eslint/node_modules/@babel/helpers/lib/helpers/classPrivateGetter.js create mode 100644 tools/node_modules/eslint/node_modules/@babel/helpers/lib/helpers/classPrivateSetter.js create mode 100644 tools/node_modules/eslint/node_modules/@babel/helpers/lib/helpers/toSetter.js rename tools/node_modules/eslint/node_modules/{doctrine/LICENSE.closure-compiler => @eslint-community/eslint-utils/node_modules/eslint-visitor-keys/LICENSE} (99%) create mode 100644 tools/node_modules/eslint/node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys/dist/eslint-visitor-keys.cjs create mode 100644 tools/node_modules/eslint/node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys/dist/eslint-visitor-keys.d.cts create mode 100644 tools/node_modules/eslint/node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys/lib/index.js create mode 100644 tools/node_modules/eslint/node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys/lib/visitor-keys.js create mode 100644 tools/node_modules/eslint/node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys/package.json delete mode 100644 tools/node_modules/eslint/node_modules/@ungap/structured-clone/LICENSE delete mode 100644 tools/node_modules/eslint/node_modules/@ungap/structured-clone/cjs/deserialize.js delete mode 100644 tools/node_modules/eslint/node_modules/@ungap/structured-clone/cjs/index.js delete mode 100644 tools/node_modules/eslint/node_modules/@ungap/structured-clone/cjs/json.js delete mode 100644 tools/node_modules/eslint/node_modules/@ungap/structured-clone/cjs/package.json delete mode 100644 tools/node_modules/eslint/node_modules/@ungap/structured-clone/cjs/serialize.js delete mode 100644 tools/node_modules/eslint/node_modules/@ungap/structured-clone/cjs/types.js delete mode 100644 tools/node_modules/eslint/node_modules/@ungap/structured-clone/esm/deserialize.js delete mode 100644 tools/node_modules/eslint/node_modules/@ungap/structured-clone/esm/index.js delete mode 100644 tools/node_modules/eslint/node_modules/@ungap/structured-clone/esm/json.js delete mode 100644 tools/node_modules/eslint/node_modules/@ungap/structured-clone/esm/serialize.js delete mode 100644 tools/node_modules/eslint/node_modules/@ungap/structured-clone/esm/types.js delete mode 100644 tools/node_modules/eslint/node_modules/@ungap/structured-clone/package.json delete mode 100644 tools/node_modules/eslint/node_modules/@ungap/structured-clone/structured-json.js create mode 100644 tools/node_modules/eslint/node_modules/caniuse-lite/data/features/css-module-scripts.js create mode 100644 tools/node_modules/eslint/node_modules/caniuse-lite/data/features/wasm-bigint.js create mode 100644 tools/node_modules/eslint/node_modules/caniuse-lite/data/features/wasm-bulk-memory.js create mode 100644 tools/node_modules/eslint/node_modules/caniuse-lite/data/features/wasm-extended-const.js create mode 100644 tools/node_modules/eslint/node_modules/caniuse-lite/data/features/wasm-gc.js create mode 100644 tools/node_modules/eslint/node_modules/caniuse-lite/data/features/wasm-multi-memory.js create mode 100644 tools/node_modules/eslint/node_modules/caniuse-lite/data/features/wasm-multi-value.js create mode 100644 tools/node_modules/eslint/node_modules/caniuse-lite/data/features/wasm-mutable-globals.js create mode 100644 tools/node_modules/eslint/node_modules/caniuse-lite/data/features/wasm-nontrapping-fptoint.js create mode 100644 tools/node_modules/eslint/node_modules/caniuse-lite/data/features/wasm-relaxed-simd.js create mode 100644 tools/node_modules/eslint/node_modules/caniuse-lite/data/features/wasm-signext.js create mode 100644 tools/node_modules/eslint/node_modules/caniuse-lite/data/features/wasm-simd.js create mode 100644 tools/node_modules/eslint/node_modules/caniuse-lite/data/features/wasm-tail-calls.js create mode 100644 tools/node_modules/eslint/node_modules/caniuse-lite/data/features/wasm-threads.js delete mode 100644 tools/node_modules/eslint/node_modules/doctrine/LICENSE delete mode 100644 tools/node_modules/eslint/node_modules/doctrine/LICENSE.esprima delete mode 100644 tools/node_modules/eslint/node_modules/doctrine/lib/doctrine.js delete mode 100644 tools/node_modules/eslint/node_modules/doctrine/lib/typed.js delete mode 100644 tools/node_modules/eslint/node_modules/doctrine/lib/utility.js delete mode 100644 tools/node_modules/eslint/node_modules/doctrine/package.json delete mode 120000 tools/node_modules/eslint/node_modules/eslint delete mode 100644 tools/node_modules/eslint/node_modules/file-entry-cache/changelog.md delete mode 100644 tools/node_modules/eslint/node_modules/fs.realpath/LICENSE delete mode 100644 tools/node_modules/eslint/node_modules/fs.realpath/index.js delete mode 100644 tools/node_modules/eslint/node_modules/fs.realpath/old.js delete mode 100644 tools/node_modules/eslint/node_modules/fs.realpath/package.json delete mode 100644 tools/node_modules/eslint/node_modules/glob/LICENSE delete mode 100644 tools/node_modules/eslint/node_modules/glob/common.js delete mode 100644 tools/node_modules/eslint/node_modules/glob/glob.js delete mode 100644 tools/node_modules/eslint/node_modules/glob/package.json delete mode 100644 tools/node_modules/eslint/node_modules/glob/sync.js delete mode 100644 tools/node_modules/eslint/node_modules/inflight/LICENSE delete mode 100644 tools/node_modules/eslint/node_modules/inflight/inflight.js delete mode 100644 tools/node_modules/eslint/node_modules/inflight/package.json delete mode 100644 tools/node_modules/eslint/node_modules/inherits/LICENSE delete mode 100644 tools/node_modules/eslint/node_modules/inherits/inherits.js delete mode 100644 tools/node_modules/eslint/node_modules/inherits/inherits_browser.js delete mode 100644 tools/node_modules/eslint/node_modules/inherits/package.json delete mode 100644 tools/node_modules/eslint/node_modules/once/LICENSE delete mode 100644 tools/node_modules/eslint/node_modules/once/once.js delete mode 100644 tools/node_modules/eslint/node_modules/once/package.json delete mode 100644 tools/node_modules/eslint/node_modules/path-is-absolute/index.js delete mode 100644 tools/node_modules/eslint/node_modules/path-is-absolute/license delete mode 100644 tools/node_modules/eslint/node_modules/path-is-absolute/package.json delete mode 100644 tools/node_modules/eslint/node_modules/path-is-absolute/readme.md delete mode 100644 tools/node_modules/eslint/node_modules/rimraf/LICENSE delete mode 100755 tools/node_modules/eslint/node_modules/rimraf/bin.js delete mode 100644 tools/node_modules/eslint/node_modules/rimraf/package.json delete mode 100644 tools/node_modules/eslint/node_modules/rimraf/rimraf.js delete mode 100644 tools/node_modules/eslint/node_modules/type-fest/license delete mode 100644 tools/node_modules/eslint/node_modules/type-fest/package.json delete mode 100644 tools/node_modules/eslint/node_modules/type-fest/readme.md delete mode 100644 tools/node_modules/eslint/node_modules/wrappy/LICENSE delete mode 100644 tools/node_modules/eslint/node_modules/wrappy/package.json delete mode 100644 tools/node_modules/eslint/node_modules/wrappy/wrappy.js diff --git a/tools/node_modules/eslint/bin/eslint.js b/tools/node_modules/eslint/bin/eslint.js index eeb4647e70b107..b6190f667428e9 100755 --- a/tools/node_modules/eslint/bin/eslint.js +++ b/tools/node_modules/eslint/bin/eslint.js @@ -148,8 +148,21 @@ ${getErrorMessage(error)}`; return; } + // Call the config inspector if `--inspect-config` is present. + if (process.argv.includes("--inspect-config")) { + + console.warn("You can also run this command directly using 'npx @eslint/config-inspector' in the same directory as your configuration file."); + + const spawn = require("cross-spawn"); + + spawn.sync("npx", ["@eslint/config-inspector"], { encoding: "utf8", stdio: "inherit" }); + + return; + } + // Otherwise, call the CLI. - const exitCode = await require("../lib/cli").execute( + const cli = require("../lib/cli"); + const exitCode = await cli.execute( process.argv, process.argv.includes("--stdin") ? await readStdin() : null, true diff --git a/tools/node_modules/eslint/conf/config-schema.js b/tools/node_modules/eslint/conf/config-schema.js deleted file mode 100644 index b83f6578832df9..00000000000000 --- a/tools/node_modules/eslint/conf/config-schema.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - * STOP!!! DO NOT MODIFY. - * - * This file is part of the ongoing work to move the eslintrc-style config - * system into the @eslint/eslintrc package. This file needs to remain - * unchanged in order for this work to proceed. - * - * If you think you need to change this file, please contact @nzakas first. - * - * Thanks in advance for your cooperation. - */ - -/** - * @fileoverview Defines a schema for configs. - * @author Sylvan Mably - */ - -"use strict"; - -const baseConfigProperties = { - $schema: { type: "string" }, - env: { type: "object" }, - extends: { $ref: "#/definitions/stringOrStrings" }, - globals: { type: "object" }, - overrides: { - type: "array", - items: { $ref: "#/definitions/overrideConfig" }, - additionalItems: false - }, - parser: { type: ["string", "null"] }, - parserOptions: { type: "object" }, - plugins: { type: "array" }, - processor: { type: "string" }, - rules: { type: "object" }, - settings: { type: "object" }, - noInlineConfig: { type: "boolean" }, - reportUnusedDisableDirectives: { type: "boolean" }, - - ecmaFeatures: { type: "object" } // deprecated; logs a warning when used -}; - -const configSchema = { - definitions: { - stringOrStrings: { - oneOf: [ - { type: "string" }, - { - type: "array", - items: { type: "string" }, - additionalItems: false - } - ] - }, - stringOrStringsRequired: { - oneOf: [ - { type: "string" }, - { - type: "array", - items: { type: "string" }, - additionalItems: false, - minItems: 1 - } - ] - }, - - // Config at top-level. - objectConfig: { - type: "object", - properties: { - root: { type: "boolean" }, - ignorePatterns: { $ref: "#/definitions/stringOrStrings" }, - ...baseConfigProperties - }, - additionalProperties: false - }, - - // Config in `overrides`. - overrideConfig: { - type: "object", - properties: { - excludedFiles: { $ref: "#/definitions/stringOrStrings" }, - files: { $ref: "#/definitions/stringOrStringsRequired" }, - ...baseConfigProperties - }, - required: ["files"], - additionalProperties: false - } - }, - - $ref: "#/definitions/objectConfig" -}; - -module.exports = configSchema; diff --git a/tools/node_modules/eslint/conf/ecma-version.js b/tools/node_modules/eslint/conf/ecma-version.js new file mode 100644 index 00000000000000..48b1e2f7edc261 --- /dev/null +++ b/tools/node_modules/eslint/conf/ecma-version.js @@ -0,0 +1,16 @@ +/** + * @fileoverview Configuration related to ECMAScript versions + * @author Milos Djermanovic + */ + +"use strict"; + +/** + * The latest ECMAScript version supported by ESLint. + * @type {number} year-based ECMAScript version + */ +const LATEST_ECMA_VERSION = 2024; + +module.exports = { + LATEST_ECMA_VERSION +}; diff --git a/tools/node_modules/eslint/conf/rule-type-list.json b/tools/node_modules/eslint/conf/rule-type-list.json index 6ca730f34f0297..f48454bb6300db 100644 --- a/tools/node_modules/eslint/conf/rule-type-list.json +++ b/tools/node_modules/eslint/conf/rule-type-list.json @@ -23,6 +23,8 @@ { "removed": "space-in-brackets", "replacedBy": ["object-curly-spacing", "array-bracket-spacing"] }, { "removed": "space-return-throw-case", "replacedBy": ["keyword-spacing"] }, { "removed": "space-unary-word-ops", "replacedBy": ["space-unary-ops"] }, - { "removed": "spaced-line-comment", "replacedBy": ["spaced-comment"] } + { "removed": "spaced-line-comment", "replacedBy": ["spaced-comment"] }, + { "removed": "valid-jsdoc", "replacedBy": [] }, + { "removed": "require-jsdoc", "replacedBy": [] } ] } diff --git a/tools/node_modules/eslint/lib/api.js b/tools/node_modules/eslint/lib/api.js index cbaac8fef1bb19..ab0ec2fcd317f9 100644 --- a/tools/node_modules/eslint/lib/api.js +++ b/tools/node_modules/eslint/lib/api.js @@ -9,8 +9,8 @@ // Requirements //----------------------------------------------------------------------------- -const { ESLint, FlatESLint } = require("./eslint"); -const { shouldUseFlatConfig } = require("./eslint/flat-eslint"); +const { ESLint, shouldUseFlatConfig } = require("./eslint/eslint"); +const { LegacyESLint } = require("./eslint/legacy-eslint"); const { Linter } = require("./linter"); const { RuleTester } = require("./rule-tester"); const { SourceCode } = require("./source-code"); @@ -23,22 +23,18 @@ const { SourceCode } = require("./source-code"); * Loads the correct ESLint constructor given the options. * @param {Object} [options] The options object * @param {boolean} [options.useFlatConfig] Whether or not to use a flat config - * @param {string} [options.cwd] The current working directory * @returns {Promise} The ESLint constructor */ -async function loadESLint({ useFlatConfig, cwd = process.cwd() } = {}) { +async function loadESLint({ useFlatConfig } = {}) { /* - * Note: The v9.x version of this function doesn't have a cwd option - * because it's not used. It's only used in the v8.x version of this - * function. + * Note: The v8.x version of this function also accepted a `cwd` option, but + * it is not used in this implementation so we silently ignore it. */ - const shouldESLintUseFlatConfig = typeof useFlatConfig === "boolean" - ? useFlatConfig - : await shouldUseFlatConfig({ cwd }); + const shouldESLintUseFlatConfig = useFlatConfig ?? (await shouldUseFlatConfig()); - return shouldESLintUseFlatConfig ? FlatESLint : ESLint; + return shouldESLintUseFlatConfig ? ESLint : LegacyESLint; } //----------------------------------------------------------------------------- diff --git a/tools/node_modules/eslint/lib/cli-engine/cli-engine.js b/tools/node_modules/eslint/lib/cli-engine/cli-engine.js index 49c8902c161ce1..4fe54843e8847c 100644 --- a/tools/node_modules/eslint/lib/cli-engine/cli-engine.js +++ b/tools/node_modules/eslint/lib/cli-engine/cli-engine.js @@ -41,6 +41,17 @@ const hash = require("./hash"); const LintResultCache = require("./lint-result-cache"); const debug = require("debug")("eslint:cli-engine"); +const removedFormatters = new Set([ + "checkstyle", + "codeframe", + "compact", + "jslint-xml", + "junit", + "table", + "tap", + "unix", + "visualstudio" +]); const validFixTypes = new Set(["directive", "problem", "suggestion", "layout"]); //------------------------------------------------------------------------------ @@ -639,7 +650,7 @@ class CLIEngine { }); const lintResultCache = options.cache ? new LintResultCache(cacheFilePath, options.cacheStrategy) : null; - const linter = new Linter({ cwd: options.cwd }); + const linter = new Linter({ cwd: options.cwd, configType: "eslintrc" }); /** @type {ConfigArray[]} */ const lastConfigArrays = [configArrayFactory.getConfigArrayForFile()]; @@ -721,7 +732,7 @@ class CLIEngine { * @returns {void} */ static outputFixes(report) { - report.results.filter(result => Object.prototype.hasOwnProperty.call(result, "output")).forEach(result => { + report.results.filter(result => Object.hasOwn(result, "output")).forEach(result => { fs.writeFileSync(result.filePath, result.output); }); } @@ -1047,7 +1058,7 @@ class CLIEngine { try { return require(formatterPath); } catch (ex) { - if (format === "table" || format === "codeframe") { + if (removedFormatters.has(format)) { ex.message = `The ${format} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${format}\``; } else { ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`; diff --git a/tools/node_modules/eslint/lib/cli-engine/formatters/checkstyle.js b/tools/node_modules/eslint/lib/cli-engine/formatters/checkstyle.js deleted file mode 100644 index f19b6fc0957e5d..00000000000000 --- a/tools/node_modules/eslint/lib/cli-engine/formatters/checkstyle.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @fileoverview CheckStyle XML reporter - * @author Ian Christian Myers - */ -"use strict"; - -const xmlEscape = require("../xml-escape"); - -//------------------------------------------------------------------------------ -// Helper Functions -//------------------------------------------------------------------------------ - -/** - * Returns the severity of warning or error - * @param {Object} message message object to examine - * @returns {string} severity level - * @private - */ -function getMessageType(message) { - if (message.fatal || message.severity === 2) { - return "error"; - } - return "warning"; - -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = function(results) { - - let output = ""; - - output += ""; - output += ""; - - results.forEach(result => { - const messages = result.messages; - - output += ``; - - messages.forEach(message => { - output += [ - `` - ].join(" "); - }); - - output += ""; - - }); - - output += ""; - - return output; -}; diff --git a/tools/node_modules/eslint/lib/cli-engine/formatters/compact.js b/tools/node_modules/eslint/lib/cli-engine/formatters/compact.js deleted file mode 100644 index 2b540bde2361c1..00000000000000 --- a/tools/node_modules/eslint/lib/cli-engine/formatters/compact.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @fileoverview Compact reporter - * @author Nicholas C. Zakas - */ -"use strict"; - -//------------------------------------------------------------------------------ -// Helper Functions -//------------------------------------------------------------------------------ - -/** - * Returns the severity of warning or error - * @param {Object} message message object to examine - * @returns {string} severity level - * @private - */ -function getMessageType(message) { - if (message.fatal || message.severity === 2) { - return "Error"; - } - return "Warning"; - -} - - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = function(results) { - - let output = "", - total = 0; - - results.forEach(result => { - - const messages = result.messages; - - total += messages.length; - - messages.forEach(message => { - - output += `${result.filePath}: `; - output += `line ${message.line || 0}`; - output += `, col ${message.column || 0}`; - output += `, ${getMessageType(message)}`; - output += ` - ${message.message}`; - output += message.ruleId ? ` (${message.ruleId})` : ""; - output += "\n"; - - }); - - }); - - if (total > 0) { - output += `\n${total} problem${total !== 1 ? "s" : ""}`; - } - - return output; -}; diff --git a/tools/node_modules/eslint/lib/cli-engine/formatters/formatters-meta.json b/tools/node_modules/eslint/lib/cli-engine/formatters/formatters-meta.json index bcd35e50428877..4cc61ae3c62d30 100644 --- a/tools/node_modules/eslint/lib/cli-engine/formatters/formatters-meta.json +++ b/tools/node_modules/eslint/lib/cli-engine/formatters/formatters-meta.json @@ -1,20 +1,8 @@ [ - { - "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." @@ -23,24 +11,8 @@ "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/tools/node_modules/eslint/lib/cli-engine/formatters/jslint-xml.js b/tools/node_modules/eslint/lib/cli-engine/formatters/jslint-xml.js deleted file mode 100644 index 0ca1cbaed1a3cc..00000000000000 --- a/tools/node_modules/eslint/lib/cli-engine/formatters/jslint-xml.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @fileoverview JSLint XML reporter - * @author Ian Christian Myers - */ -"use strict"; - -const xmlEscape = require("../xml-escape"); - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = function(results) { - - let output = ""; - - output += ""; - output += ""; - - results.forEach(result => { - const messages = result.messages; - - output += ``; - - messages.forEach(message => { - output += [ - `` - ].join(" "); - }); - - output += ""; - - }); - - output += ""; - - return output; -}; diff --git a/tools/node_modules/eslint/lib/cli-engine/formatters/junit.js b/tools/node_modules/eslint/lib/cli-engine/formatters/junit.js deleted file mode 100644 index a994b4b1980eea..00000000000000 --- a/tools/node_modules/eslint/lib/cli-engine/formatters/junit.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @fileoverview jUnit Reporter - * @author Jamund Ferguson - */ -"use strict"; - -const xmlEscape = require("../xml-escape"); -const path = require("path"); - -//------------------------------------------------------------------------------ -// Helper Functions -//------------------------------------------------------------------------------ - -/** - * Returns the severity of warning or error - * @param {Object} message message object to examine - * @returns {string} severity level - * @private - */ -function getMessageType(message) { - if (message.fatal || message.severity === 2) { - return "Error"; - } - return "Warning"; - -} - -/** - * Returns a full file path without extension - * @param {string} filePath input file path - * @returns {string} file path without extension - * @private - */ -function pathWithoutExt(filePath) { - return path.join(path.dirname(filePath), path.basename(filePath, path.extname(filePath))); -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = function(results) { - - let output = ""; - - output += "\n"; - output += "\n"; - - results.forEach(result => { - - const messages = result.messages; - const classname = pathWithoutExt(result.filePath); - - if (messages.length > 0) { - output += `\n`; - messages.forEach(message => { - const type = message.fatal ? "error" : "failure"; - - output += ``; - output += `<${type} message="${xmlEscape(message.message || "")}">`; - output += ""; - output += ``; - output += "\n"; - }); - output += "\n"; - } else { - output += `\n`; - output += `\n`; - output += "\n"; - } - - }); - - output += "\n"; - - return output; -}; diff --git a/tools/node_modules/eslint/lib/cli-engine/formatters/tap.js b/tools/node_modules/eslint/lib/cli-engine/formatters/tap.js deleted file mode 100644 index e4148a3b3929a0..00000000000000 --- a/tools/node_modules/eslint/lib/cli-engine/formatters/tap.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @fileoverview TAP reporter - * @author Jonathan Kingston - */ -"use strict"; - -const yaml = require("js-yaml"); - -//------------------------------------------------------------------------------ -// Helper Functions -//------------------------------------------------------------------------------ - -/** - * Returns a canonical error level string based upon the error message passed in. - * @param {Object} message Individual error message provided by eslint - * @returns {string} Error level string - */ -function getMessageType(message) { - if (message.fatal || message.severity === 2) { - return "error"; - } - return "warning"; -} - -/** - * Takes in a JavaScript object and outputs a TAP diagnostics string - * @param {Object} diagnostic JavaScript object to be embedded as YAML into output. - * @returns {string} diagnostics string with YAML embedded - TAP version 13 compliant - */ -function outputDiagnostics(diagnostic) { - const prefix = " "; - let output = `${prefix}---\n`; - - output += prefix + yaml.dump(diagnostic).split("\n").join(`\n${prefix}`); - output += "...\n"; - return output; -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = function(results) { - let output = `TAP version 13\n1..${results.length}\n`; - - results.forEach((result, id) => { - const messages = result.messages; - let testResult = "ok"; - let diagnostics = {}; - - if (messages.length > 0) { - messages.forEach(message => { - const severity = getMessageType(message); - const diagnostic = { - message: message.message, - severity, - data: { - line: message.line || 0, - column: message.column || 0, - ruleId: message.ruleId || "" - } - }; - - // This ensures a warning message is not flagged as error - if (severity === "error") { - testResult = "not ok"; - } - - /* - * If we have multiple messages place them under a messages key - * The first error will be logged as message key - * This is to adhere to TAP 13 loosely defined specification of having a message key - */ - if ("message" in diagnostics) { - if (typeof diagnostics.messages === "undefined") { - diagnostics.messages = []; - } - diagnostics.messages.push(diagnostic); - } else { - diagnostics = diagnostic; - } - }); - } - - output += `${testResult} ${id + 1} - ${result.filePath}\n`; - - // If we have an error include diagnostics - if (messages.length > 0) { - output += outputDiagnostics(diagnostics); - } - - }); - - return output; -}; diff --git a/tools/node_modules/eslint/lib/cli-engine/formatters/unix.js b/tools/node_modules/eslint/lib/cli-engine/formatters/unix.js deleted file mode 100644 index c6c4ebbdb9f4cc..00000000000000 --- a/tools/node_modules/eslint/lib/cli-engine/formatters/unix.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @fileoverview unix-style formatter. - * @author oshi-shinobu - */ -"use strict"; - -//------------------------------------------------------------------------------ -// Helper Functions -//------------------------------------------------------------------------------ - -/** - * Returns a canonical error level string based upon the error message passed in. - * @param {Object} message Individual error message provided by eslint - * @returns {string} Error level string - */ -function getMessageType(message) { - if (message.fatal || message.severity === 2) { - return "Error"; - } - return "Warning"; - -} - - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = function(results) { - - let output = "", - total = 0; - - results.forEach(result => { - - const messages = result.messages; - - total += messages.length; - - messages.forEach(message => { - - output += `${result.filePath}:`; - output += `${message.line || 0}:`; - output += `${message.column || 0}:`; - output += ` ${message.message} `; - output += `[${getMessageType(message)}${message.ruleId ? `/${message.ruleId}` : ""}]`; - output += "\n"; - - }); - - }); - - if (total > 0) { - output += `\n${total} problem${total !== 1 ? "s" : ""}`; - } - - return output; -}; diff --git a/tools/node_modules/eslint/lib/cli-engine/formatters/visualstudio.js b/tools/node_modules/eslint/lib/cli-engine/formatters/visualstudio.js deleted file mode 100644 index 0d49431db87926..00000000000000 --- a/tools/node_modules/eslint/lib/cli-engine/formatters/visualstudio.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * @fileoverview Visual Studio compatible formatter - * @author Ronald Pijnacker - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Helper Functions -//------------------------------------------------------------------------------ - -/** - * Returns the severity of warning or error - * @param {Object} message message object to examine - * @returns {string} severity level - * @private - */ -function getMessageType(message) { - if (message.fatal || message.severity === 2) { - return "error"; - } - return "warning"; - -} - - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = function(results) { - - let output = "", - total = 0; - - results.forEach(result => { - - const messages = result.messages; - - total += messages.length; - - messages.forEach(message => { - - output += result.filePath; - output += `(${message.line || 0}`; - output += message.column ? `,${message.column}` : ""; - output += `): ${getMessageType(message)}`; - output += message.ruleId ? ` ${message.ruleId}` : ""; - output += ` : ${message.message}`; - output += "\n"; - - }); - - }); - - if (total === 0) { - output += "no problems"; - } else { - output += `\n${total} problem${total !== 1 ? "s" : ""}`; - } - - return output; -}; diff --git a/tools/node_modules/eslint/lib/cli-engine/lint-result-cache.js b/tools/node_modules/eslint/lib/cli-engine/lint-result-cache.js index 97d2ee40b39955..fcdf4ca1e5d44d 100644 --- a/tools/node_modules/eslint/lib/cli-engine/lint-result-cache.js +++ b/tools/node_modules/eslint/lib/cli-engine/lint-result-cache.js @@ -164,7 +164,7 @@ class LintResultCache { * @returns {void} */ setCachedLintResults(filePath, config, result) { - if (result && Object.prototype.hasOwnProperty.call(result, "output")) { + if (result && Object.hasOwn(result, "output")) { return; } @@ -181,7 +181,7 @@ class LintResultCache { * In `getCachedLintResults`, if source is explicitly null, we will * read the file from the filesystem to set the value again. */ - if (Object.prototype.hasOwnProperty.call(resultToSerialize, "source")) { + if (Object.hasOwn(resultToSerialize, "source")) { resultToSerialize.source = null; } diff --git a/tools/node_modules/eslint/lib/cli-engine/xml-escape.js b/tools/node_modules/eslint/lib/cli-engine/xml-escape.js deleted file mode 100644 index 2e52dbaac02235..00000000000000 --- a/tools/node_modules/eslint/lib/cli-engine/xml-escape.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @fileoverview XML character escaper - * @author George Chung - */ -"use strict"; - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -/** - * Returns the escaped value for a character - * @param {string} s string to examine - * @returns {string} severity level - * @private - */ -module.exports = function(s) { - return (`${s}`).replace(/[<>&"'\x00-\x1F\x7F\u0080-\uFFFF]/gu, c => { // eslint-disable-line no-control-regex -- Converting controls to entities - switch (c) { - case "<": - return "<"; - case ">": - return ">"; - case "&": - return "&"; - case "\"": - return """; - case "'": - return "'"; - default: - return `&#${c.charCodeAt(0)};`; - } - }); -}; diff --git a/tools/node_modules/eslint/lib/cli.js b/tools/node_modules/eslint/lib/cli.js index 1d909ec1cf2009..24d72d6e21a862 100644 --- a/tools/node_modules/eslint/lib/cli.js +++ b/tools/node_modules/eslint/lib/cli.js @@ -18,8 +18,8 @@ const fs = require("fs"), path = require("path"), { promisify } = require("util"), - { ESLint } = require("./eslint"), - { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint"), + { LegacyESLint } = require("./eslint"), + { ESLint, shouldUseFlatConfig } = require("./eslint/eslint"), createCLIOptions = require("./options"), log = require("./shared/logging"), RuntimeInfo = require("./shared/runtime-info"), @@ -37,6 +37,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").Plugin} Plugin */ /** @typedef {import("./shared/types").ResultsMeta} ResultsMeta */ //------------------------------------------------------------------------------ @@ -47,6 +48,32 @@ const mkdir = promisify(fs.mkdir); const stat = promisify(fs.stat); const writeFile = promisify(fs.writeFile); +/** + * Loads plugins with the specified names. + * @param {{ "import": (name: string) => Promise }} importer An object with an `import` method called once for each plugin. + * @param {string[]} pluginNames The names of the plugins to be loaded, with or without the "eslint-plugin-" prefix. + * @returns {Promise>} A mapping of plugin short names to implementations. + */ +async function loadPlugins(importer, pluginNames) { + const plugins = {}; + + await Promise.all(pluginNames.map(async pluginName => { + + const longName = naming.normalizePackageName(pluginName, "eslint-plugin"); + const module = await importer.import(longName); + + if (!("default" in module)) { + throw new Error(`"${longName}" cannot be used with the \`--plugin\` option because its default module does not provide a \`default\` export`); + } + + const shortName = naming.getShorthandName(pluginName, "eslint-plugin"); + + plugins[shortName] = module.default; + })); + + return plugins; +} + /** * Predicate function for whether or not to apply fixes in quiet mode. * If a message is a warning, do not apply a fix. @@ -58,6 +85,16 @@ function quietFixPredicate(message) { return message.severity === 2; } +/** + * Predicate function for whether or not to run a rule in quiet mode. + * If a rule is set to warning, do not run it. + * @param {{ ruleId: string; severity: number; }} rule The rule id and severity. + * @returns {boolean} True if the lint rule should run, false otherwise. + */ +function quietRuleFilter(rule) { + return rule.severity === 2; +} + /** * Translates the CLI options into the options expected by the ESLint constructor. * @param {ParsedCLIOptions} cliOptions The CLI options to translate. @@ -94,7 +131,10 @@ async function translateOptions({ resolvePluginsRelativeTo, rule, rulesdir, - warnIgnored + stats, + warnIgnored, + passOnNoPatterns, + maxWarnings }, configType) { let overrideConfig, overrideConfigFile; @@ -140,17 +180,7 @@ async function translateOptions({ } if (plugin) { - const plugins = {}; - - for (const pluginName of plugin) { - - const shortName = naming.getShorthandName(pluginName, "eslint-plugin"); - const longName = naming.normalizePackageName(pluginName, "eslint-plugin"); - - plugins[shortName] = await importer.import(longName); - } - - overrideConfig[0].plugins = plugins; + overrideConfig[0].plugins = await loadPlugins(importer, plugin); } } else { @@ -187,12 +217,20 @@ async function translateOptions({ fixTypes: fixType, ignore, overrideConfig, - overrideConfigFile + overrideConfigFile, + passOnNoPatterns }; if (configType === "flat") { options.ignorePatterns = ignorePattern; + options.stats = stats; options.warnIgnored = warnIgnored; + + /* + * For performance reasons rules not marked as 'error' are filtered out in quiet mode. As maxWarnings + * requires rules set to 'warn' to be run, we only filter out 'warn' rules if maxWarnings is not specified. + */ + options.ruleFilter = quiet && maxWarnings === -1 ? quietRuleFilter : () => true; } else { options.resolvePluginsRelativeTo = resolvePluginsRelativeTo; options.rulePaths = rulesdir; @@ -266,25 +304,23 @@ async function printResults(engine, results, format, outputFile, resultsMeta) { const output = await formatter.format(results, resultsMeta); - if (output) { - if (outputFile) { - const filePath = path.resolve(process.cwd(), outputFile); + if (outputFile) { + const filePath = path.resolve(process.cwd(), outputFile); - if (await isDirectory(filePath)) { - log.error("Cannot write to output file path, it is a directory: %s", outputFile); - return false; - } + if (await isDirectory(filePath)) { + log.error("Cannot write to output file path, it is a directory: %s", outputFile); + return false; + } - try { - await mkdir(path.dirname(filePath), { recursive: true }); - await writeFile(filePath, output); - } catch (ex) { - log.error("There was a problem writing the output file:\n%s", ex); - return false; - } - } else { - log.info(output); + try { + await mkdir(path.dirname(filePath), { recursive: true }); + await writeFile(filePath, output); + } catch (ex) { + log.error("There was a problem writing the output file:\n%s", ex); + return false; } + } else if (output) { + log.info(output); } return true; @@ -304,10 +340,10 @@ const cli = { * Executes the CLI based on an array of arguments that is passed in. * @param {string|Array|Object} args The arguments to process. * @param {string} [text] The text to lint (used for TTY). - * @param {boolean} [allowFlatConfig] Whether or not to allow flat config. + * @param {boolean} [allowFlatConfig=true] Whether or not to allow flat config. * @returns {Promise} The exit code for the operation. */ - async execute(args, text, allowFlatConfig) { + async execute(args, text, allowFlatConfig = true) { if (Array.isArray(args)) { debug("CLI args: %o", args.slice(2)); } @@ -323,6 +359,10 @@ const cli = { debug("Using flat config?", usingFlatConfig); + if (allowFlatConfig && !usingFlatConfig) { + process.emitWarning("You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details.", "ESLintRCWarning"); + } + const CLIOptions = createCLIOptions(usingFlatConfig); /** @type {ParsedCLIOptions} */ @@ -376,8 +416,8 @@ const cli = { } const engine = usingFlatConfig - ? new FlatESLint(await translateOptions(options, "flat")) - : new ESLint(await translateOptions(options)); + ? new ESLint(await translateOptions(options, "flat")) + : new LegacyESLint(await translateOptions(options)); const fileConfig = await engine.calculateConfigForFile(options.printConfig); @@ -405,7 +445,7 @@ const cli = { return 2; } - const ActiveESLint = usingFlatConfig ? FlatESLint : ESLint; + const ActiveESLint = usingFlatConfig ? ESLint : LegacyESLint; const engine = new ActiveESLint(await translateOptions(options, usingFlatConfig ? "flat" : "eslintrc")); let results; diff --git a/tools/node_modules/eslint/lib/config/default-config.js b/tools/node_modules/eslint/lib/config/default-config.js index 8a6ff820057de8..670096f76dd518 100644 --- a/tools/node_modules/eslint/lib/config/default-config.js +++ b/tools/node_modules/eslint/lib/config/default-config.js @@ -42,6 +42,9 @@ exports.defaultConfig = [ ecmaVersion: "latest", parser: require("espree"), parserOptions: {} + }, + linterOptions: { + reportUnusedDisableDirectives: 1 } }, diff --git a/tools/node_modules/eslint/lib/config/flat-config-array.js b/tools/node_modules/eslint/lib/config/flat-config-array.js index 689dc429f5021f..6103374dcd69a5 100644 --- a/tools/node_modules/eslint/lib/config/flat-config-array.js +++ b/tools/node_modules/eslint/lib/config/flat-config-array.js @@ -13,7 +13,6 @@ const { ConfigArray, ConfigArraySymbol } = require("@humanwhocodes/config-array" const { flatConfigSchema } = require("./flat-config-schema"); const { RuleValidator } = require("./rule-validator"); const { defaultConfig } = require("./default-config"); -const jsPlugin = require("@eslint/js"); //----------------------------------------------------------------------------- // Helpers @@ -134,25 +133,6 @@ class FlatConfigArray extends ConfigArray { * @returns {Object} The preprocessed config. */ [ConfigArraySymbol.preprocessConfig](config) { - if (config === "eslint:recommended") { - - // 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") { - - // 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; - } /* * If `shouldIgnore` is false, we remove any ignore patterns specified diff --git a/tools/node_modules/eslint/lib/config/flat-config-helpers.js b/tools/node_modules/eslint/lib/config/flat-config-helpers.js index e00c56434cd5b4..0280255932c1ce 100644 --- a/tools/node_modules/eslint/lib/config/flat-config-helpers.js +++ b/tools/node_modules/eslint/lib/config/flat-config-helpers.js @@ -5,6 +5,23 @@ "use strict"; +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** @typedef {import("../shared/types").Rule} Rule */ + +//------------------------------------------------------------------------------ +// Private Members +//------------------------------------------------------------------------------ + +// JSON schema that disallows passing any options +const noOptionsSchema = Object.freeze({ + type: "array", + minItems: 0, + maxItems: 0 +}); + //----------------------------------------------------------------------------- // Functions //----------------------------------------------------------------------------- @@ -52,32 +69,39 @@ function getRuleFromConfig(ruleId, config) { const { pluginName, ruleName } = parseRuleId(ruleId); const plugin = config.plugins && config.plugins[pluginName]; - let rule = plugin && plugin.rules && plugin.rules[ruleName]; - - - // normalize function rules into objects - if (rule && typeof rule === "function") { - rule = { - create: rule - }; - } + const rule = plugin && plugin.rules && plugin.rules[ruleName]; return rule; } /** * Gets a complete options schema for a rule. - * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object - * @returns {Object} JSON Schema for the rule's options. + * @param {Rule} rule A rule object + * @throws {TypeError} If `meta.schema` is specified but is not an array, object or `false`. + * @returns {Object|null} JSON Schema for the rule's options. `null` if `meta.schema` is `false`. */ function getRuleOptionsSchema(rule) { - if (!rule) { + if (!rule.meta) { + return { ...noOptionsSchema }; // default if `meta.schema` is not specified + } + + const schema = rule.meta.schema; + + if (typeof schema === "undefined") { + return { ...noOptionsSchema }; // default if `meta.schema` is not specified + } + + // `schema:false` is an allowed explicit opt-out of options validation for the rule + if (schema === false) { return null; } - const schema = rule.schema || rule.meta && rule.meta.schema; + if (typeof schema !== "object" || schema === null) { + throw new TypeError("Rule's `meta.schema` must be an array or object"); + } + // ESLint-specific array form needs to be converted into a valid JSON Schema definition if (Array.isArray(schema)) { if (schema.length) { return { @@ -87,16 +111,13 @@ function getRuleOptionsSchema(rule) { maxItems: schema.length }; } - return { - type: "array", - minItems: 0, - maxItems: 0 - }; + // `schema:[]` is an explicit way to specify that the rule does not accept any options + return { ...noOptionsSchema }; } - // Given a full schema, leave it alone - return schema || null; + // `schema:` is assumed to be a valid JSON Schema definition + return schema; } diff --git a/tools/node_modules/eslint/lib/config/flat-config-schema.js b/tools/node_modules/eslint/lib/config/flat-config-schema.js index 6b64319c8fcd85..ce86229c4292ff 100644 --- a/tools/node_modules/eslint/lib/config/flat-config-schema.js +++ b/tools/node_modules/eslint/lib/config/flat-config-schema.js @@ -9,11 +9,6 @@ // Requirements //----------------------------------------------------------------------------- -/* - * Note: This can be removed in ESLint v9 because structuredClone is available globally - * starting in Node.js v17. - */ -const structuredClone = require("@ungap/structured-clone").default; const { normalizeSeverityToNumber } = require("../shared/severity"); //----------------------------------------------------------------------------- @@ -593,6 +588,5 @@ const flatConfigSchema = { module.exports = { flatConfigSchema, - assertIsRuleSeverity, - assertIsRuleOptions + assertIsRuleSeverity }; diff --git a/tools/node_modules/eslint/lib/config/rule-validator.js b/tools/node_modules/eslint/lib/config/rule-validator.js index eee5b40bd07b0d..3b4ea6122cbcaa 100644 --- a/tools/node_modules/eslint/lib/config/rule-validator.js +++ b/tools/node_modules/eslint/lib/config/rule-validator.js @@ -66,6 +66,25 @@ function throwRuleNotFoundError({ pluginName, ruleName }, config) { throw new TypeError(errorMessage); } +/** + * The error type when a rule has an invalid `meta.schema`. + */ +class InvalidRuleOptionsSchemaError extends Error { + + /** + * Creates a new instance. + * @param {string} ruleId Id of the rule that has an invalid `meta.schema`. + * @param {Error} processingError Error caught while processing the `meta.schema`. + */ + constructor(ruleId, processingError) { + super( + `Error while processing options validation schema of rule '${ruleId}': ${processingError.message}`, + { cause: processingError } + ); + this.code = "ESLINT_INVALID_RULE_OPTIONS_SCHEMA"; + } +} + //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- @@ -130,10 +149,14 @@ class RuleValidator { // Precompile and cache validator the first time if (!this.validators.has(rule)) { - const schema = getRuleOptionsSchema(rule); - - if (schema) { - this.validators.set(rule, ajv.compile(schema)); + try { + const schema = getRuleOptionsSchema(rule); + + if (schema) { + this.validators.set(rule, ajv.compile(schema)); + } + } catch (err) { + throw new InvalidRuleOptionsSchemaError(ruleId, err); } } @@ -144,9 +167,22 @@ class RuleValidator { validateRule(ruleOptions.slice(1)); if (validateRule.errors) { - throw new Error(`Key "rules": Key "${ruleId}": ${ + throw new Error(`Key "rules": Key "${ruleId}":\n${ validateRule.errors.map( - error => `\tValue ${JSON.stringify(error.data)} ${error.message}.\n` + error => { + if ( + error.keyword === "additionalProperties" && + error.schema === false && + typeof error.parentSchema?.properties === "object" && + typeof error.params?.additionalProperty === "string" + ) { + const expectedProperties = Object.keys(error.parentSchema.properties).map(property => `"${property}"`); + + return `\tValue ${JSON.stringify(error.data)} ${error.message}.\n\t\tUnexpected property "${error.params.additionalProperty}". Expected properties: ${expectedProperties.join(", ")}.\n`; + } + + return `\tValue ${JSON.stringify(error.data)} ${error.message}.\n`; + } ).join("") }`); } diff --git a/tools/node_modules/eslint/lib/eslint/eslint-helpers.js b/tools/node_modules/eslint/lib/eslint/eslint-helpers.js index 685826ac69cfc9..44a5a253cd6764 100644 --- a/tools/node_modules/eslint/lib/eslint/eslint-helpers.js +++ b/tools/node_modules/eslint/lib/eslint/eslint-helpers.js @@ -105,20 +105,30 @@ class AllFilesIgnoredError extends Error { /** * Check if a given value is a non-empty string or not. - * @param {any} x The value to check. - * @returns {boolean} `true` if `x` is a non-empty string. + * @param {any} value The value to check. + * @returns {boolean} `true` if `value` is a non-empty string. */ -function isNonEmptyString(x) { - return typeof x === "string" && x.trim() !== ""; +function isNonEmptyString(value) { + return typeof value === "string" && value.trim() !== ""; } /** * Check if a given value is an array of non-empty strings or not. - * @param {any} x The value to check. - * @returns {boolean} `true` if `x` is an array of non-empty strings. + * @param {any} value The value to check. + * @returns {boolean} `true` if `value` is an array of non-empty strings. */ -function isArrayOfNonEmptyString(x) { - return Array.isArray(x) && x.every(isNonEmptyString); +function isArrayOfNonEmptyString(value) { + return Array.isArray(value) && value.length && value.every(isNonEmptyString); +} + +/** + * Check if a given value is an empty array or an array of non-empty strings. + * @param {any} value The value to check. + * @returns {boolean} `true` if `value` is an empty array or an array of non-empty + * strings. + */ +function isEmptyArrayOrArrayOfNonEmptyString(value) { + return Array.isArray(value) && value.every(isNonEmptyString); } //----------------------------------------------------------------------------- @@ -655,9 +665,9 @@ class ESLintInvalidOptionsError extends Error { /** * Validates and normalizes options for the wrapped CLIEngine instance. - * @param {FlatESLintOptions} options The options to process. + * @param {ESLintOptions} options The options to process. * @throws {ESLintInvalidOptionsError} If of any of a variety of type errors. - * @returns {FlatESLintOptions} The normalized options. + * @returns {ESLintOptions} The normalized options. */ function processOptions({ allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored. @@ -675,7 +685,10 @@ function processOptions({ overrideConfig = null, overrideConfigFile = null, plugins = {}, + stats = false, warnIgnored = true, + passOnNoPatterns = false, + ruleFilter = () => true, ...unknownOptions }) { const errors = []; @@ -759,7 +772,7 @@ function processOptions({ if (typeof ignore !== "boolean") { errors.push("'ignore' must be a boolean."); } - if (!isArrayOfNonEmptyString(ignorePatterns) && ignorePatterns !== null) { + if (!isEmptyArrayOrArrayOfNonEmptyString(ignorePatterns) && ignorePatterns !== null) { errors.push("'ignorePatterns' must be an array of non-empty strings or null."); } if (typeof overrideConfig !== "object") { @@ -768,6 +781,9 @@ function processOptions({ if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null && overrideConfigFile !== true) { errors.push("'overrideConfigFile' must be a non-empty string, null, or true."); } + if (typeof passOnNoPatterns !== "boolean") { + errors.push("'passOnNoPatterns' must be a boolean."); + } if (typeof plugins !== "object") { errors.push("'plugins' must be an object or null."); } else if (plugins !== null && Object.keys(plugins).includes("")) { @@ -776,9 +792,15 @@ function processOptions({ if (Array.isArray(plugins)) { errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead."); } + if (typeof stats !== "boolean") { + errors.push("'stats' must be a boolean."); + } if (typeof warnIgnored !== "boolean") { errors.push("'warnIgnored' must be a boolean."); } + if (typeof ruleFilter !== "function") { + errors.push("'ruleFilter' must be a function."); + } if (errors.length > 0) { throw new ESLintInvalidOptionsError(errors); } @@ -800,7 +822,10 @@ function processOptions({ globInputPaths, ignore, ignorePatterns, - warnIgnored + stats, + passOnNoPatterns, + warnIgnored, + ruleFilter }; } @@ -887,7 +912,6 @@ function getCacheFile(cacheFile, cwd) { //----------------------------------------------------------------------------- module.exports = { - isGlobPattern, findFiles, isNonEmptyString, diff --git a/tools/node_modules/eslint/lib/eslint/eslint.js b/tools/node_modules/eslint/lib/eslint/eslint.js index 7085d5a4de2bac..b4c38503a6eb5a 100644 --- a/tools/node_modules/eslint/lib/eslint/eslint.js +++ b/tools/node_modules/eslint/lib/eslint/eslint.js @@ -1,7 +1,6 @@ /** - * @fileoverview Main API Class - * @author Kai Cataldo - * @author Toru Nagashima + * @fileoverview Main class using flat config + * @author Nicholas C. Zakas */ "use strict"; @@ -10,39 +9,60 @@ // Requirements //------------------------------------------------------------------------------ +// Note: Node.js 12 does not support fs/promises. +const fs = require("fs").promises; +const { existsSync } = require("fs"); const path = require("path"); -const fs = require("fs"); -const { promisify } = require("util"); -const { CLIEngine, getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); -const BuiltinRules = require("../rules"); +const findUp = require("find-up"); +const { version } = require("../../package.json"); +const { Linter } = require("../linter"); +const { getRuleFromConfig } = require("../config/flat-config-helpers"); const { Legacy: { ConfigOps: { getRuleSeverity - } + }, + ModuleResolver, + naming } } = require("@eslint/eslintrc"); -const { version } = require("../../package.json"); + +const { + findFiles, + getCacheFile, + + isNonEmptyString, + isArrayOfNonEmptyString, + + createIgnoreResult, + isErrorMessage, + + processOptions +} = require("./eslint-helpers"); +const { pathToFileURL } = require("url"); +const { FlatConfigArray } = require("../config/flat-config-array"); +const LintResultCache = require("../cli-engine/lint-result-cache"); + +/* + * This is necessary to allow overwriting writeFile for testing purposes. + * We can just use fs/promises once we drop Node.js 12 support. + */ //------------------------------------------------------------------------------ // Typedefs //------------------------------------------------------------------------------ -/** @typedef {import("../cli-engine/cli-engine").LintReport} CLIEngineLintReport */ -/** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */ +// For VSCode IntelliSense /** @typedef {import("../shared/types").ConfigData} ConfigData */ +/** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */ /** @typedef {import("../shared/types").LintMessage} LintMessage */ -/** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */ -/** @typedef {import("../shared/types").Plugin} Plugin */ -/** @typedef {import("../shared/types").Rule} Rule */ /** @typedef {import("../shared/types").LintResult} LintResult */ +/** @typedef {import("../shared/types").ParserOptions} ParserOptions */ +/** @typedef {import("../shared/types").Plugin} Plugin */ /** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */ - -/** - * The main formatter object. - * @typedef LoadedFormatter - * @property {(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise} format format function. - */ +/** @typedef {import("../shared/types").RuleConf} RuleConf */ +/** @typedef {import("../shared/types").Rule} Rule */ +/** @typedef {ReturnType} ExtractedConfig */ /** * The options with which to configure the ESLint instance. @@ -54,270 +74,80 @@ const { version } = require("../../package.json"); * @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files. * @property {string} [cwd] The value to use for the current working directory. * @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`. - * @property {string[]} [extensions] An array of file extensions to check. * @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 {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 {string} [overrideConfigFile] The configuration file to use. - * @property {Record|null} [plugins] Preloaded plugins. This is a map-like object, keys are plugin IDs and each value is implementation. - * @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives. - * @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD. - * @property {string[]} [rulePaths] An array of directories to load custom rules from. - * @property {boolean} [useEslintrc] False disables looking for .eslintrc.* files. - */ - -/** - * A rules metadata object. - * @typedef {Object} RulesMeta - * @property {string} id The plugin ID. - * @property {Object} definition The plugin definition. - */ - -/** - * Private members for the `ESLint` instance. - * @typedef {Object} ESLintPrivateMembers - * @property {CLIEngine} cliEngine The wrapped CLIEngine instance. - * @property {ESLintOptions} options The options used to instantiate the ESLint 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 + * when a string. + * @property {Record} [plugins] An array of plugin implementations. + * @property {boolean} [stats] True enables added statistics on lint results. + * @property {boolean} warnIgnored Show warnings when the file list includes ignored files + * @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause + * the linting operation to short circuit and not report any failures. */ //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const writeFile = promisify(fs.writeFile); - -/** - * The map with which to store private class members. - * @type {WeakMap} - */ -const privateMembersMap = new WeakMap(); - -/** - * Check if a given value is a non-empty string or not. - * @param {any} x The value to check. - * @returns {boolean} `true` if `x` is a non-empty string. - */ -function isNonEmptyString(x) { - return typeof x === "string" && x.trim() !== ""; -} - -/** - * Check if a given value is an array of non-empty strings or not. - * @param {any} x The value to check. - * @returns {boolean} `true` if `x` is an array of non-empty strings. - */ -function isArrayOfNonEmptyString(x) { - return Array.isArray(x) && x.every(isNonEmptyString); -} - -/** - * Check if a given value is a valid fix type or not. - * @param {any} x The value to check. - * @returns {boolean} `true` if `x` is valid fix type. - */ -function isFixType(x) { - return x === "directive" || x === "problem" || x === "suggestion" || x === "layout"; -} - -/** - * Check if a given value is an array of fix types or not. - * @param {any} x The value to check. - * @returns {boolean} `true` if `x` is an array of fix types. - */ -function isFixTypeArray(x) { - return Array.isArray(x) && x.every(isFixType); -} - -/** - * The error for invalid options. - */ -class ESLintInvalidOptionsError extends Error { - constructor(messages) { - super(`Invalid Options:\n- ${messages.join("\n- ")}`); - this.code = "ESLINT_INVALID_OPTIONS"; - Error.captureStackTrace(this, ESLintInvalidOptionsError); - } -} +const FLAT_CONFIG_FILENAMES = [ + "eslint.config.js", + "eslint.config.mjs", + "eslint.config.cjs" +]; +const debug = require("debug")("eslint:eslint"); +const privateMembers = new WeakMap(); +const importedConfigFileModificationTime = new Map(); +const removedFormatters = new Set([ + "checkstyle", + "codeframe", + "compact", + "jslint-xml", + "junit", + "table", + "tap", + "unix", + "visualstudio" +]); /** - * Validates and normalizes options for the wrapped CLIEngine instance. - * @param {ESLintOptions} options The options to process. - * @throws {ESLintInvalidOptionsError} If of any of a variety of type errors. - * @returns {ESLintOptions} The normalized options. + * It will calculate the error and warning count for collection of messages per file + * @param {LintMessage[]} messages Collection of messages + * @returns {Object} Contains the stats + * @private */ -function processOptions({ - allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored. - baseConfig = null, - cache = false, - cacheLocation = ".eslintcache", - cacheStrategy = "metadata", - cwd = process.cwd(), - errorOnUnmatchedPattern = true, - extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature. - fix = false, - fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property. - globInputPaths = true, - ignore = true, - ignorePath = null, // ← should be null by default because if it's a string then it may throw ENOENT. - overrideConfig = null, - overrideConfigFile = null, - plugins = {}, - reportUnusedDisableDirectives = null, // ← should be null by default because if it's a string then it overrides the 'reportUnusedDisableDirectives' setting in config files. And we cannot use `overrideConfig.reportUnusedDisableDirectives` instead because we cannot configure the `error` severity with that. - resolvePluginsRelativeTo = null, // ← should be null by default because if it's a string then it suppresses RFC47 feature. - rulePaths = [], - useEslintrc = true, - ...unknownOptions -}) { - const errors = []; - const unknownOptionKeys = Object.keys(unknownOptions); - - if (unknownOptionKeys.length >= 1) { - errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`); - if (unknownOptionKeys.includes("cacheFile")) { - errors.push("'cacheFile' has been removed. Please use the 'cacheLocation' option instead."); - } - if (unknownOptionKeys.includes("configFile")) { - errors.push("'configFile' has been removed. Please use the 'overrideConfigFile' option instead."); - } - if (unknownOptionKeys.includes("envs")) { - errors.push("'envs' has been removed. Please use the 'overrideConfig.env' option instead."); - } - if (unknownOptionKeys.includes("globals")) { - errors.push("'globals' has been removed. Please use the 'overrideConfig.globals' option instead."); - } - if (unknownOptionKeys.includes("ignorePattern")) { - errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead."); - } - if (unknownOptionKeys.includes("parser")) { - errors.push("'parser' has been removed. Please use the 'overrideConfig.parser' option instead."); - } - if (unknownOptionKeys.includes("parserOptions")) { - errors.push("'parserOptions' has been removed. Please use the 'overrideConfig.parserOptions' option instead."); - } - if (unknownOptionKeys.includes("rules")) { - errors.push("'rules' has been removed. Please use the 'overrideConfig.rules' option instead."); - } - } - if (typeof allowInlineConfig !== "boolean") { - errors.push("'allowInlineConfig' must be a boolean."); - } - if (typeof baseConfig !== "object") { - errors.push("'baseConfig' must be an object or null."); - } - if (typeof cache !== "boolean") { - errors.push("'cache' must be a boolean."); - } - if (!isNonEmptyString(cacheLocation)) { - errors.push("'cacheLocation' must be a non-empty string."); - } - if ( - cacheStrategy !== "metadata" && - cacheStrategy !== "content" - ) { - errors.push("'cacheStrategy' must be any of \"metadata\", \"content\"."); - } - if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) { - errors.push("'cwd' must be an absolute path."); - } - if (typeof errorOnUnmatchedPattern !== "boolean") { - errors.push("'errorOnUnmatchedPattern' must be a boolean."); - } - if (!isArrayOfNonEmptyString(extensions) && extensions !== null) { - errors.push("'extensions' must be an array of non-empty strings or null."); - } - if (typeof fix !== "boolean" && typeof fix !== "function") { - errors.push("'fix' must be a boolean or a function."); - } - if (fixTypes !== null && !isFixTypeArray(fixTypes)) { - errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\"."); - } - if (typeof globInputPaths !== "boolean") { - errors.push("'globInputPaths' must be a boolean."); - } - if (typeof ignore !== "boolean") { - errors.push("'ignore' must be a boolean."); - } - if (!isNonEmptyString(ignorePath) && ignorePath !== null) { - errors.push("'ignorePath' must be a non-empty string or null."); - } - if (typeof overrideConfig !== "object") { - errors.push("'overrideConfig' must be an object or null."); - } - if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null) { - errors.push("'overrideConfigFile' must be a non-empty string or null."); - } - if (typeof plugins !== "object") { - errors.push("'plugins' must be an object or null."); - } else if (plugins !== null && Object.keys(plugins).includes("")) { - errors.push("'plugins' must not include an empty string."); - } - if (Array.isArray(plugins)) { - errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead."); - } - if ( - reportUnusedDisableDirectives !== "error" && - reportUnusedDisableDirectives !== "warn" && - reportUnusedDisableDirectives !== "off" && - reportUnusedDisableDirectives !== null - ) { - errors.push("'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null."); - } - if ( - !isNonEmptyString(resolvePluginsRelativeTo) && - resolvePluginsRelativeTo !== null - ) { - errors.push("'resolvePluginsRelativeTo' must be a non-empty string or null."); - } - if (!isArrayOfNonEmptyString(rulePaths)) { - errors.push("'rulePaths' must be an array of non-empty strings."); - } - if (typeof useEslintrc !== "boolean") { - errors.push("'useEslintrc' must be a boolean."); - } - - if (errors.length > 0) { - throw new ESLintInvalidOptionsError(errors); - } - - return { - allowInlineConfig, - baseConfig, - cache, - cacheLocation, - cacheStrategy, - configFile: overrideConfigFile, - cwd: path.normalize(cwd), - errorOnUnmatchedPattern, - extensions, - fix, - fixTypes, - globInputPaths, - ignore, - ignorePath, - reportUnusedDisableDirectives, - resolvePluginsRelativeTo, - rulePaths, - useEslintrc +function calculateStatsPerFile(messages) { + const stat = { + errorCount: 0, + fatalErrorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0 }; -} -/** - * Check if a value has one or more properties and that value is not undefined. - * @param {any} obj The value to check. - * @returns {boolean} `true` if `obj` has one or more properties that that value is not undefined. - */ -function hasDefinedProperty(obj) { - if (typeof obj === "object" && obj !== null) { - for (const key in obj) { - if (typeof obj[key] !== "undefined") { - return true; + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + + if (message.fatal || message.severity === 2) { + stat.errorCount++; + if (message.fatal) { + stat.fatalErrorCount++; + } + if (message.fix) { + stat.fixableErrorCount++; + } + } else { + stat.warningCount++; + if (message.fix) { + stat.fixableWarningCount++; } } } - return false; + return stat; } /** @@ -332,62 +162,73 @@ 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(); /** * Create used deprecated rule list. - * @param {CLIEngine} cliEngine The CLIEngine instance. + * @param {CLIEngine} eslint The CLIEngine instance. * @param {string} maybeFilePath The absolute path to a lint target file or `""`. * @returns {DeprecatedRuleInfo[]} The used deprecated rule list. */ -function getOrFindUsedDeprecatedRules(cliEngine, maybeFilePath) { +function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) { const { - configArrayFactory, + configs, options: { cwd } - } = getCLIEngineInternalSlots(cliEngine); + } = privateMembers.get(eslint); const filePath = path.isAbsolute(maybeFilePath) ? maybeFilePath - : path.join(cwd, "__placeholder__.js"); - const configArray = configArrayFactory.getConfigArrayForFile(filePath); - const config = configArray.extractConfig(filePath); + : getPlaceholderPath(cwd); + const config = configs.getConfig(filePath); // Most files use the same config, so cache it. - if (!usedDeprecatedRulesCache.has(config)) { - const pluginRules = configArray.pluginRules; + if (config && !usedDeprecatedRulesCache.has(config)) { const retv = []; - for (const [ruleId, ruleConf] of Object.entries(config.rules)) { - if (getRuleSeverity(ruleConf) === 0) { - continue; - } - const rule = pluginRules.get(ruleId) || BuiltinRules.get(ruleId); - const meta = rule && rule.meta; - - if (meta && meta.deprecated) { - retv.push({ ruleId, replacedBy: meta.replacedBy || [] }); + if (config.rules) { + for (const [ruleId, ruleConf] of Object.entries(config.rules)) { + if (getRuleSeverity(ruleConf) === 0) { + continue; + } + const rule = getRuleFromConfig(ruleId, config); + const meta = rule && rule.meta; + + if (meta && meta.deprecated) { + retv.push({ ruleId, replacedBy: meta.replacedBy || [] }); + } } } + usedDeprecatedRulesCache.set(config, Object.freeze(retv)); } - return usedDeprecatedRulesCache.get(config); + return config ? usedDeprecatedRulesCache.get(config) : Object.freeze([]); } /** * Processes the linting results generated by a CLIEngine linting report to * match the ESLint class's API. - * @param {CLIEngine} cliEngine The CLIEngine instance. + * @param {CLIEngine} eslint The CLIEngine instance. * @param {CLIEngineLintReport} report The CLIEngine linting report to process. * @returns {LintResult[]} The processed linting results. */ -function processCLIEngineLintReport(cliEngine, { results }) { +function processLintReport(eslint, { results }) { const descriptor = { configurable: true, enumerable: true, get() { - return getOrFindUsedDeprecatedRules(cliEngine, this.filePath); + return getOrFindUsedDeprecatedRules(eslint, this.filePath); } }; @@ -417,44 +258,385 @@ function compareResultsByFilePath(a, b) { } /** - * Main API. + * 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 `undefined` if not. + */ +function findFlatConfigFile(cwd) { + return findUp( + FLAT_CONFIG_FILENAMES, + { cwd } + ); +} + +/** + * Load the config array from the given filename. + * @param {string} filePath The filename to load from. + * @returns {Promise} The config loaded from the config file. + */ +async function loadFlatConfigFile(filePath) { + debug(`Loading config from ${filePath}`); + + const fileURL = pathToFileURL(filePath); + + debug(`Config file URL is ${fileURL}`); + + const mtime = (await fs.stat(filePath)).mtime.getTime(); + + /* + * 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, + error + }; + +} + +/** + * Calculates the config array for this run based on inputs. + * @param {ESLint} eslint The instance to create the config array for. + * @param {import("./eslint").ESLintOptions} options The ESLint instance options. + * @returns {FlatConfigArray} The config array for `eslint``. + */ +async function calculateConfigArray(eslint, { + cwd, + baseConfig, + overrideConfig, + configFile, + ignore: shouldIgnore, + ignorePatterns +}) { + + // check for cached instance + const slots = privateMembers.get(eslint); + + if (slots.configs) { + return slots.configs; + } + + const { configFilePath, basePath, error } = await locateConfigFileToUse({ configFile, cwd }); + + // config file is required to calculate config + if (error) { + throw error; + } + + const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore }); + + // load config file + if (configFilePath) { + 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); + + // append command line ignore patterns + 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 { + + const relativeIgnorePath = path.relative(basePath, cwd); + + relativeIgnorePatterns = ignorePatterns.map(pattern => { + const negated = pattern.startsWith("!"); + const basePattern = negated ? pattern.slice(1) : pattern; + + return (negated ? "!" : "") + + path.posix.join(relativeIgnorePath, basePattern); + }); + } + + /* + * Ignore patterns are added to the end of the config array + * so they can override default ignores. + */ + configs.push({ + ignores: relativeIgnorePatterns + }); + } + + if (overrideConfig) { + if (Array.isArray(overrideConfig)) { + configs.push(...overrideConfig); + } else { + configs.push(overrideConfig); + } + } + + await configs.normalize(); + + // cache the config array for this instance + slots.configs = configs; + + return configs; +} + +/** + * Processes an source code using ESLint. + * @param {Object} config The config object. + * @param {string} config.text The source code to verify. + * @param {string} config.cwd The path to the current working directory. + * @param {string|undefined} config.filePath The path to the file of `text`. If this is undefined, it uses ``. + * @param {FlatConfigArray} config.configs The config. + * @param {boolean} config.fix If `true` then it does fix. + * @param {boolean} config.allowInlineConfig If `true` then it uses directive comments. + * @param {Function} config.ruleFilter A predicate function to filter which rules should be run. + * @param {boolean} config.stats If `true`, then if reports extra statistics with the lint results. + * @param {Linter} config.linter The linter instance to verify. + * @returns {LintResult} The result of linting. + * @private + */ +function verifyText({ + text, + cwd, + filePath: providedFilePath, + configs, + fix, + allowInlineConfig, + ruleFilter, + stats, + linter +}) { + const filePath = providedFilePath || ""; + + debug(`Lint ${filePath}`); + + /* + * Verify. + * `config.extractConfig(filePath)` requires an absolute path, but `linter` + * doesn't know CWD, so it gives `linter` an absolute path always. + */ + const filePathToVerify = filePath === "" ? getPlaceholderPath(cwd) : filePath; + const { fixed, messages, output } = linter.verifyAndFix( + text, + configs, + { + allowInlineConfig, + filename: filePathToVerify, + fix, + ruleFilter, + stats, + + /** + * Check if the linter should adopt a given code block or not. + * @param {string} blockFilename The virtual filename of a code block. + * @returns {boolean} `true` if the linter should adopt the code block. + */ + filterCodeBlock(blockFilename) { + return configs.isExplicitMatch(blockFilename); + } + } + ); + + // Tweak and return. + const result = { + filePath: filePath === "" ? filePath : path.resolve(filePath), + messages, + suppressedMessages: linter.getSuppressedMessages(), + ...calculateStatsPerFile(messages) + }; + + if (fixed) { + result.output = output; + } + + if ( + result.errorCount + result.warningCount > 0 && + typeof result.output === "undefined" + ) { + result.source = text; + } + + if (stats) { + result.stats = { + times: linter.getTimes(), + fixPasses: linter.getFixPassCount() + }; + } + + return result; +} + +/** + * Checks whether a message's rule type should be fixed. + * @param {LintMessage} message The message to check. + * @param {FlatConfig} config The config for the file that generated the message. + * @param {string[]} fixTypes An array of fix types to check. + * @returns {boolean} Whether the message should be fixed. + */ +function shouldMessageBeFixed(message, config, fixTypes) { + if (!message.ruleId) { + return fixTypes.has("directive"); + } + + const rule = message.ruleId && getRuleFromConfig(message.ruleId, config); + + return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type)); +} + +/** + * 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 +//----------------------------------------------------------------------------- + +/** + * Primary Node.js API for ESLint. */ class ESLint { + /** + * The type of configuration used by this class. + * @type {string} + */ + static configType = "flat"; + /** * Creates a new instance of the main ESLint API. * @param {ESLintOptions} options The options for this instance. */ constructor(options = {}) { + + const defaultConfigs = []; const processedOptions = processOptions(options); - const cliEngine = new CLIEngine(processedOptions, { preloadedPlugins: options.plugins }); - const { - configArrayFactory, - lastConfigArrays - } = getCLIEngineInternalSlots(cliEngine); - let updated = false; + const linter = new Linter({ + cwd: processedOptions.cwd, + configType: "flat" + }); - /* - * Address `overrideConfig` to set override config. - * Operate the `configArrayFactory` internal slot directly because this - * functionality doesn't exist as the public API of CLIEngine. + const cacheFilePath = getCacheFile( + processedOptions.cacheLocation, + processedOptions.cwd + ); + + const lintResultCache = processedOptions.cache + ? new LintResultCache(cacheFilePath, processedOptions.cacheStrategy) + : null; + + privateMembers.set(this, { + options: processedOptions, + linter, + cacheFilePath, + lintResultCache, + defaultConfigs, + configs: null + }); + + /** + * If additional plugins are passed in, add that to the default + * configs for this instance. */ - if (hasDefinedProperty(options.overrideConfig)) { - configArrayFactory.setOverrideConfig(options.overrideConfig); - updated = true; - } + if (options.plugins) { + + const plugins = {}; - // Update caches. - if (updated) { - configArrayFactory.clearCache(); - lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile(); + for (const [pluginName, plugin] of Object.entries(options.plugins)) { + plugins[naming.getShorthandName(pluginName, "eslint-plugin")] = plugin; + } + + defaultConfigs.push({ + plugins + }); } - // Initialize private properties. - privateMembersMap.set(this, { - cliEngine, - options: processedOptions - }); + // Check for the .eslintignore file, and warn if it's present. + if (existsSync(path.resolve(processedOptions.cwd, ".eslintignore"))) { + process.emitWarning( + "The \".eslintignore\" file is no longer supported. Switch to using the \"ignores\" property in \"eslint.config.js\": https://eslint.org/docs/latest/use/configure/migration-guide#ignoring-files", + "ESLintIgnoreWarning" + ); + } } /** @@ -486,7 +668,7 @@ class ESLint { path.isAbsolute(result.filePath) ); }) - .map(r => writeFile(r.filePath, r.output)) + .map(r => fs.writeFile(r.filePath, r.output)) ); } @@ -496,60 +678,295 @@ class ESLint { * @returns {LintResult[]} The filtered results. */ static getErrorResults(results) { - return CLIEngine.getErrorResults(results); + const filtered = []; + + results.forEach(result => { + const filteredMessages = result.messages.filter(isErrorMessage); + const filteredSuppressedMessages = result.suppressedMessages.filter(isErrorMessage); + + if (filteredMessages.length > 0) { + filtered.push({ + ...result, + messages: filteredMessages, + suppressedMessages: filteredSuppressedMessages, + errorCount: filteredMessages.length, + warningCount: 0, + fixableErrorCount: result.fixableErrorCount, + fixableWarningCount: 0 + }); + } + }); + + return filtered; } /** * Returns meta objects for each rule represented in the lint results. * @param {LintResult[]} results The results to fetch rules meta for. * @returns {Object} A mapping of ruleIds to rule meta objects. + * @throws {TypeError} When the results object wasn't created from this ESLint instance. + * @throws {TypeError} When a plugin or rule is missing. */ getRulesMetaForResults(results) { - const resultRuleIds = new Set(); + // short-circuit simple case + if (results.length === 0) { + return {}; + } - // first gather all ruleIds from all results + const resultRules = new Map(); + const { + configs, + options: { cwd } + } = privateMembers.get(this); - for (const result of results) { - for (const { ruleId } of result.messages) { - resultRuleIds.add(ruleId); - } - for (const { ruleId } of result.suppressedMessages) { - resultRuleIds.add(ruleId); - } + /* + * We can only accurately return rules meta information for linting results if the + * results were created by this instance. Otherwise, the necessary rules data is + * not available. So if the config array doesn't already exist, just throw an error + * to let the user know we can't do anything here. + */ + if (!configs) { + throw createExtraneousResultsError(); } - // create a map of all rules in the results - - const { cliEngine } = privateMembersMap.get(this); - const rules = cliEngine.getRules(); - const resultRules = new Map(); + for (const result of results) { - for (const [ruleId, rule] of rules) { - if (resultRuleIds.has(ruleId)) { - resultRules.set(ruleId, rule); + /* + * Normalize filename for . + */ + const filePath = result.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); + + // ignore unknown rules + if (rule) { + resultRules.set(ruleId, rule); + } } } return createRulesMeta(resultRules); - } /** * Executes the current configuration on an array of file and directory names. - * @param {string[]} patterns An array of file and directory names. + * @param {string|string[]} patterns An array of file and directory names. * @returns {Promise} The results of linting the file patterns given. */ async lintFiles(patterns) { - if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) { - throw new Error("'patterns' must be a non-empty string or an array of non-empty strings"); + + let normalizedPatterns = patterns; + const { + cacheFilePath, + lintResultCache, + linter, + options: eslintOptions + } = privateMembers.get(this); + + /* + * Special cases: + * 1. `patterns` is an empty string + * 2. `patterns` is an empty array + * + * In both cases, we use the cwd as the directory to lint. + */ + if (patterns === "" || Array.isArray(patterns) && patterns.length === 0) { + + /* + * Special case: If `passOnNoPatterns` is true, then we just exit + * without doing any work. + */ + if (eslintOptions.passOnNoPatterns) { + return []; + } + + normalizedPatterns = ["."]; + } else { + + if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) { + throw new Error("'patterns' must be a non-empty string or an array of non-empty strings"); + } + + if (typeof patterns === "string") { + normalizedPatterns = [patterns]; + } + } + + debug(`Using file patterns: ${normalizedPatterns}`); + + const configs = await calculateConfigArray(this, eslintOptions); + const { + allowInlineConfig, + cache, + cwd, + fix, + fixTypes, + ruleFilter, + stats, + globInputPaths, + errorOnUnmatchedPattern, + warnIgnored + } = eslintOptions; + const startTime = Date.now(); + const fixTypesSet = fixTypes ? new Set(fixTypes) : null; + + // Delete cache file; should this be done here? + if (!cache && cacheFilePath) { + debug(`Deleting cache file at ${cacheFilePath}`); + + try { + await fs.unlink(cacheFilePath); + } catch (error) { + const errorCode = error && error.code; + + // Ignore errors when no such file exists or file system is read only (and cache file does not exist) + if (errorCode !== "ENOENT" && !(errorCode === "EROFS" && !existsSync(cacheFilePath))) { + throw error; + } + } } - const { cliEngine } = privateMembersMap.get(this); - return processCLIEngineLintReport( - cliEngine, - cliEngine.executeOnFiles(patterns) + const filePaths = await findFiles({ + patterns: normalizedPatterns, + cwd, + globInputPaths, + configs, + errorOnUnmatchedPattern + }); + const controller = new AbortController(); + + debug(`${filePaths.length} files found in: ${Date.now() - startTime}ms`); + + /* + * Because we need to process multiple files, including reading from disk, + * it is most efficient to start by reading each file via promises so that + * they can be done in parallel. Then, we can lint the returned text. This + * ensures we are waiting the minimum amount of time in between lints. + */ + const results = await Promise.all( + + filePaths.map(({ filePath, ignored }) => { + + /* + * If a filename was entered that matches an ignore + * pattern, then notify the user. + */ + if (ignored) { + if (warnIgnored) { + return createIgnoreResult(filePath, cwd); + } + + return void 0; + } + + const config = configs.getConfig(filePath); + + /* + * Sometimes a file found through a glob pattern will + * be ignored. In this case, `config` will be undefined + * and we just silently ignore the file. + */ + if (!config) { + return void 0; + } + + // Skip if there is cached result. + if (lintResultCache) { + const cachedResult = + lintResultCache.getCachedLintResults(filePath, config); + + if (cachedResult) { + const hadMessages = + cachedResult.messages && + cachedResult.messages.length > 0; + + if (hadMessages && fix) { + debug(`Reprocessing cached file to allow autofix: ${filePath}`); + } else { + debug(`Skipping file since it hasn't changed: ${filePath}`); + return cachedResult; + } + } + } + + + // set up fixer for fixTypes if necessary + let fixer = fix; + + if (fix && fixTypesSet) { + + // save original value of options.fix in case it's a function + const originalFix = (typeof fix === "function") + ? fix : () => true; + + fixer = message => shouldMessageBeFixed(message, config, fixTypesSet) && originalFix(message); + } + + return fs.readFile(filePath, { encoding: "utf8", signal: controller.signal }) + .then(text => { + + // fail immediately if an error occurred in another file + controller.signal.throwIfAborted(); + + // do the linting + const result = verifyText({ + text, + filePath, + configs, + cwd, + fix: fixer, + allowInlineConfig, + ruleFilter, + stats, + linter + }); + + /* + * Store the lint result in the LintResultCache. + * NOTE: The LintResultCache will remove the file source and any + * other properties that are difficult to serialize, and will + * hydrate those properties back in on future lint runs. + */ + if (lintResultCache) { + lintResultCache.setCachedLintResults(filePath, config, result); + } + + return result; + }).catch(error => { + controller.abort(error); + throw error; + }); + + }) ); + + // Persist the cache to disk. + if (lintResultCache) { + lintResultCache.reconcile(); + } + + const finalResults = results.filter(result => !!result); + + return processLintReport(this, { + results: finalResults + }); } /** @@ -561,15 +978,22 @@ class ESLint { * @returns {Promise} The results of linting the string of code given. */ async lintText(code, options = {}) { + + // Parameter validation + if (typeof code !== "string") { throw new Error("'code' must be a string"); } + if (typeof options !== "object") { throw new Error("'options' must be an object, null, or undefined"); } + + // Options validation + const { filePath, - warnIgnored = false, + warnIgnored, ...unknownOptions } = options || {}; @@ -582,16 +1006,59 @@ class ESLint { if (filePath !== void 0 && !isNonEmptyString(filePath)) { throw new Error("'options.filePath' must be a non-empty string or undefined"); } - if (typeof warnIgnored !== "boolean") { + + if (typeof warnIgnored !== "boolean" && typeof warnIgnored !== "undefined") { throw new Error("'options.warnIgnored' must be a boolean or undefined"); } - const { cliEngine } = privateMembersMap.get(this); + // Now we can get down to linting + + const { + linter, + options: eslintOptions + } = privateMembers.get(this); + const configs = await calculateConfigArray(this, eslintOptions); + const { + allowInlineConfig, + cwd, + fix, + warnIgnored: constructorWarnIgnored, + ruleFilter, + stats + } = eslintOptions; + const results = []; + const startTime = Date.now(); + const resolvedFilename = path.resolve(cwd, filePath || "__placeholder__.js"); + + // Clear the last used config arrays. + if (resolvedFilename && await this.isPathIgnored(resolvedFilename)) { + const shouldWarnIgnored = typeof warnIgnored === "boolean" ? warnIgnored : constructorWarnIgnored; + + if (shouldWarnIgnored) { + results.push(createIgnoreResult(resolvedFilename, cwd)); + } + } else { + + // Do lint. + results.push(verifyText({ + text: code, + filePath: resolvedFilename.endsWith("__placeholder__.js") ? "" : resolvedFilename, + configs, + cwd, + fix, + allowInlineConfig, + ruleFilter, + stats, + linter + })); + } + + debug(`Linting complete in: ${Date.now() - startTime}ms`); + + return processLintReport(this, { + results + }); - return processCLIEngineLintReport( - cliEngine, - cliEngine.executeOnText(code, filePath, warnIgnored) - ); } /** @@ -605,7 +1072,7 @@ class ESLint { * - `@foo` → `@foo/eslint-formatter` * - `@foo/bar` → `@foo/eslint-formatter-bar` * - A file path ... Load the file. - * @returns {Promise} A promise resolving to the formatter object. + * @returns {Promise} A promise resolving to the formatter object. * This promise will be rejected if the given formatter was not found or not * a function. */ @@ -614,20 +1081,60 @@ class ESLint { throw new Error("'name' must be a string"); } - const { cliEngine, options } = privateMembersMap.get(this); - const formatter = cliEngine.getFormatter(name); + // replace \ with / for Windows compatibility + const normalizedFormatName = name.replace(/\\/gu, "/"); + const namespace = naming.getNamespaceFromTerm(normalizedFormatName); + + // grab our options + const { cwd } = privateMembers.get(this).options; + + + let formatterPath; + + // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages) + if (!namespace && normalizedFormatName.includes("/")) { + formatterPath = path.resolve(cwd, normalizedFormatName); + } else { + try { + 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, getPlaceholderPath(cwd)); + } catch { + formatterPath = path.resolve(__dirname, "../", "cli-engine", "formatters", `${normalizedFormatName}.js`); + } + } + + let formatter; + + try { + formatter = (await import(pathToFileURL(formatterPath))).default; + } catch (ex) { + + // check for formatters that have been removed + if (removedFormatters.has(name)) { + ex.message = `The ${name} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${name}\``; + } else { + ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`; + } + + throw ex; + } + if (typeof formatter !== "function") { - throw new Error(`Formatter must be a function, but got a ${typeof formatter}.`); + throw new TypeError(`Formatter must be a function, but got a ${typeof formatter}.`); } + const eslint = this; + return { /** * The main formatter method. - * @param {LintResult[]} results The lint results to format. + * @param {LintResults[]} results The lint results to format. * @param {ResultsMeta} resultsMeta Warning count and max threshold. - * @returns {string | Promise} The formatted lint results. + * @returns {string} The formatted lint results. */ format(results, resultsMeta) { let rulesMeta = null; @@ -636,12 +1143,10 @@ class ESLint { return formatter(results, { ...resultsMeta, - get cwd() { - return options.cwd; - }, + cwd, get rulesMeta() { if (!rulesMeta) { - rulesMeta = createRulesMeta(cliEngine.getRules()); + rulesMeta = eslint.getRulesMetaForResults(results); } return rulesMeta; @@ -656,15 +1161,31 @@ class ESLint { * This is the same logic used by the ESLint CLI executable to determine * configuration for each file it processes. * @param {string} filePath The path of the file to retrieve a config object for. - * @returns {Promise} A configuration object for the file. + * @returns {Promise} A configuration object for the file + * or `undefined` if there is no configuration data for the object. */ async calculateConfigForFile(filePath) { if (!isNonEmptyString(filePath)) { throw new Error("'filePath' must be a non-empty string"); } - const { cliEngine } = privateMembersMap.get(this); + const options = privateMembers.get(this).options; + const absolutePath = path.resolve(options.cwd, filePath); + const configs = await calculateConfigArray(this, options); - return cliEngine.getConfigForFile(filePath); + 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; } /** @@ -673,21 +1194,19 @@ class ESLint { * @returns {Promise} Whether or not the given path is ignored. */ async isPathIgnored(filePath) { - if (!isNonEmptyString(filePath)) { - throw new Error("'filePath' must be a non-empty string"); - } - const { cliEngine } = privateMembersMap.get(this); + const config = await this.calculateConfigForFile(filePath); - return cliEngine.isPathIgnored(filePath); + return config === void 0; } } /** - * The type of configuration used by this class. - * @type {string} - * @static + * Returns whether flat config should be used. + * @returns {Promise} Whether flat config should be used. */ -ESLint.configType = "eslintrc"; +async function shouldUseFlatConfig() { + return (process.env.ESLINT_USE_FLAT_CONFIG !== "false"); +} //------------------------------------------------------------------------------ // Public Interface @@ -695,13 +1214,5 @@ ESLint.configType = "eslintrc"; module.exports = { ESLint, - - /** - * Get the private class members of a given ESLint instance for tests. - * @param {ESLint} instance The ESLint instance to get. - * @returns {ESLintPrivateMembers} The instance's private class members. - */ - getESLintPrivateMembers(instance) { - return privateMembersMap.get(instance); - } + shouldUseFlatConfig }; diff --git a/tools/node_modules/eslint/lib/eslint/flat-eslint.js b/tools/node_modules/eslint/lib/eslint/flat-eslint.js deleted file mode 100644 index ca961aafb64978..00000000000000 --- a/tools/node_modules/eslint/lib/eslint/flat-eslint.js +++ /dev/null @@ -1,1155 +0,0 @@ -/** - * @fileoverview Main class using flat config - * @author Nicholas C. Zakas - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -// Note: Node.js 12 does not support fs/promises. -const fs = require("fs").promises; -const { existsSync } = require("fs"); -const path = require("path"); -const findUp = require("find-up"); -const { version } = require("../../package.json"); -const { Linter } = require("../linter"); -const { getRuleFromConfig } = require("../config/flat-config-helpers"); -const { - Legacy: { - ConfigOps: { - getRuleSeverity - }, - ModuleResolver, - naming - } -} = require("@eslint/eslintrc"); - -const { - findFiles, - getCacheFile, - - isNonEmptyString, - isArrayOfNonEmptyString, - - createIgnoreResult, - isErrorMessage, - - processOptions -} = require("./eslint-helpers"); -const { pathToFileURL } = require("url"); -const { FlatConfigArray } = require("../config/flat-config-array"); -const LintResultCache = require("../cli-engine/lint-result-cache"); - -/* - * This is necessary to allow overwriting writeFile for testing purposes. - * We can just use fs/promises once we drop Node.js 12 support. - */ - -//------------------------------------------------------------------------------ -// Typedefs -//------------------------------------------------------------------------------ - -// For VSCode IntelliSense -/** @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 */ - -/** - * The options with which to configure the ESLint instance. - * @typedef {Object} FlatESLintOptions - * @property {boolean} [allowInlineConfig] Enable or disable inline configuration comments. - * @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this instance - * @property {boolean} [cache] Enable result caching. - * @property {string} [cacheLocation] The cache file to use instead of .eslintcache. - * @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files. - * @property {string} [cwd] The value to use for the current working directory. - * @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`. - * @property {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 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 - * when a string. - * @property {Record} [plugins] An array of plugin implementations. - * @property {boolean} warnIgnored Show warnings when the file list includes ignored files - */ - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -const FLAT_CONFIG_FILENAMES = [ - "eslint.config.js", - "eslint.config.mjs", - "eslint.config.cjs" -]; -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 - * @param {LintMessage[]} messages Collection of messages - * @returns {Object} Contains the stats - * @private - */ -function calculateStatsPerFile(messages) { - const stat = { - errorCount: 0, - fatalErrorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - }; - - for (let i = 0; i < messages.length; i++) { - const message = messages[i]; - - if (message.fatal || message.severity === 2) { - stat.errorCount++; - if (message.fatal) { - stat.fatalErrorCount++; - } - if (message.fix) { - stat.fixableErrorCount++; - } - } else { - stat.warningCount++; - if (message.fix) { - stat.fixableWarningCount++; - } - } - } - return stat; -} - -/** - * Create rulesMeta object. - * @param {Map} rules a map of rules from which to generate the object. - * @returns {Object} metadata for all enabled rules. - */ -function createRulesMeta(rules) { - return Array.from(rules).reduce((retVal, [id, rule]) => { - retVal[id] = rule.meta; - return retVal; - }, {}); -} - -/** - * 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(); - -/** - * Create used deprecated rule list. - * @param {CLIEngine} eslint The CLIEngine instance. - * @param {string} maybeFilePath The absolute path to a lint target file or `""`. - * @returns {DeprecatedRuleInfo[]} The used deprecated rule list. - */ -function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) { - const { - configs, - options: { cwd } - } = privateMembers.get(eslint); - const filePath = path.isAbsolute(maybeFilePath) - ? maybeFilePath - : getPlaceholderPath(cwd); - const config = configs.getConfig(filePath); - - // Most files use the same config, so cache it. - if (config && !usedDeprecatedRulesCache.has(config)) { - const retv = []; - - if (config.rules) { - for (const [ruleId, ruleConf] of Object.entries(config.rules)) { - if (getRuleSeverity(ruleConf) === 0) { - continue; - } - const rule = getRuleFromConfig(ruleId, config); - const meta = rule && rule.meta; - - if (meta && meta.deprecated) { - retv.push({ ruleId, replacedBy: meta.replacedBy || [] }); - } - } - } - - - usedDeprecatedRulesCache.set(config, Object.freeze(retv)); - } - - return config ? usedDeprecatedRulesCache.get(config) : Object.freeze([]); -} - -/** - * Processes the linting results generated by a CLIEngine linting report to - * match the ESLint class's API. - * @param {CLIEngine} eslint The CLIEngine instance. - * @param {CLIEngineLintReport} report The CLIEngine linting report to process. - * @returns {LintResult[]} The processed linting results. - */ -function processLintReport(eslint, { results }) { - const descriptor = { - configurable: true, - enumerable: true, - get() { - return getOrFindUsedDeprecatedRules(eslint, this.filePath); - } - }; - - for (const result of results) { - Object.defineProperty(result, "usedDeprecatedRules", descriptor); - } - - return results; -} - -/** - * An Array.prototype.sort() compatible compare function to order results by their file path. - * @param {LintResult} a The first lint result. - * @param {LintResult} b The second lint result. - * @returns {number} An integer representing the order in which the two results should occur. - */ -function compareResultsByFilePath(a, b) { - if (a.filePath < b.filePath) { - return -1; - } - - if (a.filePath > b.filePath) { - return 1; - } - - return 0; -} - -/** - * 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 `undefined` if not. - */ -function findFlatConfigFile(cwd) { - return findUp( - FLAT_CONFIG_FILENAMES, - { cwd } - ); -} - -/** - * Load the config array from the given filename. - * @param {string} filePath The filename to load from. - * @returns {Promise} The config loaded from the config file. - */ -async function loadFlatConfigFile(filePath) { - debug(`Loading config from ${filePath}`); - - const fileURL = pathToFileURL(filePath); - - debug(`Config file URL is ${fileURL}`); - - const mtime = (await fs.stat(filePath)).mtime.getTime(); - - /* - * 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, - error - }; - -} - -/** - * Calculates the config array for this run based on inputs. - * @param {FlatESLint} eslint The instance to create the config array for. - * @param {import("./eslint").ESLintOptions} options The ESLint instance options. - * @returns {FlatConfigArray} The config array for `eslint``. - */ -async function calculateConfigArray(eslint, { - cwd, - baseConfig, - overrideConfig, - configFile, - ignore: shouldIgnore, - ignorePatterns -}) { - - // check for cached instance - const slots = privateMembers.get(eslint); - - if (slots.configs) { - return slots.configs; - } - - const { configFilePath, basePath, error } = await locateConfigFileToUse({ configFile, cwd }); - - // config file is required to calculate config - if (error) { - throw error; - } - - const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore }); - - // load config file - if (configFilePath) { - 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); - - // append command line ignore patterns - 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 { - - const relativeIgnorePath = path.relative(basePath, cwd); - - relativeIgnorePatterns = ignorePatterns.map(pattern => { - const negated = pattern.startsWith("!"); - const basePattern = negated ? pattern.slice(1) : pattern; - - return (negated ? "!" : "") + - path.posix.join(relativeIgnorePath, basePattern); - }); - } - - /* - * Ignore patterns are added to the end of the config array - * so they can override default ignores. - */ - configs.push({ - ignores: relativeIgnorePatterns - }); - } - - if (overrideConfig) { - if (Array.isArray(overrideConfig)) { - configs.push(...overrideConfig); - } else { - configs.push(overrideConfig); - } - } - - await configs.normalize(); - - // cache the config array for this instance - slots.configs = configs; - - return configs; -} - -/** - * Processes an source code using ESLint. - * @param {Object} config The config object. - * @param {string} config.text The source code to verify. - * @param {string} config.cwd The path to the current working directory. - * @param {string|undefined} config.filePath The path to the file of `text`. If this is undefined, it uses ``. - * @param {FlatConfigArray} config.configs The config. - * @param {boolean} config.fix If `true` then it does fix. - * @param {boolean} config.allowInlineConfig If `true` then it uses directive comments. - * @param {Linter} config.linter The linter instance to verify. - * @returns {LintResult} The result of linting. - * @private - */ -function verifyText({ - text, - cwd, - filePath: providedFilePath, - configs, - fix, - allowInlineConfig, - linter -}) { - const filePath = providedFilePath || ""; - - debug(`Lint ${filePath}`); - - /* - * Verify. - * `config.extractConfig(filePath)` requires an absolute path, but `linter` - * doesn't know CWD, so it gives `linter` an absolute path always. - */ - const filePathToVerify = filePath === "" ? getPlaceholderPath(cwd) : filePath; - const { fixed, messages, output } = linter.verifyAndFix( - text, - configs, - { - allowInlineConfig, - filename: filePathToVerify, - fix, - - /** - * Check if the linter should adopt a given code block or not. - * @param {string} blockFilename The virtual filename of a code block. - * @returns {boolean} `true` if the linter should adopt the code block. - */ - filterCodeBlock(blockFilename) { - return configs.isExplicitMatch(blockFilename); - } - } - ); - - // Tweak and return. - const result = { - filePath: filePath === "" ? filePath : path.resolve(filePath), - messages, - suppressedMessages: linter.getSuppressedMessages(), - ...calculateStatsPerFile(messages) - }; - - if (fixed) { - result.output = output; - } - - if ( - result.errorCount + result.warningCount > 0 && - typeof result.output === "undefined" - ) { - result.source = text; - } - - return result; -} - -/** - * Checks whether a message's rule type should be fixed. - * @param {LintMessage} message The message to check. - * @param {FlatConfig} config The config for the file that generated the message. - * @param {string[]} fixTypes An array of fix types to check. - * @returns {boolean} Whether the message should be fixed. - */ -function shouldMessageBeFixed(message, config, fixTypes) { - if (!message.ruleId) { - return fixTypes.has("directive"); - } - - const rule = message.ruleId && getRuleFromConfig(message.ruleId, config); - - return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type)); -} - -/** - * 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 -//----------------------------------------------------------------------------- - -/** - * Primary Node.js API for ESLint. - */ -class FlatESLint { - - /** - * Creates a new instance of the main ESLint API. - * @param {FlatESLintOptions} options The options for this instance. - */ - constructor(options = {}) { - - const defaultConfigs = []; - const processedOptions = processOptions(options); - const linter = new Linter({ - cwd: processedOptions.cwd, - configType: "flat" - }); - - const cacheFilePath = getCacheFile( - processedOptions.cacheLocation, - processedOptions.cwd - ); - - const lintResultCache = processedOptions.cache - ? new LintResultCache(cacheFilePath, processedOptions.cacheStrategy) - : null; - - privateMembers.set(this, { - options: processedOptions, - linter, - cacheFilePath, - lintResultCache, - defaultConfigs, - configs: null - }); - - /** - * If additional plugins are passed in, add that to the default - * configs for this instance. - */ - if (options.plugins) { - - const plugins = {}; - - for (const [pluginName, plugin] of Object.entries(options.plugins)) { - plugins[naming.getShorthandName(pluginName, "eslint-plugin")] = plugin; - } - - defaultConfigs.push({ - plugins - }); - } - - } - - /** - * The version text. - * @type {string} - */ - static get version() { - return version; - } - - /** - * Outputs fixes from the given results to files. - * @param {LintResult[]} results The lint results. - * @returns {Promise} Returns a promise that is used to track side effects. - */ - static async outputFixes(results) { - if (!Array.isArray(results)) { - throw new Error("'results' must be an array"); - } - - await Promise.all( - results - .filter(result => { - if (typeof result !== "object" || result === null) { - throw new Error("'results' must include only objects"); - } - return ( - typeof result.output === "string" && - path.isAbsolute(result.filePath) - ); - }) - .map(r => fs.writeFile(r.filePath, r.output)) - ); - } - - /** - * Returns results that only contains errors. - * @param {LintResult[]} results The results to filter. - * @returns {LintResult[]} The filtered results. - */ - static getErrorResults(results) { - const filtered = []; - - results.forEach(result => { - const filteredMessages = result.messages.filter(isErrorMessage); - const filteredSuppressedMessages = result.suppressedMessages.filter(isErrorMessage); - - if (filteredMessages.length > 0) { - filtered.push({ - ...result, - messages: filteredMessages, - suppressedMessages: filteredSuppressedMessages, - errorCount: filteredMessages.length, - warningCount: 0, - fixableErrorCount: result.fixableErrorCount, - fixableWarningCount: 0 - }); - } - }); - - return filtered; - } - - /** - * Returns meta objects for each rule represented in the lint results. - * @param {LintResult[]} results The results to fetch rules meta for. - * @returns {Object} A mapping of ruleIds to rule meta objects. - * @throws {TypeError} When the results object wasn't created from this ESLint instance. - * @throws {TypeError} When a plugin or rule is missing. - */ - getRulesMetaForResults(results) { - - // short-circuit simple case - if (results.length === 0) { - return {}; - } - - const resultRules = new Map(); - const { - configs, - options: { cwd } - } = privateMembers.get(this); - - /* - * We can only accurately return rules meta information for linting results if the - * results were created by this instance. Otherwise, the necessary rules data is - * not available. So if the config array doesn't already exist, just throw an error - * to let the user know we can't do anything here. - */ - if (!configs) { - throw createExtraneousResultsError(); - } - - for (const result of results) { - - /* - * Normalize filename for . - */ - const filePath = result.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); - - // ignore unknown rules - if (rule) { - resultRules.set(ruleId, rule); - } - } - } - - return createRulesMeta(resultRules); - } - - /** - * Executes the current configuration on an array of file and directory names. - * @param {string|string[]} patterns An array of file and directory names. - * @returns {Promise} The results of linting the file patterns given. - */ - async lintFiles(patterns) { - if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) { - throw new Error("'patterns' must be a non-empty string or an array of non-empty strings"); - } - - const { - cacheFilePath, - lintResultCache, - linter, - options: eslintOptions - } = privateMembers.get(this); - const configs = await calculateConfigArray(this, eslintOptions); - const { - allowInlineConfig, - cache, - cwd, - fix, - fixTypes, - globInputPaths, - errorOnUnmatchedPattern, - warnIgnored - } = eslintOptions; - const startTime = Date.now(); - const fixTypesSet = fixTypes ? new Set(fixTypes) : null; - - // Delete cache file; should this be done here? - if (!cache && cacheFilePath) { - debug(`Deleting cache file at ${cacheFilePath}`); - - try { - await fs.unlink(cacheFilePath); - } catch (error) { - const errorCode = error && error.code; - - // Ignore errors when no such file exists or file system is read only (and cache file does not exist) - if (errorCode !== "ENOENT" && !(errorCode === "EROFS" && !existsSync(cacheFilePath))) { - throw error; - } - } - } - - const filePaths = await findFiles({ - patterns: typeof patterns === "string" ? [patterns] : patterns, - cwd, - globInputPaths, - configs, - errorOnUnmatchedPattern - }); - - debug(`${filePaths.length} files found in: ${Date.now() - startTime}ms`); - - /* - * Because we need to process multiple files, including reading from disk, - * it is most efficient to start by reading each file via promises so that - * they can be done in parallel. Then, we can lint the returned text. This - * ensures we are waiting the minimum amount of time in between lints. - */ - const results = await Promise.all( - - filePaths.map(({ filePath, ignored }) => { - - /* - * If a filename was entered that matches an ignore - * pattern, then notify the user. - */ - if (ignored) { - if (warnIgnored) { - return createIgnoreResult(filePath, cwd); - } - - return void 0; - } - - const config = configs.getConfig(filePath); - - /* - * Sometimes a file found through a glob pattern will - * be ignored. In this case, `config` will be undefined - * and we just silently ignore the file. - */ - if (!config) { - return void 0; - } - - // Skip if there is cached result. - if (lintResultCache) { - const cachedResult = - lintResultCache.getCachedLintResults(filePath, config); - - if (cachedResult) { - const hadMessages = - cachedResult.messages && - cachedResult.messages.length > 0; - - if (hadMessages && fix) { - debug(`Reprocessing cached file to allow autofix: ${filePath}`); - } else { - debug(`Skipping file since it hasn't changed: ${filePath}`); - return cachedResult; - } - } - } - - - // set up fixer for fixTypes if necessary - let fixer = fix; - - if (fix && fixTypesSet) { - - // save original value of options.fix in case it's a function - const originalFix = (typeof fix === "function") - ? fix : () => true; - - fixer = message => shouldMessageBeFixed(message, config, fixTypesSet) && originalFix(message); - } - - return fs.readFile(filePath, "utf8") - .then(text => { - - // do the linting - const result = verifyText({ - text, - filePath, - configs, - cwd, - fix: fixer, - allowInlineConfig, - linter - }); - - /* - * Store the lint result in the LintResultCache. - * NOTE: The LintResultCache will remove the file source and any - * other properties that are difficult to serialize, and will - * hydrate those properties back in on future lint runs. - */ - if (lintResultCache) { - lintResultCache.setCachedLintResults(filePath, config, result); - } - - return result; - }); - - }) - ); - - // Persist the cache to disk. - if (lintResultCache) { - lintResultCache.reconcile(); - } - - const finalResults = results.filter(result => !!result); - - return processLintReport(this, { - results: finalResults - }); - } - - /** - * Executes the current configuration on text. - * @param {string} code A string of JavaScript code to lint. - * @param {Object} [options] The options. - * @param {string} [options.filePath] The path to the file of the source code. - * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path. - * @returns {Promise} The results of linting the string of code given. - */ - async lintText(code, options = {}) { - - // Parameter validation - - if (typeof code !== "string") { - throw new Error("'code' must be a string"); - } - - if (typeof options !== "object") { - throw new Error("'options' must be an object, null, or undefined"); - } - - // Options validation - - const { - filePath, - warnIgnored, - ...unknownOptions - } = options || {}; - - const unknownOptionKeys = Object.keys(unknownOptions); - - if (unknownOptionKeys.length > 0) { - throw new Error(`'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`); - } - - if (filePath !== void 0 && !isNonEmptyString(filePath)) { - throw new Error("'options.filePath' must be a non-empty string or undefined"); - } - - if (typeof warnIgnored !== "boolean" && typeof warnIgnored !== "undefined") { - throw new Error("'options.warnIgnored' must be a boolean or undefined"); - } - - // Now we can get down to linting - - const { - linter, - options: eslintOptions - } = privateMembers.get(this); - const configs = await calculateConfigArray(this, eslintOptions); - const { - allowInlineConfig, - cwd, - fix, - warnIgnored: constructorWarnIgnored - } = eslintOptions; - const results = []; - const startTime = Date.now(); - const resolvedFilename = path.resolve(cwd, filePath || "__placeholder__.js"); - - // Clear the last used config arrays. - if (resolvedFilename && await this.isPathIgnored(resolvedFilename)) { - const shouldWarnIgnored = typeof warnIgnored === "boolean" ? warnIgnored : constructorWarnIgnored; - - if (shouldWarnIgnored) { - results.push(createIgnoreResult(resolvedFilename, cwd)); - } - } else { - - // Do lint. - results.push(verifyText({ - text: code, - filePath: resolvedFilename.endsWith("__placeholder__.js") ? "" : resolvedFilename, - configs, - cwd, - fix, - allowInlineConfig, - linter - })); - } - - debug(`Linting complete in: ${Date.now() - startTime}ms`); - - return processLintReport(this, { - results - }); - - } - - /** - * Returns the formatter representing the given formatter name. - * @param {string} [name] The name of the formatter to load. - * The following values are allowed: - * - `undefined` ... Load `stylish` builtin formatter. - * - A builtin formatter name ... Load the builtin formatter. - * - A third-party formatter name: - * - `foo` → `eslint-formatter-foo` - * - `@foo` → `@foo/eslint-formatter` - * - `@foo/bar` → `@foo/eslint-formatter-bar` - * - A file path ... Load the file. - * @returns {Promise} A promise resolving to the formatter object. - * This promise will be rejected if the given formatter was not found or not - * a function. - */ - async loadFormatter(name = "stylish") { - if (typeof name !== "string") { - throw new Error("'name' must be a string"); - } - - // replace \ with / for Windows compatibility - const normalizedFormatName = name.replace(/\\/gu, "/"); - const namespace = naming.getNamespaceFromTerm(normalizedFormatName); - - // grab our options - const { cwd } = privateMembers.get(this).options; - - - let formatterPath; - - // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages) - if (!namespace && normalizedFormatName.includes("/")) { - formatterPath = path.resolve(cwd, normalizedFormatName); - } else { - try { - 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, getPlaceholderPath(cwd)); - } catch { - formatterPath = path.resolve(__dirname, "../", "cli-engine", "formatters", `${normalizedFormatName}.js`); - } - } - - let formatter; - - try { - formatter = (await import(pathToFileURL(formatterPath))).default; - } catch (ex) { - - // check for formatters that have been removed - if (removedFormatters.has(name)) { - ex.message = `The ${name} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${name}\``; - } else { - ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`; - } - - throw ex; - } - - - if (typeof formatter !== "function") { - throw new TypeError(`Formatter must be a function, but got a ${typeof formatter}.`); - } - - const eslint = this; - - return { - - /** - * 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, resultsMeta) { - let rulesMeta = null; - - results.sort(compareResultsByFilePath); - - return formatter(results, { - ...resultsMeta, - cwd, - get rulesMeta() { - if (!rulesMeta) { - rulesMeta = eslint.getRulesMetaForResults(results); - } - - return rulesMeta; - } - }); - } - }; - } - - /** - * Returns a configuration object for the given file based on the CLI options. - * This is the same logic used by the ESLint CLI executable to determine - * configuration for each file it processes. - * @param {string} filePath The path of the file to retrieve a config object for. - * @returns {Promise} A configuration object for the file - * or `undefined` if there is no configuration data for the object. - */ - async calculateConfigForFile(filePath) { - if (!isNonEmptyString(filePath)) { - throw new Error("'filePath' must be a non-empty string"); - } - const options = privateMembers.get(this).options; - const absolutePath = path.resolve(options.cwd, filePath); - const configs = await calculateConfigArray(this, options); - - 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. - * @returns {Promise} Whether or not the given path is ignored. - */ - async isPathIgnored(filePath) { - const config = await this.calculateConfigForFile(filePath); - - return config === void 0; - } -} - -/** - * The type of configuration used by this class. - * @type {string} - * @static - */ -FlatESLint.configType = "flat"; - -/** - * Returns whether flat config should be used. - * @param {Object} [options] The options for this function. - * @param {string} [options.cwd] The current working directory. - * @returns {Promise} Whether flat config should be used. - */ -async function shouldUseFlatConfig({ cwd = process.cwd() } = {}) { - 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(cwd)); - } -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = { - FlatESLint, - shouldUseFlatConfig -}; diff --git a/tools/node_modules/eslint/lib/eslint/index.js b/tools/node_modules/eslint/lib/eslint/index.js index 017b768ecd03ca..b7c52a4ea701b5 100644 --- a/tools/node_modules/eslint/lib/eslint/index.js +++ b/tools/node_modules/eslint/lib/eslint/index.js @@ -1,9 +1,9 @@ "use strict"; const { ESLint } = require("./eslint"); -const { FlatESLint } = require("./flat-eslint"); +const { LegacyESLint } = require("./legacy-eslint"); module.exports = { ESLint, - FlatESLint + LegacyESLint }; diff --git a/tools/node_modules/eslint/lib/eslint/legacy-eslint.js b/tools/node_modules/eslint/lib/eslint/legacy-eslint.js new file mode 100644 index 00000000000000..9c86163ef6348b --- /dev/null +++ b/tools/node_modules/eslint/lib/eslint/legacy-eslint.js @@ -0,0 +1,728 @@ +/** + * @fileoverview Main API Class + * @author Kai Cataldo + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const path = require("path"); +const fs = require("fs"); +const { promisify } = require("util"); +const { CLIEngine, getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); +const BuiltinRules = require("../rules"); +const { + Legacy: { + ConfigOps: { + getRuleSeverity + } + } +} = require("@eslint/eslintrc"); +const { version } = require("../../package.json"); + +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** @typedef {import("../cli-engine/cli-engine").LintReport} CLIEngineLintReport */ +/** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */ +/** @typedef {import("../shared/types").ConfigData} ConfigData */ +/** @typedef {import("../shared/types").LintMessage} LintMessage */ +/** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */ +/** @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 {(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise} format format function. + */ + +/** + * The options with which to configure the LegacyESLint instance. + * @typedef {Object} LegacyESLintOptions + * @property {boolean} [allowInlineConfig] Enable or disable inline configuration comments. + * @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this instance + * @property {boolean} [cache] Enable result caching. + * @property {string} [cacheLocation] The cache file to use instead of .eslintcache. + * @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files. + * @property {string} [cwd] The value to use for the current working directory. + * @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`. + * @property {string[]} [extensions] An array of file extensions to check. + * @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 {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance + * @property {string} [overrideConfigFile] The configuration file to use. + * @property {Record|null} [plugins] Preloaded plugins. This is a map-like object, keys are plugin IDs and each value is implementation. + * @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives. + * @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD. + * @property {string[]} [rulePaths] An array of directories to load custom rules from. + * @property {boolean} [useEslintrc] False disables looking for .eslintrc.* files. + * @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause + * the linting operation to short circuit and not report any failures. + */ + +/** + * A rules metadata object. + * @typedef {Object} RulesMeta + * @property {string} id The plugin ID. + * @property {Object} definition The plugin definition. + */ + +/** + * Private members for the `ESLint` instance. + * @typedef {Object} ESLintPrivateMembers + * @property {CLIEngine} cliEngine The wrapped CLIEngine instance. + * @property {LegacyESLintOptions} options The options used to instantiate the ESLint instance. + */ + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const writeFile = promisify(fs.writeFile); + +/** + * The map with which to store private class members. + * @type {WeakMap} + */ +const privateMembersMap = new WeakMap(); + +/** + * Check if a given value is a non-empty string or not. + * @param {any} value The value to check. + * @returns {boolean} `true` if `value` is a non-empty string. + */ +function isNonEmptyString(value) { + return typeof value === "string" && value.trim() !== ""; +} + +/** + * Check if a given value is an array of non-empty strings or not. + * @param {any} value The value to check. + * @returns {boolean} `true` if `value` is an array of non-empty strings. + */ +function isArrayOfNonEmptyString(value) { + return Array.isArray(value) && value.length && value.every(isNonEmptyString); +} + +/** + * Check if a given value is an empty array or an array of non-empty strings. + * @param {any} value The value to check. + * @returns {boolean} `true` if `value` is an empty array or an array of non-empty + * strings. + */ +function isEmptyArrayOrArrayOfNonEmptyString(value) { + return Array.isArray(value) && value.every(isNonEmptyString); +} + +/** + * Check if a given value is a valid fix type or not. + * @param {any} value The value to check. + * @returns {boolean} `true` if `value` is valid fix type. + */ +function isFixType(value) { + return value === "directive" || value === "problem" || value === "suggestion" || value === "layout"; +} + +/** + * Check if a given value is an array of fix types or not. + * @param {any} value The value to check. + * @returns {boolean} `true` if `value` is an array of fix types. + */ +function isFixTypeArray(value) { + return Array.isArray(value) && value.every(isFixType); +} + +/** + * The error for invalid options. + */ +class ESLintInvalidOptionsError extends Error { + constructor(messages) { + super(`Invalid Options:\n- ${messages.join("\n- ")}`); + this.code = "ESLINT_INVALID_OPTIONS"; + Error.captureStackTrace(this, ESLintInvalidOptionsError); + } +} + +/** + * Validates and normalizes options for the wrapped CLIEngine instance. + * @param {LegacyESLintOptions} options The options to process. + * @throws {ESLintInvalidOptionsError} If of any of a variety of type errors. + * @returns {LegacyESLintOptions} The normalized options. + */ +function processOptions({ + allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored. + baseConfig = null, + cache = false, + cacheLocation = ".eslintcache", + cacheStrategy = "metadata", + cwd = process.cwd(), + errorOnUnmatchedPattern = true, + extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature. + fix = false, + fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property. + globInputPaths = true, + ignore = true, + ignorePath = null, // ← should be null by default because if it's a string then it may throw ENOENT. + overrideConfig = null, + overrideConfigFile = null, + plugins = {}, + reportUnusedDisableDirectives = null, // ← should be null by default because if it's a string then it overrides the 'reportUnusedDisableDirectives' setting in config files. And we cannot use `overrideConfig.reportUnusedDisableDirectives` instead because we cannot configure the `error` severity with that. + resolvePluginsRelativeTo = null, // ← should be null by default because if it's a string then it suppresses RFC47 feature. + rulePaths = [], + useEslintrc = true, + passOnNoPatterns = false, + ...unknownOptions +}) { + const errors = []; + const unknownOptionKeys = Object.keys(unknownOptions); + + if (unknownOptionKeys.length >= 1) { + errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`); + if (unknownOptionKeys.includes("cacheFile")) { + errors.push("'cacheFile' has been removed. Please use the 'cacheLocation' option instead."); + } + if (unknownOptionKeys.includes("configFile")) { + errors.push("'configFile' has been removed. Please use the 'overrideConfigFile' option instead."); + } + if (unknownOptionKeys.includes("envs")) { + errors.push("'envs' has been removed. Please use the 'overrideConfig.env' option instead."); + } + if (unknownOptionKeys.includes("globals")) { + errors.push("'globals' has been removed. Please use the 'overrideConfig.globals' option instead."); + } + if (unknownOptionKeys.includes("ignorePattern")) { + errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead."); + } + if (unknownOptionKeys.includes("parser")) { + errors.push("'parser' has been removed. Please use the 'overrideConfig.parser' option instead."); + } + if (unknownOptionKeys.includes("parserOptions")) { + errors.push("'parserOptions' has been removed. Please use the 'overrideConfig.parserOptions' option instead."); + } + if (unknownOptionKeys.includes("rules")) { + errors.push("'rules' has been removed. Please use the 'overrideConfig.rules' option instead."); + } + } + if (typeof allowInlineConfig !== "boolean") { + errors.push("'allowInlineConfig' must be a boolean."); + } + if (typeof baseConfig !== "object") { + errors.push("'baseConfig' must be an object or null."); + } + if (typeof cache !== "boolean") { + errors.push("'cache' must be a boolean."); + } + if (!isNonEmptyString(cacheLocation)) { + errors.push("'cacheLocation' must be a non-empty string."); + } + if ( + cacheStrategy !== "metadata" && + cacheStrategy !== "content" + ) { + errors.push("'cacheStrategy' must be any of \"metadata\", \"content\"."); + } + if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) { + errors.push("'cwd' must be an absolute path."); + } + if (typeof errorOnUnmatchedPattern !== "boolean") { + errors.push("'errorOnUnmatchedPattern' must be a boolean."); + } + if (!isEmptyArrayOrArrayOfNonEmptyString(extensions) && extensions !== null) { + errors.push("'extensions' must be an array of non-empty strings or null."); + } + if (typeof fix !== "boolean" && typeof fix !== "function") { + errors.push("'fix' must be a boolean or a function."); + } + if (fixTypes !== null && !isFixTypeArray(fixTypes)) { + errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\"."); + } + if (typeof globInputPaths !== "boolean") { + errors.push("'globInputPaths' must be a boolean."); + } + if (typeof ignore !== "boolean") { + errors.push("'ignore' must be a boolean."); + } + if (!isNonEmptyString(ignorePath) && ignorePath !== null) { + errors.push("'ignorePath' must be a non-empty string or null."); + } + if (typeof overrideConfig !== "object") { + errors.push("'overrideConfig' must be an object or null."); + } + if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null) { + errors.push("'overrideConfigFile' must be a non-empty string or null."); + } + if (typeof plugins !== "object") { + errors.push("'plugins' must be an object or null."); + } else if (plugins !== null && Object.keys(plugins).includes("")) { + errors.push("'plugins' must not include an empty string."); + } + if (Array.isArray(plugins)) { + errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead."); + } + if ( + reportUnusedDisableDirectives !== "error" && + reportUnusedDisableDirectives !== "warn" && + reportUnusedDisableDirectives !== "off" && + reportUnusedDisableDirectives !== null + ) { + errors.push("'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null."); + } + if ( + !isNonEmptyString(resolvePluginsRelativeTo) && + resolvePluginsRelativeTo !== null + ) { + errors.push("'resolvePluginsRelativeTo' must be a non-empty string or null."); + } + if (!isEmptyArrayOrArrayOfNonEmptyString(rulePaths)) { + errors.push("'rulePaths' must be an array of non-empty strings."); + } + if (typeof useEslintrc !== "boolean") { + errors.push("'useEslintrc' must be a boolean."); + } + if (typeof passOnNoPatterns !== "boolean") { + errors.push("'passOnNoPatterns' must be a boolean."); + } + + if (errors.length > 0) { + throw new ESLintInvalidOptionsError(errors); + } + + return { + allowInlineConfig, + baseConfig, + cache, + cacheLocation, + cacheStrategy, + configFile: overrideConfigFile, + cwd: path.normalize(cwd), + errorOnUnmatchedPattern, + extensions, + fix, + fixTypes, + globInputPaths, + ignore, + ignorePath, + reportUnusedDisableDirectives, + resolvePluginsRelativeTo, + rulePaths, + useEslintrc, + passOnNoPatterns + }; +} + +/** + * Check if a value has one or more properties and that value is not undefined. + * @param {any} obj The value to check. + * @returns {boolean} `true` if `obj` has one or more properties that that value is not undefined. + */ +function hasDefinedProperty(obj) { + if (typeof obj === "object" && obj !== null) { + for (const key in obj) { + if (typeof obj[key] !== "undefined") { + return true; + } + } + } + return false; +} + +/** + * Create rulesMeta object. + * @param {Map} rules a map of rules from which to generate the object. + * @returns {Object} metadata for all enabled rules. + */ +function createRulesMeta(rules) { + return Array.from(rules).reduce((retVal, [id, rule]) => { + retVal[id] = rule.meta; + return retVal; + }, {}); +} + +/** @type {WeakMap} */ +const usedDeprecatedRulesCache = new WeakMap(); + +/** + * Create used deprecated rule list. + * @param {CLIEngine} cliEngine The CLIEngine instance. + * @param {string} maybeFilePath The absolute path to a lint target file or `""`. + * @returns {DeprecatedRuleInfo[]} The used deprecated rule list. + */ +function getOrFindUsedDeprecatedRules(cliEngine, maybeFilePath) { + const { + configArrayFactory, + options: { cwd } + } = getCLIEngineInternalSlots(cliEngine); + const filePath = path.isAbsolute(maybeFilePath) + ? maybeFilePath + : path.join(cwd, "__placeholder__.js"); + const configArray = configArrayFactory.getConfigArrayForFile(filePath); + const config = configArray.extractConfig(filePath); + + // Most files use the same config, so cache it. + if (!usedDeprecatedRulesCache.has(config)) { + const pluginRules = configArray.pluginRules; + const retv = []; + + for (const [ruleId, ruleConf] of Object.entries(config.rules)) { + if (getRuleSeverity(ruleConf) === 0) { + continue; + } + const rule = pluginRules.get(ruleId) || BuiltinRules.get(ruleId); + const meta = rule && rule.meta; + + if (meta && meta.deprecated) { + retv.push({ ruleId, replacedBy: meta.replacedBy || [] }); + } + } + + usedDeprecatedRulesCache.set(config, Object.freeze(retv)); + } + + return usedDeprecatedRulesCache.get(config); +} + +/** + * Processes the linting results generated by a CLIEngine linting report to + * match the ESLint class's API. + * @param {CLIEngine} cliEngine The CLIEngine instance. + * @param {CLIEngineLintReport} report The CLIEngine linting report to process. + * @returns {LintResult[]} The processed linting results. + */ +function processCLIEngineLintReport(cliEngine, { results }) { + const descriptor = { + configurable: true, + enumerable: true, + get() { + return getOrFindUsedDeprecatedRules(cliEngine, this.filePath); + } + }; + + for (const result of results) { + Object.defineProperty(result, "usedDeprecatedRules", descriptor); + } + + return results; +} + +/** + * An Array.prototype.sort() compatible compare function to order results by their file path. + * @param {LintResult} a The first lint result. + * @param {LintResult} b The second lint result. + * @returns {number} An integer representing the order in which the two results should occur. + */ +function compareResultsByFilePath(a, b) { + if (a.filePath < b.filePath) { + return -1; + } + + if (a.filePath > b.filePath) { + return 1; + } + + return 0; +} + +/** + * Main API. + */ +class LegacyESLint { + + /** + * The type of configuration used by this class. + * @type {string} + */ + static configType = "eslintrc"; + + /** + * Creates a new instance of the main ESLint API. + * @param {LegacyESLintOptions} options The options for this instance. + */ + constructor(options = {}) { + const processedOptions = processOptions(options); + const cliEngine = new CLIEngine(processedOptions, { preloadedPlugins: options.plugins }); + const { + configArrayFactory, + lastConfigArrays + } = getCLIEngineInternalSlots(cliEngine); + let updated = false; + + /* + * Address `overrideConfig` to set override config. + * Operate the `configArrayFactory` internal slot directly because this + * functionality doesn't exist as the public API of CLIEngine. + */ + if (hasDefinedProperty(options.overrideConfig)) { + configArrayFactory.setOverrideConfig(options.overrideConfig); + updated = true; + } + + // Update caches. + if (updated) { + configArrayFactory.clearCache(); + lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile(); + } + + // Initialize private properties. + privateMembersMap.set(this, { + cliEngine, + options: processedOptions + }); + } + + /** + * The version text. + * @type {string} + */ + static get version() { + return version; + } + + /** + * Outputs fixes from the given results to files. + * @param {LintResult[]} results The lint results. + * @returns {Promise} Returns a promise that is used to track side effects. + */ + static async outputFixes(results) { + if (!Array.isArray(results)) { + throw new Error("'results' must be an array"); + } + + await Promise.all( + results + .filter(result => { + if (typeof result !== "object" || result === null) { + throw new Error("'results' must include only objects"); + } + return ( + typeof result.output === "string" && + path.isAbsolute(result.filePath) + ); + }) + .map(r => writeFile(r.filePath, r.output)) + ); + } + + /** + * Returns results that only contains errors. + * @param {LintResult[]} results The results to filter. + * @returns {LintResult[]} The filtered results. + */ + static getErrorResults(results) { + return CLIEngine.getErrorResults(results); + } + + /** + * Returns meta objects for each rule represented in the lint results. + * @param {LintResult[]} results The results to fetch rules meta for. + * @returns {Object} A mapping of ruleIds to rule meta objects. + */ + getRulesMetaForResults(results) { + + const resultRuleIds = new Set(); + + // first gather all ruleIds from all results + + for (const result of results) { + for (const { ruleId } of result.messages) { + resultRuleIds.add(ruleId); + } + for (const { ruleId } of result.suppressedMessages) { + resultRuleIds.add(ruleId); + } + } + + // create a map of all rules in the results + + const { cliEngine } = privateMembersMap.get(this); + const rules = cliEngine.getRules(); + const resultRules = new Map(); + + for (const [ruleId, rule] of rules) { + if (resultRuleIds.has(ruleId)) { + resultRules.set(ruleId, rule); + } + } + + return createRulesMeta(resultRules); + + } + + /** + * Executes the current configuration on an array of file and directory names. + * @param {string[]} patterns An array of file and directory names. + * @returns {Promise} The results of linting the file patterns given. + */ + async lintFiles(patterns) { + const { cliEngine, options } = privateMembersMap.get(this); + + if (options.passOnNoPatterns && (patterns === "" || (Array.isArray(patterns) && patterns.length === 0))) { + return []; + } + + if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) { + throw new Error("'patterns' must be a non-empty string or an array of non-empty strings"); + } + + return processCLIEngineLintReport( + cliEngine, + cliEngine.executeOnFiles(patterns) + ); + } + + /** + * Executes the current configuration on text. + * @param {string} code A string of JavaScript code to lint. + * @param {Object} [options] The options. + * @param {string} [options.filePath] The path to the file of the source code. + * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path. + * @returns {Promise} The results of linting the string of code given. + */ + async lintText(code, options = {}) { + if (typeof code !== "string") { + throw new Error("'code' must be a string"); + } + if (typeof options !== "object") { + throw new Error("'options' must be an object, null, or undefined"); + } + const { + filePath, + warnIgnored = false, + ...unknownOptions + } = options || {}; + + const unknownOptionKeys = Object.keys(unknownOptions); + + if (unknownOptionKeys.length > 0) { + throw new Error(`'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`); + } + + if (filePath !== void 0 && !isNonEmptyString(filePath)) { + throw new Error("'options.filePath' must be a non-empty string or undefined"); + } + if (typeof warnIgnored !== "boolean") { + throw new Error("'options.warnIgnored' must be a boolean or undefined"); + } + + const { cliEngine } = privateMembersMap.get(this); + + return processCLIEngineLintReport( + cliEngine, + cliEngine.executeOnText(code, filePath, warnIgnored) + ); + } + + /** + * Returns the formatter representing the given formatter name. + * @param {string} [name] The name of the formatter to load. + * The following values are allowed: + * - `undefined` ... Load `stylish` builtin formatter. + * - A builtin formatter name ... Load the builtin formatter. + * - A third-party formatter name: + * - `foo` → `eslint-formatter-foo` + * - `@foo` → `@foo/eslint-formatter` + * - `@foo/bar` → `@foo/eslint-formatter-bar` + * - A file path ... Load the file. + * @returns {Promise} A promise resolving to the formatter object. + * This promise will be rejected if the given formatter was not found or not + * a function. + */ + async loadFormatter(name = "stylish") { + if (typeof name !== "string") { + throw new Error("'name' must be a string"); + } + + const { cliEngine, options } = privateMembersMap.get(this); + const formatter = cliEngine.getFormatter(name); + + if (typeof formatter !== "function") { + throw new Error(`Formatter must be a function, but got a ${typeof formatter}.`); + } + + return { + + /** + * 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, resultsMeta) { + let rulesMeta = null; + + results.sort(compareResultsByFilePath); + + return formatter(results, { + ...resultsMeta, + get cwd() { + return options.cwd; + }, + get rulesMeta() { + if (!rulesMeta) { + rulesMeta = createRulesMeta(cliEngine.getRules()); + } + + return rulesMeta; + } + }); + } + }; + } + + /** + * Returns a configuration object for the given file based on the CLI options. + * This is the same logic used by the ESLint CLI executable to determine + * configuration for each file it processes. + * @param {string} filePath The path of the file to retrieve a config object for. + * @returns {Promise} A configuration object for the file. + */ + async calculateConfigForFile(filePath) { + if (!isNonEmptyString(filePath)) { + throw new Error("'filePath' must be a non-empty string"); + } + const { cliEngine } = privateMembersMap.get(this); + + return cliEngine.getConfigForFile(filePath); + } + + /** + * Checks if a given path is ignored by ESLint. + * @param {string} filePath The path of the file to check. + * @returns {Promise} Whether or not the given path is ignored. + */ + async isPathIgnored(filePath) { + if (!isNonEmptyString(filePath)) { + throw new Error("'filePath' must be a non-empty string"); + } + const { cliEngine } = privateMembersMap.get(this); + + return cliEngine.isPathIgnored(filePath); + } +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + LegacyESLint, + + /** + * Get the private class members of a given ESLint instance for tests. + * @param {ESLint} instance The ESLint instance to get. + * @returns {ESLintPrivateMembers} The instance's private class members. + */ + getESLintPrivateMembers(instance) { + return privateMembersMap.get(instance); + } +}; diff --git a/tools/node_modules/eslint/lib/linter/apply-disable-directives.js b/tools/node_modules/eslint/lib/linter/apply-disable-directives.js index c5e3c9ddc1ced5..f511821dd53825 100644 --- a/tools/node_modules/eslint/lib/linter/apply-disable-directives.js +++ b/tools/node_modules/eslint/lib/linter/apply-disable-directives.js @@ -16,6 +16,11 @@ //------------------------------------------------------------------------------ const escapeRegExp = require("escape-string-regexp"); +const { + Legacy: { + ConfigOps + } +} = require("@eslint/eslintrc/universal"); /** * Compares the locations of two objects in a source file @@ -166,7 +171,7 @@ function createCommentRemoval(directives, commentToken) { return { description: ruleIds.length <= 2 ? ruleIds.join(" or ") - : `${ruleIds.slice(0, ruleIds.length - 1).join(", ")}, or ${ruleIds[ruleIds.length - 1]}`, + : `${ruleIds.slice(0, ruleIds.length - 1).join(", ")}, or ${ruleIds.at(-1)}`, fix: { range, text: " " @@ -337,7 +342,7 @@ function applyDirectives(options) { problem.suppressions = problem.suppressions.concat(suppressions); } else { problem.suppressions = suppressions; - usedDisableDirectives.add(disableDirectivesForProblem[disableDirectivesForProblem.length - 1]); + usedDisableDirectives.add(disableDirectivesForProblem.at(-1)); } } @@ -345,11 +350,11 @@ function applyDirectives(options) { } const unusedDisableDirectivesToReport = options.directives - .filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive)); + .filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive) && !options.rulesToIgnore.has(directive.ruleId)); const unusedEnableDirectivesToReport = new Set( - options.directives.filter(directive => directive.unprocessedDirective.type === "enable") + options.directives.filter(directive => directive.unprocessedDirective.type === "enable" && !options.rulesToIgnore.has(directive.ruleId)) ); /* @@ -410,11 +415,13 @@ function applyDirectives(options) { * @param {{ruleId: (string|null), line: number, column: number}[]} options.problems * A list of problems reported by rules, sorted by increasing location in the file, with one-based columns. * @param {"off" | "warn" | "error"} options.reportUnusedDisableDirectives If `"warn"` or `"error"`, adds additional problems for unused directives + * @param {Object} options.configuredRules The rules configuration. + * @param {Function} options.ruleFilter A predicate function to filter which rules should be executed. * @param {boolean} options.disableFixes If true, it doesn't make `fix` properties. * @returns {{ruleId: (string|null), line: number, column: number, suppressions?: {kind: string, justification: string}}[]} * An object with a list of reported problems, the suppressed of which contain the suppression information. */ -module.exports = ({ directives, disableFixes, problems, reportUnusedDisableDirectives = "off" }) => { +module.exports = ({ directives, disableFixes, problems, configuredRules, ruleFilter, reportUnusedDisableDirectives = "off" }) => { const blockDirectives = directives .filter(directive => directive.type === "disable" || directive.type === "enable") .map(directive => Object.assign({}, directive, { unprocessedDirective: directive })) @@ -443,17 +450,38 @@ module.exports = ({ directives, disableFixes, problems, reportUnusedDisableDirec } }).sort(compareLocations); + // This determines a list of rules that are not being run by the given ruleFilter, if present. + const rulesToIgnore = configuredRules && ruleFilter + ? new Set(Object.keys(configuredRules).filter(ruleId => { + const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]); + + // Ignore for disabled rules. + if (severity === 0) { + return false; + } + + return !ruleFilter({ severity, ruleId }); + })) + : new Set(); + + // If no ruleId is supplied that means this directive is applied to all rules, so we can't determine if it's unused if any rules are filtered out. + if (rulesToIgnore.size > 0) { + rulesToIgnore.add(null); + } + const blockDirectivesResult = applyDirectives({ problems, directives: blockDirectives, disableFixes, - reportUnusedDisableDirectives + reportUnusedDisableDirectives, + rulesToIgnore }); const lineDirectivesResult = applyDirectives({ problems: blockDirectivesResult.problems, directives: lineDirectives, disableFixes, - reportUnusedDisableDirectives + reportUnusedDisableDirectives, + rulesToIgnore }); return reportUnusedDisableDirectives !== "off" diff --git a/tools/node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js b/tools/node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js index b60e55c16dedcf..f5f26d0f470b01 100644 --- a/tools/node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/tools/node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js @@ -222,7 +222,6 @@ function forwardCurrentToHead(analyzer, node) { : "onUnreachableCodePathSegmentStart"; debug.dump(`${eventName} ${headSegment.id}`); - CodePathSegment.markUsed(headSegment); analyzer.emitter.emit( eventName, diff --git a/tools/node_modules/eslint/lib/linter/code-path-analysis/code-path.js b/tools/node_modules/eslint/lib/linter/code-path-analysis/code-path.js index 3bf570d754bfa9..8c438e29a31ec8 100644 --- a/tools/node_modules/eslint/lib/linter/code-path-analysis/code-path.js +++ b/tools/node_modules/eslint/lib/linter/code-path-analysis/code-path.js @@ -125,20 +125,6 @@ class CodePath { return this.internal.thrownForkContext; } - /** - * Tracks the traversal of the code path through each segment. This array - * starts empty and segments are added or removed as the code path is - * traversed. This array always ends up empty at the end of a code path - * traversal. The `CodePathState` uses this to track its progress through - * the code path. - * This is a passthrough to the underlying `CodePathState`. - * @type {CodePathSegment[]} - * @deprecated - */ - get currentSegments() { - return this.internal.currentSegments; - } - /** * Traverses all segments in this code path. * @@ -180,9 +166,9 @@ class CodePath { const lastSegment = resolvedOptions.last; // set up initial location information - let record = null; - let index = 0; - let end = 0; + let record; + let index; + let end; let segment = null; // segments that have already been visited during traversal @@ -191,8 +177,8 @@ class CodePath { // tracks the traversal steps const stack = [[startSegment, 0]]; - // tracks the last skipped segment during traversal - let skippedSegment = null; + // segments that have been skipped during traversal + const skipped = new Set(); // indicates if we exited early from the traversal let broken = false; @@ -207,11 +193,7 @@ class CodePath { * @returns {void} */ skip() { - if (stack.length <= 1) { - broken = true; - } else { - skippedSegment = stack[stack.length - 2][0]; - } + skipped.add(segment); }, /** @@ -236,6 +218,18 @@ class CodePath { ); } + /** + * Checks if a given previous segment has been skipped. + * @param {CodePathSegment} prevSegment A previous segment to check. + * @returns {boolean} `true` if the segment has been skipped. + */ + function isSkipped(prevSegment) { + return ( + skipped.has(prevSegment) || + segment.isLoopedPrevSegment(prevSegment) + ); + } + // the traversal while (stack.length > 0) { @@ -251,7 +245,7 @@ class CodePath { * Otherwise, we just read the value and sometimes modify the * record as we traverse. */ - record = stack[stack.length - 1]; + record = stack.at(-1); segment = record[0]; index = record[1]; @@ -272,17 +266,21 @@ class CodePath { continue; } - // Reset the skipping flag if all branches have been skipped. - if (skippedSegment && segment.prevSegments.includes(skippedSegment)) { - skippedSegment = null; - } visited.add(segment); + + // Skips the segment if all previous segments have been skipped. + const shouldSkip = ( + skipped.size > 0 && + segment.prevSegments.length > 0 && + segment.prevSegments.every(isSkipped) + ); + /* * If the most recent segment hasn't been skipped, then we call * the callback, passing in the segment and the controller. */ - if (!skippedSegment) { + if (!shouldSkip) { resolvedCallback.call(this, segment, controller); // exit if we're at the last segment @@ -298,6 +296,10 @@ class CodePath { if (broken) { break; } + } else { + + // If the most recent segment has been skipped, then mark it as skipped. + skipped.add(segment); } } diff --git a/tools/node_modules/eslint/lib/linter/code-path-analysis/fork-context.js b/tools/node_modules/eslint/lib/linter/code-path-analysis/fork-context.js index 33140272f53b42..695d3bfa7e273d 100644 --- a/tools/node_modules/eslint/lib/linter/code-path-analysis/fork-context.js +++ b/tools/node_modules/eslint/lib/linter/code-path-analysis/fork-context.js @@ -207,7 +207,7 @@ class ForkContext { get head() { const list = this.segmentsList; - return list.length === 0 ? [] : list[list.length - 1]; + return list.length === 0 ? [] : list.at(-1); } /** diff --git a/tools/node_modules/eslint/lib/linter/config-comment-parser.js b/tools/node_modules/eslint/lib/linter/config-comment-parser.js index 9d33c55273cb25..aaa56e1daa9641 100644 --- a/tools/node_modules/eslint/lib/linter/config-comment-parser.js +++ b/tools/node_modules/eslint/lib/linter/config-comment-parser.js @@ -40,7 +40,7 @@ module.exports = class ConfigCommentParser { /** * Parses a list of "name:string_value" or/and "name" options divided by comma or - * whitespace. Used for "global" and "exported" comments. + * whitespace. Used for "global" comments. * @param {string} string The string to parse. * @param {Comment} comment The comment node which has the string. * @returns {Object} Result map object of names and string values, or null values if no value was provided @@ -75,11 +75,9 @@ module.exports = class ConfigCommentParser { parseJsonConfig(string, location) { debug("Parsing JSON config"); - let items = {}; - // Parses a JSON-like comment by the same way as parsing CLI option. try { - items = levn.parse("Object", string) || {}; + const items = levn.parse("Object", string) || {}; // Some tests say that it should ignore invalid comments such as `/*eslint no-alert:abc*/`. // Also, commaless notations have invalid severity: @@ -102,11 +100,15 @@ module.exports = class ConfigCommentParser { * Optionator cannot parse commaless notations. * But we are supporting that. So this is a fallback for that. */ - items = {}; const normalizedString = string.replace(/([-a-zA-Z0-9/]+):/gu, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/u, "$1,"); try { - items = JSON.parse(`{${normalizedString}}`); + const items = JSON.parse(`{${normalizedString}}`); + + return { + success: true, + config: items + }; } catch (ex) { debug("Manual parsing failed."); @@ -124,11 +126,6 @@ module.exports = class ConfigCommentParser { }; } - - return { - success: true, - config: items - }; } /** diff --git a/tools/node_modules/eslint/lib/linter/index.js b/tools/node_modules/eslint/lib/linter/index.js index 25fd769bde997f..9e5397768bb99f 100644 --- a/tools/node_modules/eslint/lib/linter/index.js +++ b/tools/node_modules/eslint/lib/linter/index.js @@ -1,13 +1,11 @@ "use strict"; const { Linter } = require("./linter"); -const interpolate = require("./interpolate"); const SourceCodeFixer = require("./source-code-fixer"); module.exports = { Linter, // For testers. - SourceCodeFixer, - interpolate + SourceCodeFixer }; diff --git a/tools/node_modules/eslint/lib/linter/interpolate.js b/tools/node_modules/eslint/lib/linter/interpolate.js index 87e06a0236991d..5f4ff92273633c 100644 --- a/tools/node_modules/eslint/lib/linter/interpolate.js +++ b/tools/node_modules/eslint/lib/linter/interpolate.js @@ -9,13 +9,30 @@ // Public Interface //------------------------------------------------------------------------------ -module.exports = (text, data) => { +/** + * Returns a global expression matching placeholders in messages. + * @returns {RegExp} Global regular expression matching placeholders + */ +function getPlaceholderMatcher() { + return /\{\{([^{}]+?)\}\}/gu; +} + +/** + * Replaces {{ placeholders }} in the message with the provided data. + * Does not replace placeholders not available in the data. + * @param {string} text Original message with potential placeholders + * @param {Record} data Map of placeholder name to its value + * @returns {string} Message with replaced placeholders + */ +function interpolate(text, data) { if (!data) { return text; } + const matcher = getPlaceholderMatcher(); + // Substitution content for any {{ }} markers. - return text.replace(/\{\{([^{}]+?)\}\}/gu, (fullMatch, termWithWhitespace) => { + return text.replace(matcher, (fullMatch, termWithWhitespace) => { const term = termWithWhitespace.trim(); if (term in data) { @@ -25,4 +42,9 @@ module.exports = (text, data) => { // Preserve old behavior: If parameter name not provided, don't replace it. return fullMatch; }); +} + +module.exports = { + getPlaceholderMatcher, + interpolate }; diff --git a/tools/node_modules/eslint/lib/linter/linter.js b/tools/node_modules/eslint/lib/linter/linter.js index f74d0ecd13f2e2..22cf17b54ba4ff 100644 --- a/tools/node_modules/eslint/lib/linter/linter.js +++ b/tools/node_modules/eslint/lib/linter/linter.js @@ -30,7 +30,6 @@ const } = require("@eslint/eslintrc/universal"), Traverser = require("../shared/traverser"), { SourceCode } = require("../source-code"), - CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"), applyDisableDirectives = require("./apply-disable-directives"), ConfigCommentParser = require("./config-comment-parser"), NodeEventGenerator = require("./node-event-generator"), @@ -42,8 +41,9 @@ const ruleReplacements = require("../../conf/replacements.json"); const { getRuleFromConfig } = require("../config/flat-config-helpers"); const { FlatConfigArray } = require("../config/flat-config-array"); +const { startTime, endTime } = require("../shared/stats"); const { RuleValidator } = require("../config/rule-validator"); -const { assertIsRuleOptions, assertIsRuleSeverity } = require("../config/flat-config-schema"); +const { assertIsRuleSeverity } = require("../config/flat-config-schema"); const { normalizeSeverityToString } = require("../shared/severity"); const debug = require("debug")("eslint:linter"); const MAX_AUTOFIX_PASSES = 10; @@ -52,13 +52,14 @@ const DEFAULT_ECMA_VERSION = 5; const commentParser = new ConfigCommentParser(); const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }; const parserSymbol = Symbol.for("eslint.RuleTester.parser"); +const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version"); +const STEP_KIND_VISIT = 1; +const STEP_KIND_CALL = 2; //------------------------------------------------------------------------------ // Typedefs //------------------------------------------------------------------------------ -/** @typedef {InstanceType} ConfigArray */ -/** @typedef {InstanceType} ExtractedConfig */ /** @typedef {import("../shared/types").ConfigData} ConfigData */ /** @typedef {import("../shared/types").Environment} Environment */ /** @typedef {import("../shared/types").GlobalConf} GlobalConf */ @@ -68,6 +69,7 @@ const parserSymbol = Symbol.for("eslint.RuleTester.parser"); /** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */ /** @typedef {import("../shared/types").Processor} Processor */ /** @typedef {import("../shared/types").Rule} Rule */ +/** @typedef {import("../shared/types").Times} Times */ /* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */ /** @@ -92,6 +94,7 @@ const parserSymbol = Symbol.for("eslint.RuleTester.parser"); * @property {SourceCode|null} lastSourceCode The `SourceCode` instance that the last `verify()` call used. * @property {SuppressedLintMessage[]} lastSuppressedMessages The `SuppressedLintMessage[]` instance that the last `verify()` call produced. * @property {Map} parserMap The loaded parsers. + * @property {Times} times The times spent on applying a rule to a file (see `stats` option). * @property {Rules} ruleMap The loaded rules. */ @@ -105,6 +108,7 @@ const parserSymbol = Symbol.for("eslint.RuleTester.parser"); * @property {string} [filename] the filename of the source code. * @property {boolean | "off" | "warn" | "error"} [reportUnusedDisableDirectives] Adds reported errors for * unused `eslint-disable` directives. + * @property {Function} [ruleFilter] A predicate function that determines whether a given rule should run. */ /** @@ -230,7 +234,7 @@ function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, ena * @private */ function createMissingRuleMessage(ruleId) { - return Object.prototype.hasOwnProperty.call(ruleReplacements.rules, ruleId) + return Object.hasOwn(ruleReplacements.rules, ruleId) ? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements.rules[ruleId].join(", ")}` : `Definition for rule '${ruleId}' was not found.`; } @@ -324,10 +328,11 @@ function createDisableDirectives(options) { * @param {SourceCode} sourceCode The SourceCode object to get comments from. * @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. + * @param {ConfigData} config Provided config. * @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(sourceCode, ruleMapper, warnInlineConfig) { +function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config) { const configuredRules = {}; const enabledGlobals = Object.create(null); const exportedVariables = {}; @@ -392,7 +397,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) { } case "exported": - Object.assign(exportedVariables, commentParser.parseStringConfig(directiveValue, comment)); + Object.assign(exportedVariables, commentParser.parseListConfig(directiveValue, comment)); break; case "globals": @@ -436,9 +441,68 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) { return; } + if (Object.hasOwn(configuredRules, name)) { + problems.push(createLintingProblem({ + message: `Rule "${name}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`, + loc: comment.loc + })); + return; + } + + let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue]; + + /* + * If the rule was already configured, inline rule configuration that + * only has severity should retain options from the config and just override the severity. + * + * Example: + * + * { + * rules: { + * curly: ["error", "multi"] + * } + * } + * + * /* eslint curly: ["warn"] * / + * + * Results in: + * + * curly: ["warn", "multi"] + */ + if ( + + /* + * If inline config for the rule has only severity + */ + ruleOptions.length === 1 && + + /* + * And the rule was already configured + */ + config.rules && Object.hasOwn(config.rules, name) + ) { + + /* + * Then use severity from the inline config and options from the provided config + */ + ruleOptions = [ + ruleOptions[0], // severity from the inline config + ...Array.isArray(config.rules[name]) ? config.rules[name].slice(1) : [] // options from the provided config + ]; + } + try { - validator.validateRuleOptions(rule, name, ruleValue); + validator.validateRuleOptions(rule, name, ruleOptions); } catch (err) { + + /* + * If the rule has invalid `meta.schema`, throw the error because + * this is not an invalid inline configuration but an invalid rule. + */ + if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") { + throw err; + } + problems.push(createLintingProblem({ ruleId: name, message: err.message, @@ -449,7 +513,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) { return; } - configuredRules[name] = ruleValue; + configuredRules[name] = ruleOptions; }); } else { problems.push(parseResult.error); @@ -584,7 +648,7 @@ function normalizeEcmaVersionForLanguageOptions(ecmaVersion) { * that is used for a number of processes inside of ESLint. It's normally * safe to assume people want the latest unless otherwise specified. */ - return espree.latestEcmaVersion + 2009; + return LATEST_ECMA_VERSION; } const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu; @@ -661,6 +725,12 @@ function normalizeVerifyOptions(providedOptions, config) { } } + let ruleFilter = providedOptions.ruleFilter; + + if (typeof ruleFilter !== "function") { + ruleFilter = () => true; + } + return { filename: normalizeFilename(providedOptions.filename || ""), allowInlineConfig: !ignoreInlineConfig, @@ -668,7 +738,9 @@ function normalizeVerifyOptions(providedOptions, config) { ? `your config${configNameOfNoInlineConfig}` : null, reportUnusedDisableDirectives, - disableFixes: Boolean(providedOptions.disableFixes) + disableFixes: Boolean(providedOptions.disableFixes), + stats: providedOptions.stats, + ruleFilter }; } @@ -757,6 +829,36 @@ function stripUnicodeBOM(text) { return text; } +/** + * Store time measurements in map + * @param {number} time Time measurement + * @param {Object} timeOpts Options relating which time was measured + * @param {WeakMap} slots Linter internal slots map + * @returns {void} + */ +function storeTime(time, timeOpts, slots) { + const { type, key } = timeOpts; + + if (!slots.times) { + slots.times = { passes: [{}] }; + } + + const passIndex = slots.fixPasses; + + if (passIndex > slots.times.passes.length - 1) { + slots.times.passes.push({}); + } + + if (key) { + slots.times.passes[passIndex][type] ??= {}; + slots.times.passes[passIndex][type][key] ??= { total: 0 }; + slots.times.passes[passIndex][type][key].total += time; + } else { + slots.times.passes[passIndex][type] ??= { total: 0 }; + slots.times.passes[passIndex][type].total += time; + } +} + /** * Get the options for a rule (not including severity), if any * @param {Array|number} ruleConfig rule configuration @@ -885,12 +987,18 @@ function parse(text, languageOptions, filePath) { /** * Runs a rule, and gets its listeners - * @param {Rule} rule A normalized rule with a `create` method + * @param {Rule} rule A rule object * @param {Context} ruleContext The context that should be passed to the rule + * @throws {TypeError} If `rule` is not an object with a `create` method * @throws {any} Any error during the rule's `create` * @returns {Object} A map of selector listeners provided by the rule */ function createRuleListeners(rule, ruleContext) { + + if (!rule || typeof rule !== "object" || typeof rule.create !== "function") { + throw new TypeError(`Error while loading rule '${ruleContext.id}': Rule must be an object with a \`create\` method`); + } + try { return rule.create(ruleContext); } catch (ex) { @@ -899,43 +1007,6 @@ function createRuleListeners(rule, ruleContext) { } } -// methods that exist on SourceCode object -const DEPRECATED_SOURCECODE_PASSTHROUGHS = { - getSource: "getText", - getSourceLines: "getLines", - getAllComments: "getAllComments", - getNodeByRangeIndex: "getNodeByRangeIndex", - getComments: "getComments", - getCommentsBefore: "getCommentsBefore", - getCommentsAfter: "getCommentsAfter", - getCommentsInside: "getCommentsInside", - getJSDocComment: "getJSDocComment", - getFirstToken: "getFirstToken", - getFirstTokens: "getFirstTokens", - getLastToken: "getLastToken", - getLastTokens: "getLastTokens", - getTokenAfter: "getTokenAfter", - getTokenBefore: "getTokenBefore", - getTokenByRangeStart: "getTokenByRangeStart", - getTokens: "getTokens", - getTokensAfter: "getTokensAfter", - getTokensBefore: "getTokensBefore", - getTokensBetween: "getTokensBetween" -}; - - -const BASE_TRAVERSAL_CONTEXT = Object.freeze( - Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce( - (contextInfo, methodName) => - Object.assign(contextInfo, { - [methodName](...args) { - return this.sourceCode[DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]](...args); - } - }), - {} - ) -); - /** * Runs the given rules on the given SourceCode object * @param {SourceCode} sourceCode A SourceCode object for the given text @@ -948,23 +1019,18 @@ 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 + * @param {Function} ruleFilter A predicate function to filter which rules should be executed. + * @param {boolean} stats If true, stats are collected appended to the result + * @param {WeakMap} slots InternalSlotsMap of linter * @returns {LintMessage[]} An array of reported problems + * @throws {Error} If traversal into a node fails. */ -function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename) { +function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename, ruleFilter, + stats, slots) { const emitter = createEmitter(); - const nodeQueue = []; - let currentNode = sourceCode.ast; - - Traverser.traverse(sourceCode.ast, { - enter(node, parent) { - node.parent = parent; - nodeQueue.push({ isEntering: true, node }); - }, - leave(node) { - nodeQueue.push({ isEntering: false, node }); - }, - visitorKeys: sourceCode.visitorKeys - }); + + // must happen first to assign all node.parent properties + const eventQueue = sourceCode.traverse(); /* * Create a frozen object with the ruleContext properties and methods that are shared by all rules. @@ -972,30 +1038,22 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO * properties once for each rule. */ const sharedTraversalContext = Object.freeze( - Object.assign( - Object.create(BASE_TRAVERSAL_CONTEXT), - { - getAncestors: () => sourceCode.getAncestors(currentNode), - getDeclaredVariables: node => sourceCode.getDeclaredVariables(node), - getCwd: () => cwd, - cwd, - getFilename: () => filename, - filename, - getPhysicalFilename: () => physicalFilename || filename, - physicalFilename: physicalFilename || filename, - getScope: () => sourceCode.getScope(currentNode), - getSourceCode: () => sourceCode, - sourceCode, - markVariableAsUsed: name => sourceCode.markVariableAsUsed(name, currentNode), - parserOptions: { - ...languageOptions.parserOptions - }, - parserPath: parserName, - languageOptions, - parserServices: sourceCode.parserServices, - settings - } - ) + { + getCwd: () => cwd, + cwd, + getFilename: () => filename, + filename, + getPhysicalFilename: () => physicalFilename || filename, + physicalFilename: physicalFilename || filename, + getSourceCode: () => sourceCode, + sourceCode, + parserOptions: { + ...languageOptions.parserOptions + }, + parserPath: parserName, + languageOptions, + settings + } ); const lintingProblems = []; @@ -1008,6 +1066,10 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO return; } + if (ruleFilter && !ruleFilter({ ruleId, severity })) { + return; + } + const rule = ruleMapper(ruleId); if (!rule) { @@ -1063,7 +1125,14 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO ) ); - const ruleListeners = timing.enabled ? timing.time(ruleId, createRuleListeners)(rule, ruleContext) : createRuleListeners(rule, ruleContext); + const ruleListenersReturn = (timing.enabled || stats) + ? timing.time(ruleId, createRuleListeners, stats)(rule, ruleContext) : createRuleListeners(rule, ruleContext); + + const ruleListeners = stats ? ruleListenersReturn.result : ruleListenersReturn; + + if (stats) { + storeTime(ruleListenersReturn.tdiff, { type: "rules", key: ruleId }, slots); + } /** * Include `ruleId` in error logs @@ -1073,7 +1142,15 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO function addRuleErrorHandler(ruleListener) { return function ruleErrorHandler(...listenerArgs) { try { - return ruleListener(...listenerArgs); + const ruleListenerReturn = ruleListener(...listenerArgs); + + const ruleListenerResult = stats ? ruleListenerReturn.result : ruleListenerReturn; + + if (stats) { + storeTime(ruleListenerReturn.tdiff, { type: "rules", key: ruleId }, slots); + } + + return ruleListenerResult; } catch (e) { e.ruleId = ruleId; throw e; @@ -1087,9 +1164,8 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO // add all the selectors from the rule as listeners Object.keys(ruleListeners).forEach(selector => { - const ruleListener = timing.enabled - ? timing.time(ruleId, ruleListeners[selector]) - : ruleListeners[selector]; + const ruleListener = (timing.enabled || stats) + ? timing.time(ruleId, ruleListeners[selector], stats) : ruleListeners[selector]; emitter.on( selector, @@ -1098,25 +1174,34 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO }); }); - // only run code path analyzer if the top level node is "Program", skip otherwise - const eventGenerator = nodeQueue[0].node.type === "Program" - ? new CodePathAnalyzer(new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys })) - : new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys }); + const eventGenerator = new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys }); - nodeQueue.forEach(traversalInfo => { - currentNode = traversalInfo.node; + for (const step of eventQueue) { + switch (step.kind) { + case STEP_KIND_VISIT: { + try { + if (step.phase === 1) { + eventGenerator.enterNode(step.target); + } else { + eventGenerator.leaveNode(step.target); + } + } catch (err) { + err.currentNode = step.target; + throw err; + } + break; + } - try { - if (traversalInfo.isEntering) { - eventGenerator.enterNode(currentNode); - } else { - eventGenerator.leaveNode(currentNode); + case STEP_KIND_CALL: { + emitter.emit(step.target, ...step.args); + break; } - } catch (err) { - err.currentNode = currentNode; - throw err; + + default: + throw new Error(`Invalid traversal step found: "${step.type}".`); } - }); + + } return lintingProblems; } @@ -1202,7 +1287,6 @@ function assertEslintrcConfig(linter) { } } - //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -1217,9 +1301,9 @@ class Linter { * Initialize the Linter. * @param {Object} [config] the config object * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined. - * @param {"flat"|"eslintrc"} [config.configType="eslintrc"] the type of config used. + * @param {"flat"|"eslintrc"} [config.configType="flat"] the type of config used. */ - constructor({ cwd, configType } = {}) { + constructor({ cwd, configType = "flat" } = {}) { internalSlotsMap.set(this, { cwd: normalizeCwd(cwd), lastConfigArray: null, @@ -1308,12 +1392,25 @@ class Linter { }); if (!slots.lastSourceCode) { + let t; + + if (options.stats) { + t = startTime(); + } + const parseResult = parse( text, languageOptions, options.filename ); + if (options.stats) { + const time = endTime(t); + const timeOpts = { type: "parse" }; + + storeTime(time, timeOpts, slots); + } + if (!parseResult.success) { return [parseResult.error]; } @@ -1338,7 +1435,7 @@ class Linter { const sourceCode = slots.lastSourceCode; const commentDirectives = options.allowInlineConfig - ? getDirectiveComments(sourceCode, ruleId => getRule(slots, ruleId), options.warnInlineConfig) + ? getDirectiveComments(sourceCode, ruleId => getRule(slots, ruleId), options.warnInlineConfig, config) : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] }; // augment global scope with declared global variables @@ -1349,6 +1446,7 @@ class Linter { ); const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules); + let lintingProblems; try { @@ -1362,7 +1460,10 @@ class Linter { options.filename, options.disableFixes, slots.cwd, - providedOptions.physicalFilename + providedOptions.physicalFilename, + null, + options.stats, + slots ); } catch (err) { err.message += `\nOccurred while linting ${options.filename}`; @@ -1413,29 +1514,29 @@ class Linter { ? { filename: filenameOrOptions } : filenameOrOptions || {}; - if (config) { - if (configType === "flat") { - - /* - * Because of how Webpack packages up the files, we can't - * compare directly to `FlatConfigArray` using `instanceof` - * because it's not the same `FlatConfigArray` as in the tests. - * So, we work around it by assuming an array is, in fact, a - * `FlatConfigArray` if it has a `getConfig()` method. - */ - let configArray = config; - - if (!Array.isArray(config) || typeof config.getConfig !== "function") { - configArray = new FlatConfigArray(config, { basePath: cwd }); - configArray.normalizeSync(); - } + const configToUse = config ?? {}; - return this._distinguishSuppressedMessages(this._verifyWithFlatConfigArray(textOrSourceCode, configArray, options, true)); - } + if (configType !== "eslintrc") { - if (typeof config.extractConfig === "function") { - return this._distinguishSuppressedMessages(this._verifyWithConfigArray(textOrSourceCode, config, options)); + /* + * Because of how Webpack packages up the files, we can't + * compare directly to `FlatConfigArray` using `instanceof` + * because it's not the same `FlatConfigArray` as in the tests. + * So, we work around it by assuming an array is, in fact, a + * `FlatConfigArray` if it has a `getConfig()` method. + */ + let configArray = configToUse; + + if (!Array.isArray(configToUse) || typeof configToUse.getConfig !== "function") { + configArray = new FlatConfigArray(configToUse, { basePath: cwd }); + configArray.normalizeSync(); } + + return this._distinguishSuppressedMessages(this._verifyWithFlatConfigArray(textOrSourceCode, configArray, options, true)); + } + + if (typeof configToUse.extractConfig === "function") { + return this._distinguishSuppressedMessages(this._verifyWithConfigArray(textOrSourceCode, configToUse, options)); } /* @@ -1448,9 +1549,9 @@ class Linter { * So we cannot apply multiple processors. */ if (options.preprocess || options.postprocess) { - return this._distinguishSuppressedMessages(this._verifyWithProcessor(textOrSourceCode, config, options)); + return this._distinguishSuppressedMessages(this._verifyWithProcessor(textOrSourceCode, configToUse, options)); } - return this._distinguishSuppressedMessages(this._verifyWithoutProcessors(textOrSourceCode, config, options)); + return this._distinguishSuppressedMessages(this._verifyWithoutProcessors(textOrSourceCode, configToUse, options)); } /** @@ -1590,12 +1691,24 @@ class Linter { const settings = config.settings || {}; if (!slots.lastSourceCode) { + let t; + + if (options.stats) { + t = startTime(); + } + const parseResult = parse( text, languageOptions, options.filename ); + if (options.stats) { + const time = endTime(t); + + storeTime(time, { type: "parse" }, slots); + } + if (!parseResult.success) { return [parseResult.error]; } @@ -1677,22 +1790,88 @@ class Linter { return; } + if (Object.hasOwn(mergedInlineConfig.rules, ruleId)) { + inlineConfigProblems.push(createLintingProblem({ + message: `Rule "${ruleId}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`, + loc: node.loc + })); + return; + } + try { - const ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue]; + let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue]; - assertIsRuleOptions(ruleId, ruleValue); assertIsRuleSeverity(ruleId, ruleOptions[0]); - ruleValidator.validate({ - plugins: config.plugins, - rules: { - [ruleId]: ruleOptions + /* + * If the rule was already configured, inline rule configuration that + * only has severity should retain options from the config and just override the severity. + * + * Example: + * + * { + * rules: { + * curly: ["error", "multi"] + * } + * } + * + * /* eslint curly: ["warn"] * / + * + * Results in: + * + * curly: ["warn", "multi"] + */ + + let shouldValidateOptions = true; + + if ( + + /* + * If inline config for the rule has only severity + */ + ruleOptions.length === 1 && + + /* + * And the rule was already configured + */ + config.rules && Object.hasOwn(config.rules, ruleId) + ) { + + /* + * Then use severity from the inline config and options from the provided config + */ + ruleOptions = [ + ruleOptions[0], // severity from the inline config + ...config.rules[ruleId].slice(1) // options from the provided config + ]; + + // if the rule was enabled, the options have already been validated + if (config.rules[ruleId][0] > 0) { + shouldValidateOptions = false; } - }); - mergedInlineConfig.rules[ruleId] = ruleValue; + } + + if (shouldValidateOptions) { + ruleValidator.validate({ + plugins: config.plugins, + rules: { + [ruleId]: ruleOptions + } + }); + } + + mergedInlineConfig.rules[ruleId] = ruleOptions; } catch (err) { + /* + * If the rule has invalid `meta.schema`, throw the error because + * this is not an invalid inline configuration but an invalid rule. + */ + if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") { + throw err; + } + let baseMessage = err.message.slice( err.message.startsWith("Key \"rules\":") ? err.message.indexOf(":", 12) + 1 @@ -1722,6 +1901,7 @@ class Linter { : { problems: [], disableDirectives: [] }; const configuredRules = Object.assign({}, config.rules, mergedInlineConfig.rules); + let lintingProblems; sourceCode.finalize(); @@ -1737,7 +1917,10 @@ class Linter { options.filename, options.disableFixes, slots.cwd, - providedOptions.physicalFilename + providedOptions.physicalFilename, + options.ruleFilter, + options.stats, + slots ); } catch (err) { err.message += `\nOccurred while linting ${options.filename}`; @@ -1768,7 +1951,9 @@ class Linter { .concat(commentDirectives.problems) .concat(inlineConfigProblems) .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column), - reportUnusedDisableDirectives: options.reportUnusedDisableDirectives + reportUnusedDisableDirectives: options.reportUnusedDisableDirectives, + ruleFilter: options.ruleFilter, + configuredRules }); } @@ -1975,6 +2160,22 @@ class Linter { return internalSlotsMap.get(this).lastSourceCode; } + /** + * Gets the times spent on (parsing, fixing, linting) a file. + * @returns {LintTimes} The times. + */ + getTimes() { + return internalSlotsMap.get(this).times ?? { passes: [] }; + } + + /** + * Gets the number of autofix passes that were made in the last run. + * @returns {number} The number of autofix passes. + */ + getFixPassCount() { + return internalSlotsMap.get(this).fixPasses ?? 0; + } + /** * Gets the list of SuppressedLintMessage produced in the last running. * @returns {SuppressedLintMessage[]} The list of SuppressedLintMessage @@ -1986,17 +2187,17 @@ class Linter { /** * Defines a new linting rule. * @param {string} ruleId A unique rule identifier - * @param {Function | Rule} ruleModule Function from context to object mapping AST node types to event handlers + * @param {Rule} rule A rule object * @returns {void} */ - defineRule(ruleId, ruleModule) { + defineRule(ruleId, rule) { assertEslintrcConfig(this); - internalSlotsMap.get(this).ruleMap.define(ruleId, ruleModule); + internalSlotsMap.get(this).ruleMap.define(ruleId, rule); } /** * Defines many new linting rules. - * @param {Record} rulesToDefine map from unique rule identifier to rule + * @param {Record} rulesToDefine map from unique rule identifier to rule * @returns {void} */ defineRules(rulesToDefine) { @@ -2044,13 +2245,14 @@ class Linter { * SourceCodeFixer. */ verifyAndFix(text, config, options) { - let messages = [], + let messages, fixedResult, fixed = false, passNumber = 0, currentText = text; const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`; const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true; + const stats = options?.stats; /** * This loop continues until one of the following is true: @@ -2061,15 +2263,46 @@ class Linter { * That means anytime a fix is successfully applied, there will be another pass. * Essentially, guaranteeing a minimum of two passes. */ + const slots = internalSlotsMap.get(this); + + // Remove lint times from the last run. + if (stats) { + delete slots.times; + slots.fixPasses = 0; + } + do { passNumber++; + let tTotal; + + if (stats) { + tTotal = startTime(); + } debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`); messages = this.verify(currentText, config, options); debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`); + let t; + + if (stats) { + t = startTime(); + } + fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix); + if (stats) { + + if (fixedResult.fixed) { + const time = endTime(t); + + storeTime(time, { type: "fix" }, slots); + slots.fixPasses++; + } else { + storeTime(0, { type: "fix" }, slots); + } + } + /* * stop if there are any syntax errors. * 'fixedResult.output' is a empty string. @@ -2084,6 +2317,13 @@ class Linter { // update to use the fixed output instead of the original text currentText = fixedResult.output; + if (stats) { + tTotal = endTime(tTotal); + const passIndex = slots.times.passes.length - 1; + + slots.times.passes[passIndex].total = tTotal; + } + } while ( fixedResult.fixed && passNumber < MAX_AUTOFIX_PASSES @@ -2094,7 +2334,18 @@ class Linter { * the most up-to-date information. */ if (fixedResult.fixed) { + let tTotal; + + if (stats) { + tTotal = startTime(); + } + fixedResult.messages = this.verify(currentText, config, options); + + if (stats) { + storeTime(0, { type: "fix" }, slots); + slots.times.passes.at(-1).total = endTime(tTotal); + } } // ensure the last result properly reflects if fixes were done diff --git a/tools/node_modules/eslint/lib/linter/report-translator.js b/tools/node_modules/eslint/lib/linter/report-translator.js index 41a43eadc3be18..c4a159a993e6fe 100644 --- a/tools/node_modules/eslint/lib/linter/report-translator.js +++ b/tools/node_modules/eslint/lib/linter/report-translator.js @@ -11,7 +11,7 @@ const assert = require("assert"); const ruleFixer = require("./rule-fixer"); -const interpolate = require("./interpolate"); +const { interpolate } = require("./interpolate"); //------------------------------------------------------------------------------ // Typedefs @@ -160,7 +160,7 @@ function mergeFixes(fixes, sourceCode) { const originalText = sourceCode.text; const start = fixes[0].range[0]; - const end = fixes[fixes.length - 1].range[1]; + const end = fixes.at(-1).range[1]; let text = ""; let lastPos = Number.MIN_SAFE_INTEGER; @@ -343,7 +343,7 @@ module.exports = function createReportTranslator(metadata) { if (descriptor.message) { throw new TypeError("context.report() called with a message and a messageId. Please only pass one."); } - if (!messages || !Object.prototype.hasOwnProperty.call(messages, id)) { + if (!messages || !Object.hasOwn(messages, id)) { throw new TypeError(`context.report() called with a messageId of '${id}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`); } computedMessage = messages[id]; diff --git a/tools/node_modules/eslint/lib/linter/rules.js b/tools/node_modules/eslint/lib/linter/rules.js index 647bab68784388..bb8e36823bd188 100644 --- a/tools/node_modules/eslint/lib/linter/rules.js +++ b/tools/node_modules/eslint/lib/linter/rules.js @@ -13,18 +13,10 @@ const builtInRules = require("../rules"); //------------------------------------------------------------------------------ -// Helpers +// Typedefs //------------------------------------------------------------------------------ -/** - * Normalizes a rule module to the new-style API - * @param {(Function|{create: Function})} rule A rule object, which can either be a function - * ("old-style") or an object with a `create` method ("new-style") - * @returns {{create: Function}} A new-style rule. - */ -function normalizeRule(rule) { - return typeof rule === "function" ? Object.assign({ create: rule }, rule) : rule; -} +/** @typedef {import("../shared/types").Rule} Rule */ //------------------------------------------------------------------------------ // Public Interface @@ -41,18 +33,17 @@ class Rules { /** * Registers a rule module for rule id in storage. * @param {string} ruleId Rule id (file name). - * @param {Function} ruleModule Rule handler. + * @param {Rule} rule Rule object. * @returns {void} */ - define(ruleId, ruleModule) { - this._rules[ruleId] = normalizeRule(ruleModule); + define(ruleId, rule) { + this._rules[ruleId] = rule; } /** * Access rule handler by id (file name). * @param {string} ruleId Rule id (file name). - * @returns {{create: Function, schema: JsonSchema[]}} - * A rule. This is normalized to always have the new-style shape with a `create` method. + * @returns {Rule} Rule object. */ get(ruleId) { if (typeof this._rules[ruleId] === "string") { diff --git a/tools/node_modules/eslint/lib/linter/source-code-fixer.js b/tools/node_modules/eslint/lib/linter/source-code-fixer.js index 15386c926c25ab..312e84f61cbc3f 100644 --- a/tools/node_modules/eslint/lib/linter/source-code-fixer.js +++ b/tools/node_modules/eslint/lib/linter/source-code-fixer.js @@ -107,7 +107,7 @@ SourceCodeFixer.applyFixes = function(sourceText, messages, shouldFix) { } messages.forEach(problem => { - if (Object.prototype.hasOwnProperty.call(problem, "fix")) { + if (Object.hasOwn(problem, "fix")) { fixes.push(problem); } else { remainingMessages.push(problem); diff --git a/tools/node_modules/eslint/lib/linter/timing.js b/tools/node_modules/eslint/lib/linter/timing.js index 1076ff25887cbe..232c5f4f2fb479 100644 --- a/tools/node_modules/eslint/lib/linter/timing.js +++ b/tools/node_modules/eslint/lib/linter/timing.js @@ -5,6 +5,8 @@ "use strict"; +const { startTime, endTime } = require("../shared/stats"); + //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ @@ -128,21 +130,27 @@ module.exports = (function() { * Time the run * @param {any} key key from the data object * @param {Function} fn function to be called + * @param {boolean} stats if 'stats' is true, return the result and the time difference * @returns {Function} function to be executed * @private */ - function time(key, fn) { - if (typeof data[key] === "undefined") { - data[key] = 0; - } + function time(key, fn, stats) { return function(...args) { - let t = process.hrtime(); + + const t = startTime(); const result = fn(...args); + const tdiff = endTime(t); + + if (enabled) { + if (typeof data[key] === "undefined") { + data[key] = 0; + } + + data[key] += tdiff; + } - t = process.hrtime(t); - data[key] += t[0] * 1e3 + t[1] / 1e6; - return result; + return stats ? { result, tdiff } : result; }; } diff --git a/tools/node_modules/eslint/lib/options.js b/tools/node_modules/eslint/lib/options.js index 089f347430a46d..c8c3f8e4f70c1c 100644 --- a/tools/node_modules/eslint/lib/options.js +++ b/tools/node_modules/eslint/lib/options.js @@ -57,7 +57,10 @@ const optionator = require("optionator"); * @property {boolean} quiet Report errors only * @property {boolean} [version] Output the version number * @property {boolean} warnIgnored Show warnings when the file list includes ignored files + * @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause + * the linting operation to short circuit and not report any failures. * @property {string[]} _ Positional filenames or patterns + * @property {boolean} [stats] Report additional statistics */ //------------------------------------------------------------------------------ @@ -101,6 +104,16 @@ module.exports = function(usingFlatConfig) { }; } + let inspectConfigFlag; + + if (usingFlatConfig) { + inspectConfigFlag = { + option: "inspect-config", + type: "Boolean", + description: "Open the config inspector with the current configuration" + }; + } + let extFlag; if (!usingFlatConfig) { @@ -141,6 +154,17 @@ module.exports = function(usingFlatConfig) { }; } + let statsFlag; + + if (usingFlatConfig) { + statsFlag = { + option: "stats", + type: "Boolean", + default: "false", + description: "Add statistics to the lint report" + }; + } + let warnIgnoredFlag; if (usingFlatConfig) { @@ -171,6 +195,7 @@ module.exports = function(usingFlatConfig) { ? "Use this configuration instead of eslint.config.js, eslint.config.mjs, or eslint.config.cjs" : "Use this configuration, overriding .eslintrc.* config options if present" }, + inspectConfigFlag, envFlag, extFlag, { @@ -370,6 +395,12 @@ module.exports = function(usingFlatConfig) { description: "Exit with exit code 2 in case of fatal error" }, warnIgnoredFlag, + { + option: "pass-on-no-patterns", + type: "Boolean", + default: false, + description: "Exit with exit code 0 in case no file patterns are passed" + }, { option: "debug", type: "Boolean", @@ -392,7 +423,8 @@ module.exports = function(usingFlatConfig) { option: "print-config", type: "path::String", description: "Print the configuration for the given file" - } + }, + statsFlag ].filter(value => !!value) }); }; diff --git a/tools/node_modules/eslint/lib/rule-tester/flat-rule-tester.js b/tools/node_modules/eslint/lib/rule-tester/flat-rule-tester.js deleted file mode 100644 index 03a97b072b7e79..00000000000000 --- a/tools/node_modules/eslint/lib/rule-tester/flat-rule-tester.js +++ /dev/null @@ -1,1131 +0,0 @@ -/** - * @fileoverview Mocha/Jest test wrapper - * @author Ilya Volodin - */ -"use strict"; - -/* globals describe, it -- Mocha globals */ - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const - assert = require("assert"), - util = require("util"), - path = require("path"), - equal = require("fast-deep-equal"), - Traverser = require("../shared/traverser"), - { getRuleOptionsSchema } = require("../config/flat-config-helpers"), - { Linter, SourceCodeFixer, interpolate } = require("../linter"), - CodePath = require("../linter/code-path-analysis/code-path"); - -const { FlatConfigArray } = require("../config/flat-config-array"); -const { defaultConfig } = require("../config/default-config"); - -const ajv = require("../shared/ajv")({ strictDefaults: true }); - -const parserSymbol = Symbol.for("eslint.RuleTester.parser"); -const { SourceCode } = require("../source-code"); -const { ConfigArraySymbol } = require("@humanwhocodes/config-array"); - -//------------------------------------------------------------------------------ -// Typedefs -//------------------------------------------------------------------------------ - -/** @typedef {import("../shared/types").Parser} Parser */ -/** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */ -/** @typedef {import("../shared/types").Rule} Rule */ - - -/** - * A test case that is expected to pass lint. - * @typedef {Object} ValidTestCase - * @property {string} [name] Name for the test case. - * @property {string} code Code for the test case. - * @property {any[]} [options] Options for the test case. - * @property {LanguageOptions} [languageOptions] The language options to use in the test case. - * @property {{ [name: string]: any }} [settings] Settings for the test case. - * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames. - * @property {boolean} [only] Run only this test case or the subset of test cases with this property. - */ - -/** - * A test case that is expected to fail lint. - * @typedef {Object} InvalidTestCase - * @property {string} [name] Name for the test case. - * @property {string} code Code for the test case. - * @property {number | Array} errors Expected errors. - * @property {string | null} [output] The expected code after autofixes are applied. If set to `null`, the test runner will assert that no autofix is suggested. - * @property {any[]} [options] Options for the test case. - * @property {{ [name: string]: any }} [settings] Settings for the test case. - * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames. - * @property {LanguageOptions} [languageOptions] The language options to use in the test case. - * @property {boolean} [only] Run only this test case or the subset of test cases with this property. - */ - -/** - * A description of a reported error used in a rule tester test. - * @typedef {Object} TestCaseError - * @property {string | RegExp} [message] Message. - * @property {string} [messageId] Message ID. - * @property {string} [type] The type of the reported AST node. - * @property {{ [name: string]: string }} [data] The data used to fill the message template. - * @property {number} [line] The 1-based line number of the reported start location. - * @property {number} [column] The 1-based column number of the reported start location. - * @property {number} [endLine] The 1-based line number of the reported end location. - * @property {number} [endColumn] The 1-based column number of the reported end location. - */ - -//------------------------------------------------------------------------------ -// Private Members -//------------------------------------------------------------------------------ - -/* - * testerDefaultConfig must not be modified as it allows to reset the tester to - * the initial default configuration - */ -const testerDefaultConfig = { rules: {} }; - -/* - * RuleTester uses this config as its default. This can be overwritten via - * setDefaultConfig(). - */ -let sharedDefaultConfig = { rules: {} }; - -/* - * List every parameters possible on a test case that are not related to eslint - * configuration - */ -const RuleTesterParameters = [ - "name", - "code", - "filename", - "options", - "errors", - "output", - "only" -]; - -/* - * All allowed property names in error objects. - */ -const errorObjectParameters = new Set([ - "message", - "messageId", - "data", - "type", - "line", - "column", - "endLine", - "endColumn", - "suggestions" -]); -const friendlyErrorObjectParameterList = `[${[...errorObjectParameters].map(key => `'${key}'`).join(", ")}]`; - -/* - * All allowed property names in suggestion objects. - */ -const suggestionObjectParameters = new Set([ - "desc", - "messageId", - "data", - "output" -]); -const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`; - -const forbiddenMethods = [ - "applyInlineConfig", - "applyLanguageOptions", - "finalize" -]; - -/** @type {Map} */ -const forbiddenMethodCalls = new Map(forbiddenMethods.map(methodName => ([methodName, new WeakSet()]))); - -const hasOwnProperty = Function.call.bind(Object.hasOwnProperty); - -/** - * Clones a given value deeply. - * Note: This ignores `parent` property. - * @param {any} x A value to clone. - * @returns {any} A cloned value. - */ -function cloneDeeplyExcludesParent(x) { - if (typeof x === "object" && x !== null) { - if (Array.isArray(x)) { - return x.map(cloneDeeplyExcludesParent); - } - - const retv = {}; - - for (const key in x) { - if (key !== "parent" && hasOwnProperty(x, key)) { - retv[key] = cloneDeeplyExcludesParent(x[key]); - } - } - - return retv; - } - - return x; -} - -/** - * Freezes a given value deeply. - * @param {any} x A value to freeze. - * @returns {void} - */ -function freezeDeeply(x) { - if (typeof x === "object" && x !== null) { - if (Array.isArray(x)) { - x.forEach(freezeDeeply); - } else { - for (const key in x) { - if (key !== "parent" && hasOwnProperty(x, key)) { - freezeDeeply(x[key]); - } - } - } - Object.freeze(x); - } -} - -/** - * Replace control characters by `\u00xx` form. - * @param {string} text The text to sanitize. - * @returns {string} The sanitized text. - */ -function sanitize(text) { - if (typeof text !== "string") { - return ""; - } - return text.replace( - /[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex -- Escaping controls - c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}` - ); -} - -/** - * Define `start`/`end` properties as throwing error. - * @param {string} objName Object name used for error messages. - * @param {ASTNode} node The node to define. - * @returns {void} - */ -function defineStartEndAsError(objName, node) { - Object.defineProperties(node, { - start: { - get() { - throw new Error(`Use ${objName}.range[0] instead of ${objName}.start`); - }, - configurable: true, - enumerable: false - }, - end: { - get() { - throw new Error(`Use ${objName}.range[1] instead of ${objName}.end`); - }, - configurable: true, - enumerable: false - } - }); -} - - -/** - * Define `start`/`end` properties of all nodes of the given AST as throwing error. - * @param {ASTNode} ast The root node to errorize `start`/`end` properties. - * @param {Object} [visitorKeys] Visitor keys to be used for traversing the given ast. - * @returns {void} - */ -function defineStartEndAsErrorInTree(ast, visitorKeys) { - Traverser.traverse(ast, { visitorKeys, enter: defineStartEndAsError.bind(null, "node") }); - ast.tokens.forEach(defineStartEndAsError.bind(null, "token")); - ast.comments.forEach(defineStartEndAsError.bind(null, "token")); -} - -/** - * Wraps the given parser in order to intercept and modify return values from the `parse` and `parseForESLint` methods, for test purposes. - * In particular, to modify ast nodes, tokens and comments to throw on access to their `start` and `end` properties. - * @param {Parser} parser Parser object. - * @returns {Parser} Wrapped parser object. - */ -function wrapParser(parser) { - - if (typeof parser.parseForESLint === "function") { - return { - [parserSymbol]: parser, - parseForESLint(...args) { - const ret = parser.parseForESLint(...args); - - defineStartEndAsErrorInTree(ret.ast, ret.visitorKeys); - return ret; - } - }; - } - - return { - [parserSymbol]: parser, - parse(...args) { - const ast = parser.parse(...args); - - defineStartEndAsErrorInTree(ast); - return ast; - } - }; -} - -/** - * Function to replace `SourceCode.prototype.getComments`. - * @returns {void} - * @throws {Error} Deprecation message. - */ -function getCommentsDeprecation() { - throw new Error( - "`SourceCode#getComments()` is deprecated and will be removed in a future major version. Use `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` instead." - ); -} - -/** - * Emit a deprecation warning if rule uses CodePath#currentSegments. - * @param {string} ruleName Name of the rule. - * @returns {void} - */ -function emitCodePathCurrentSegmentsWarning(ruleName) { - if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) { - emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true; - process.emitWarning( - `"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`, - "DeprecationWarning" - ); - } -} - -/** - * Function to replace forbidden `SourceCode` methods. Allows just one call per method. - * @param {string} methodName The name of the method to forbid. - * @param {Function} prototype The prototype with the original method to call. - * @returns {Function} The function that throws the error. - */ -function throwForbiddenMethodError(methodName, prototype) { - - const original = prototype[methodName]; - - return function(...args) { - - const called = forbiddenMethodCalls.get(methodName); - - /* eslint-disable no-invalid-this -- needed to operate as a method. */ - if (!called.has(this)) { - called.add(this); - - return original.apply(this, args); - } - /* eslint-enable no-invalid-this -- not needed past this point */ - - throw new Error( - `\`SourceCode#${methodName}()\` cannot be called inside a rule.` - ); - }; -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -// default separators for testing -const DESCRIBE = Symbol("describe"); -const IT = Symbol("it"); -const IT_ONLY = Symbol("itOnly"); - -/** - * This is `it` default handler if `it` don't exist. - * @this {Mocha} - * @param {string} text The description of the test case. - * @param {Function} method The logic of the test case. - * @throws {Error} Any error upon execution of `method`. - * @returns {any} Returned value of `method`. - */ -function itDefaultHandler(text, method) { - try { - return method.call(this); - } catch (err) { - if (err instanceof assert.AssertionError) { - err.message += ` (${util.inspect(err.actual)} ${err.operator} ${util.inspect(err.expected)})`; - } - throw err; - } -} - -/** - * This is `describe` default handler if `describe` don't exist. - * @this {Mocha} - * @param {string} text The description of the test case. - * @param {Function} method The logic of the test case. - * @returns {any} Returned value of `method`. - */ -function describeDefaultHandler(text, method) { - return method.call(this); -} - -/** - * Mocha test wrapper. - */ -class FlatRuleTester { - - /** - * Creates a new instance of RuleTester. - * @param {Object} [testerConfig] Optional, extra configuration for the tester - */ - constructor(testerConfig = {}) { - - /** - * The configuration to use for this tester. Combination of the tester - * configuration and the default configuration. - * @type {Object} - */ - this.testerConfig = [ - sharedDefaultConfig, - testerConfig, - { rules: { "rule-tester/validate-ast": "error" } } - ]; - - this.linter = new Linter({ configType: "flat" }); - } - - /** - * Set the configuration to use for all future tests - * @param {Object} config the configuration to use. - * @throws {TypeError} If non-object config. - * @returns {void} - */ - static setDefaultConfig(config) { - if (typeof config !== "object" || config === null) { - throw new TypeError("FlatRuleTester.setDefaultConfig: config must be an object"); - } - sharedDefaultConfig = config; - - // Make sure the rules object exists since it is assumed to exist later - sharedDefaultConfig.rules = sharedDefaultConfig.rules || {}; - } - - /** - * Get the current configuration used for all tests - * @returns {Object} the current configuration - */ - static getDefaultConfig() { - return sharedDefaultConfig; - } - - /** - * Reset the configuration to the initial configuration of the tester removing - * any changes made until now. - * @returns {void} - */ - static resetDefaultConfig() { - sharedDefaultConfig = { - rules: { - ...testerDefaultConfig.rules - } - }; - } - - - /* - * If people use `mocha test.js --watch` command, `describe` and `it` function - * instances are different for each execution. So `describe` and `it` should get fresh instance - * always. - */ - static get describe() { - return ( - this[DESCRIBE] || - (typeof describe === "function" ? describe : describeDefaultHandler) - ); - } - - static set describe(value) { - this[DESCRIBE] = value; - } - - static get it() { - return ( - this[IT] || - (typeof it === "function" ? it : itDefaultHandler) - ); - } - - static set it(value) { - this[IT] = value; - } - - /** - * Adds the `only` property to a test to run it in isolation. - * @param {string | ValidTestCase | InvalidTestCase} item A single test to run by itself. - * @returns {ValidTestCase | InvalidTestCase} The test with `only` set. - */ - static only(item) { - if (typeof item === "string") { - return { code: item, only: true }; - } - - return { ...item, only: true }; - } - - static get itOnly() { - if (typeof this[IT_ONLY] === "function") { - return this[IT_ONLY]; - } - if (typeof this[IT] === "function" && typeof this[IT].only === "function") { - return Function.bind.call(this[IT].only, this[IT]); - } - if (typeof it === "function" && typeof it.only === "function") { - return Function.bind.call(it.only, it); - } - - if (typeof this[DESCRIBE] === "function" || typeof this[IT] === "function") { - throw new Error( - "Set `RuleTester.itOnly` to use `only` with a custom test framework.\n" + - "See https://eslint.org/docs/latest/integrate/nodejs-api#customizing-ruletester for more." - ); - } - if (typeof it === "function") { - throw new Error("The current test framework does not support exclusive tests with `only`."); - } - throw new Error("To use `only`, use RuleTester with a test framework that provides `it.only()` like Mocha."); - } - - static set itOnly(value) { - this[IT_ONLY] = value; - } - - - /** - * Adds a new rule test to execute. - * @param {string} ruleName The name of the rule to run. - * @param {Function | Rule} rule The rule to test. - * @param {{ - * valid: (ValidTestCase | string)[], - * invalid: InvalidTestCase[] - * }} test The collection of tests to run. - * @throws {TypeError|Error} If non-object `test`, or if a required - * scenario of the given type is missing. - * @returns {void} - */ - run(ruleName, rule, test) { - - const testerConfig = this.testerConfig, - requiredScenarios = ["valid", "invalid"], - scenarioErrors = [], - linter = this.linter, - ruleId = `rule-to-test/${ruleName}`; - - if (!test || typeof test !== "object") { - throw new TypeError(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`); - } - - requiredScenarios.forEach(scenarioType => { - if (!test[scenarioType]) { - scenarioErrors.push(`Could not find any ${scenarioType} test scenarios`); - } - }); - - if (scenarioErrors.length > 0) { - throw new Error([ - `Test Scenarios for rule ${ruleName} is invalid:` - ].concat(scenarioErrors).join("\n")); - } - - const baseConfig = [ - { files: ["**"] }, // Make sure the default config matches for all files - { - plugins: { - - // copy root plugin over - "@": { - - /* - * Parsers are wrapped to detect more errors, so this needs - * to be a new object for each call to run(), otherwise the - * parsers will be wrapped multiple times. - */ - parsers: { - ...defaultConfig[0].plugins["@"].parsers - }, - - /* - * The rules key on the default plugin is a proxy to lazy-load - * just the rules that are needed. So, don't create a new object - * here, just use the default one to keep that performance - * enhancement. - */ - rules: defaultConfig[0].plugins["@"].rules - }, - "rule-to-test": { - rules: { - [ruleName]: Object.assign({}, rule, { - - // Create a wrapper rule that freezes the `context` properties. - create(context) { - freezeDeeply(context.options); - freezeDeeply(context.settings); - freezeDeeply(context.parserOptions); - - // freezeDeeply(context.languageOptions); - - return (typeof rule === "function" ? rule : rule.create)(context); - } - }) - } - } - }, - languageOptions: { - ...defaultConfig[0].languageOptions - } - }, - ...defaultConfig.slice(1) - ]; - - /** - * Run the rule for the given item - * @param {string|Object} item Item to run the rule against - * @throws {Error} If an invalid schema. - * @returns {Object} Eslint run result - * @private - */ - function runRuleForItem(item) { - const flatConfigArrayOptions = { - baseConfig - }; - - if (item.filename) { - flatConfigArrayOptions.basePath = path.parse(item.filename).root; - } - - const configs = new FlatConfigArray(testerConfig, flatConfigArrayOptions); - - /* - * Modify the returned config so that the parser is wrapped to catch - * access of the start/end properties. This method is called just - * once per code snippet being tested, so each test case gets a clean - * parser. - */ - configs[ConfigArraySymbol.finalizeConfig] = function(...args) { - - // can't do super here :( - const proto = Object.getPrototypeOf(this); - const calculatedConfig = proto[ConfigArraySymbol.finalizeConfig].apply(this, args); - - // wrap the parser to catch start/end property access - calculatedConfig.languageOptions.parser = wrapParser(calculatedConfig.languageOptions.parser); - return calculatedConfig; - }; - - let code, filename, output, beforeAST, afterAST; - - if (typeof item === "string") { - code = item; - } else { - code = item.code; - - /* - * Assumes everything on the item is a config except for the - * parameters used by this tester - */ - const itemConfig = { ...item }; - - for (const parameter of RuleTesterParameters) { - delete itemConfig[parameter]; - } - - // wrap any parsers - if (itemConfig.languageOptions && itemConfig.languageOptions.parser) { - - const parser = itemConfig.languageOptions.parser; - - if (parser && typeof parser !== "object") { - throw new Error("Parser must be an object with a parse() or parseForESLint() method."); - } - - } - - /* - * Create the config object from the tester config and this item - * specific configurations. - */ - configs.push(itemConfig); - } - - if (item.filename) { - filename = item.filename; - } - - let ruleConfig = 1; - - if (hasOwnProperty(item, "options")) { - assert(Array.isArray(item.options), "options must be an array"); - ruleConfig = [1, ...item.options]; - } - - configs.push({ - rules: { - [ruleId]: ruleConfig - } - }); - - const schema = getRuleOptionsSchema(rule); - - /* - * Setup AST getters. - * The goal is to check whether or not AST was modified when - * running the rule under test. - */ - configs.push({ - plugins: { - "rule-tester": { - rules: { - "validate-ast": { - create() { - return { - Program(node) { - beforeAST = cloneDeeplyExcludesParent(node); - }, - "Program:exit"(node) { - afterAST = node; - } - }; - } - } - } - } - } - }); - - if (schema) { - ajv.validateSchema(schema); - - if (ajv.errors) { - const errors = ajv.errors.map(error => { - const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath; - - return `\t${field}: ${error.message}`; - }).join("\n"); - - throw new Error([`Schema for rule ${ruleName} is invalid:`, errors]); - } - - /* - * `ajv.validateSchema` checks for errors in the structure of the schema (by comparing the schema against a "meta-schema"), - * and it reports those errors individually. However, there are other types of schema errors that only occur when compiling - * the schema (e.g. using invalid defaults in a schema), and only one of these errors can be reported at a time. As a result, - * the schema is compiled here separately from checking for `validateSchema` errors. - */ - try { - ajv.compile(schema); - } catch (err) { - throw new Error(`Schema for rule ${ruleName} is invalid: ${err.message}`); - } - } - - // check for validation errors - try { - configs.normalizeSync(); - configs.getConfig("test.js"); - } catch (error) { - error.message = `ESLint configuration in rule-tester is invalid: ${error.message}`; - throw error; - } - - // Verify the code. - const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype; - const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments"); - let messages; - - try { - SourceCode.prototype.getComments = getCommentsDeprecation; - Object.defineProperty(CodePath.prototype, "currentSegments", { - get() { - emitCodePathCurrentSegmentsWarning(ruleName); - return originalCurrentSegments.get.call(this); - } - }); - - forbiddenMethods.forEach(methodName => { - SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype); - }); - - messages = linter.verify(code, configs, filename); - } finally { - SourceCode.prototype.getComments = getComments; - Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments); - SourceCode.prototype.applyInlineConfig = applyInlineConfig; - SourceCode.prototype.applyLanguageOptions = applyLanguageOptions; - SourceCode.prototype.finalize = finalize; - } - - - const fatalErrorMessage = messages.find(m => m.fatal); - - assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`); - - // Verify if autofix makes a syntax error or not. - if (messages.some(m => m.fix)) { - output = SourceCodeFixer.applyFixes(code, messages).output; - const errorMessageInFix = linter.verify(output, configs, filename).find(m => m.fatal); - - assert(!errorMessageInFix, [ - "A fatal parsing error occurred in autofix.", - `Error: ${errorMessageInFix && errorMessageInFix.message}`, - "Autofix output:", - output - ].join("\n")); - } else { - output = code; - } - - return { - messages, - output, - beforeAST, - afterAST: cloneDeeplyExcludesParent(afterAST) - }; - } - - /** - * Check if the AST was changed - * @param {ASTNode} beforeAST AST node before running - * @param {ASTNode} afterAST AST node after running - * @returns {void} - * @private - */ - function assertASTDidntChange(beforeAST, afterAST) { - if (!equal(beforeAST, afterAST)) { - assert.fail("Rule should not modify AST."); - } - } - - /** - * Check if the template is valid or not - * all valid cases go through this - * @param {string|Object} item Item to run the rule against - * @returns {void} - * @private - */ - function testValidTemplate(item) { - const code = typeof item === "object" ? item.code : item; - - assert.ok(typeof code === "string", "Test case must specify a string value for 'code'"); - if (item.name) { - assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string"); - } - - const result = runRuleForItem(item); - const messages = result.messages; - - assert.strictEqual(messages.length, 0, util.format("Should have no errors but had %d: %s", - messages.length, - util.inspect(messages))); - - assertASTDidntChange(result.beforeAST, result.afterAST); - } - - /** - * Asserts that the message matches its expected value. If the expected - * value is a regular expression, it is checked against the actual - * value. - * @param {string} actual Actual value - * @param {string|RegExp} expected Expected value - * @returns {void} - * @private - */ - function assertMessageMatches(actual, expected) { - if (expected instanceof RegExp) { - - // assert.js doesn't have a built-in RegExp match function - assert.ok( - expected.test(actual), - `Expected '${actual}' to match ${expected}` - ); - } else { - assert.strictEqual(actual, expected); - } - } - - /** - * Check if the template is invalid or not - * all invalid cases go through this. - * @param {string|Object} item Item to run the rule against - * @returns {void} - * @private - */ - function testInvalidTemplate(item) { - assert.ok(typeof item.code === "string", "Test case must specify a string value for 'code'"); - if (item.name) { - assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string"); - } - assert.ok(item.errors || item.errors === 0, - `Did not specify errors for an invalid test of ${ruleName}`); - - if (Array.isArray(item.errors) && item.errors.length === 0) { - assert.fail("Invalid cases must have at least one error"); - } - - const ruleHasMetaMessages = hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages"); - const friendlyIDList = ruleHasMetaMessages ? `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]` : null; - - const result = runRuleForItem(item); - const messages = result.messages; - - if (typeof item.errors === "number") { - - if (item.errors === 0) { - assert.fail("Invalid cases must have 'error' value greater than 0"); - } - - assert.strictEqual(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s", - item.errors, - item.errors === 1 ? "" : "s", - messages.length, - util.inspect(messages))); - } else { - assert.strictEqual( - messages.length, item.errors.length, util.format( - "Should have %d error%s but had %d: %s", - item.errors.length, - item.errors.length === 1 ? "" : "s", - messages.length, - util.inspect(messages) - ) - ); - - const hasMessageOfThisRule = messages.some(m => m.ruleId === ruleId); - - for (let i = 0, l = item.errors.length; i < l; i++) { - const error = item.errors[i]; - const message = messages[i]; - - assert(hasMessageOfThisRule, "Error rule name should be the same as the name of the rule being tested"); - - if (typeof error === "string" || error instanceof RegExp) { - - // Just an error message. - assertMessageMatches(message.message, error); - } else if (typeof error === "object" && error !== null) { - - /* - * Error object. - * This may have a message, messageId, data, node type, line, and/or - * column. - */ - - Object.keys(error).forEach(propertyName => { - assert.ok( - errorObjectParameters.has(propertyName), - `Invalid error property name '${propertyName}'. Expected one of ${friendlyErrorObjectParameterList}.` - ); - }); - - if (hasOwnProperty(error, "message")) { - assert.ok(!hasOwnProperty(error, "messageId"), "Error should not specify both 'message' and a 'messageId'."); - assert.ok(!hasOwnProperty(error, "data"), "Error should not specify both 'data' and 'message'."); - assertMessageMatches(message.message, error.message); - } else if (hasOwnProperty(error, "messageId")) { - assert.ok( - ruleHasMetaMessages, - "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'." - ); - if (!hasOwnProperty(rule.meta.messages, error.messageId)) { - assert(false, `Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`); - } - assert.strictEqual( - message.messageId, - error.messageId, - `messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.` - ); - if (hasOwnProperty(error, "data")) { - - /* - * if data was provided, then directly compare the returned message to a synthetic - * interpolated message using the same message ID and data provided in the test. - * See https://github.com/eslint/eslint/issues/9890 for context. - */ - const unformattedOriginalMessage = rule.meta.messages[error.messageId]; - const rehydratedMessage = interpolate(unformattedOriginalMessage, error.data); - - assert.strictEqual( - message.message, - rehydratedMessage, - `Hydrated message "${rehydratedMessage}" does not match "${message.message}"` - ); - } - } - - assert.ok( - hasOwnProperty(error, "data") ? hasOwnProperty(error, "messageId") : true, - "Error must specify 'messageId' if 'data' is used." - ); - - if (error.type) { - assert.strictEqual(message.nodeType, error.type, `Error type should be ${error.type}, found ${message.nodeType}`); - } - - if (hasOwnProperty(error, "line")) { - assert.strictEqual(message.line, error.line, `Error line should be ${error.line}`); - } - - if (hasOwnProperty(error, "column")) { - assert.strictEqual(message.column, error.column, `Error column should be ${error.column}`); - } - - if (hasOwnProperty(error, "endLine")) { - assert.strictEqual(message.endLine, error.endLine, `Error endLine should be ${error.endLine}`); - } - - if (hasOwnProperty(error, "endColumn")) { - assert.strictEqual(message.endColumn, error.endColumn, `Error endColumn should be ${error.endColumn}`); - } - - if (hasOwnProperty(error, "suggestions")) { - - // Support asserting there are no suggestions - if (!error.suggestions || (Array.isArray(error.suggestions) && error.suggestions.length === 0)) { - if (Array.isArray(message.suggestions) && message.suggestions.length > 0) { - assert.fail(`Error should have no suggestions on error with message: "${message.message}"`); - } - } else { - assert.strictEqual(Array.isArray(message.suggestions), true, `Error should have an array of suggestions. Instead received "${message.suggestions}" on error with message: "${message.message}"`); - assert.strictEqual(message.suggestions.length, error.suggestions.length, `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`); - - error.suggestions.forEach((expectedSuggestion, index) => { - assert.ok( - typeof expectedSuggestion === "object" && expectedSuggestion !== null, - "Test suggestion in 'suggestions' array must be an object." - ); - Object.keys(expectedSuggestion).forEach(propertyName => { - assert.ok( - suggestionObjectParameters.has(propertyName), - `Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.` - ); - }); - - const actualSuggestion = message.suggestions[index]; - const suggestionPrefix = `Error Suggestion at index ${index} :`; - - if (hasOwnProperty(expectedSuggestion, "desc")) { - assert.ok( - !hasOwnProperty(expectedSuggestion, "data"), - `${suggestionPrefix} Test should not specify both 'desc' and 'data'.` - ); - assert.strictEqual( - actualSuggestion.desc, - expectedSuggestion.desc, - `${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.` - ); - } - - if (hasOwnProperty(expectedSuggestion, "messageId")) { - assert.ok( - ruleHasMetaMessages, - `${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.` - ); - assert.ok( - hasOwnProperty(rule.meta.messages, expectedSuggestion.messageId), - `${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.` - ); - assert.strictEqual( - actualSuggestion.messageId, - expectedSuggestion.messageId, - `${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.` - ); - if (hasOwnProperty(expectedSuggestion, "data")) { - const unformattedMetaMessage = rule.meta.messages[expectedSuggestion.messageId]; - const rehydratedDesc = interpolate(unformattedMetaMessage, expectedSuggestion.data); - - assert.strictEqual( - actualSuggestion.desc, - rehydratedDesc, - `${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".` - ); - } - } else { - assert.ok( - !hasOwnProperty(expectedSuggestion, "data"), - `${suggestionPrefix} Test must specify 'messageId' if 'data' is used.` - ); - } - - if (hasOwnProperty(expectedSuggestion, "output")) { - const codeWithAppliedSuggestion = SourceCodeFixer.applyFixes(item.code, [actualSuggestion]).output; - - assert.strictEqual(codeWithAppliedSuggestion, expectedSuggestion.output, `Expected the applied suggestion fix to match the test suggestion output for suggestion at index: ${index} on error with message: "${message.message}"`); - } - }); - } - } - } else { - - // Message was an unexpected type - assert.fail(`Error should be a string, object, or RegExp, but found (${util.inspect(message)})`); - } - } - } - - if (hasOwnProperty(item, "output")) { - if (item.output === null) { - assert.strictEqual( - result.output, - item.code, - "Expected no autofixes to be suggested" - ); - } else { - assert.strictEqual(result.output, item.output, "Output is incorrect."); - } - } else { - assert.strictEqual( - result.output, - item.code, - "The rule fixed the code. Please add 'output' property." - ); - } - - assertASTDidntChange(result.beforeAST, result.afterAST); - } - - /* - * This creates a mocha test suite and pipes all supplied info through - * one of the templates above. - * The test suites for valid/invalid are created conditionally as - * test runners (eg. vitest) fail for empty test suites. - */ - this.constructor.describe(ruleName, () => { - if (test.valid.length > 0) { - this.constructor.describe("valid", () => { - test.valid.forEach(valid => { - this.constructor[valid.only ? "itOnly" : "it"]( - sanitize(typeof valid === "object" ? valid.name || valid.code : valid), - () => { - testValidTemplate(valid); - } - ); - }); - }); - } - - if (test.invalid.length > 0) { - this.constructor.describe("invalid", () => { - test.invalid.forEach(invalid => { - this.constructor[invalid.only ? "itOnly" : "it"]( - sanitize(invalid.name || invalid.code), - () => { - testInvalidTemplate(invalid); - } - ); - }); - }); - } - }); - } -} - -FlatRuleTester[DESCRIBE] = FlatRuleTester[IT] = FlatRuleTester[IT_ONLY] = null; - -module.exports = FlatRuleTester; diff --git a/tools/node_modules/eslint/lib/rule-tester/index.js b/tools/node_modules/eslint/lib/rule-tester/index.js index f52d14027c58ff..58f67ee49403ba 100644 --- a/tools/node_modules/eslint/lib/rule-tester/index.js +++ b/tools/node_modules/eslint/lib/rule-tester/index.js @@ -1,5 +1,7 @@ "use strict"; +const RuleTester = require("./rule-tester"); + module.exports = { - RuleTester: require("./rule-tester") + RuleTester }; diff --git a/tools/node_modules/eslint/lib/rule-tester/rule-tester.js b/tools/node_modules/eslint/lib/rule-tester/rule-tester.js index 3bc80ab1837a6f..f965fbbe472193 100644 --- a/tools/node_modules/eslint/lib/rule-tester/rule-tester.js +++ b/tools/node_modules/eslint/lib/rule-tester/rule-tester.js @@ -1,68 +1,42 @@ /** - * @fileoverview Mocha test wrapper + * @fileoverview Mocha/Jest test wrapper * @author Ilya Volodin */ "use strict"; /* globals describe, it -- Mocha globals */ -/* - * This is a wrapper around mocha to allow for DRY unittests for eslint - * Format: - * RuleTester.run("{ruleName}", { - * valid: [ - * "{code}", - * { code: "{code}", options: {options}, globals: {globals}, parser: "{parser}", settings: {settings} } - * ], - * invalid: [ - * { code: "{code}", errors: {numErrors} }, - * { code: "{code}", errors: ["{errorMessage}"] }, - * { code: "{code}", options: {options}, globals: {globals}, parser: "{parser}", settings: {settings}, errors: [{ message: "{errorMessage}", type: "{errorNodeType}"}] } - * ] - * }); - * - * Variables: - * {code} - String that represents the code to be tested - * {options} - Arguments that are passed to the configurable rules. - * {globals} - An object representing a list of variables that are - * registered as globals - * {parser} - String representing the parser to use - * {settings} - An object representing global settings for all rules - * {numErrors} - If failing case doesn't need to check error message, - * this integer will specify how many errors should be - * received - * {errorMessage} - Message that is returned by the rule on failure - * {errorNodeType} - AST node type that is returned by they rule as - * a cause of the failure. - */ - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const assert = require("assert"), - path = require("path"), util = require("util"), - merge = require("lodash.merge"), + path = require("path"), equal = require("fast-deep-equal"), - Traverser = require("../../lib/shared/traverser"), - { getRuleOptionsSchema, validate } = require("../shared/config-validator"), - { Linter, SourceCodeFixer, interpolate } = require("../linter"), - CodePath = require("../linter/code-path-analysis/code-path"); + Traverser = require("../shared/traverser"), + { getRuleOptionsSchema } = require("../config/flat-config-helpers"), + { Linter, SourceCodeFixer } = require("../linter"), + { interpolate, getPlaceholderMatcher } = require("../linter/interpolate"), + stringify = require("json-stable-stringify-without-jsonify"); + +const { FlatConfigArray } = require("../config/flat-config-array"); +const { defaultConfig } = require("../config/default-config"); const ajv = require("../shared/ajv")({ strictDefaults: true }); -const espreePath = require.resolve("espree"); const parserSymbol = Symbol.for("eslint.RuleTester.parser"); - const { SourceCode } = require("../source-code"); +const { ConfigArraySymbol } = require("@humanwhocodes/config-array"); +const { isSerializable } = require("../shared/serialization"); //------------------------------------------------------------------------------ // Typedefs //------------------------------------------------------------------------------ /** @typedef {import("../shared/types").Parser} Parser */ +/** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */ /** @typedef {import("../shared/types").Rule} Rule */ @@ -72,12 +46,9 @@ const { SourceCode } = require("../source-code"); * @property {string} [name] Name for the test case. * @property {string} code Code for the test case. * @property {any[]} [options] Options for the test case. + * @property {LanguageOptions} [languageOptions] The language options to use in the test case. * @property {{ [name: string]: any }} [settings] Settings for the test case. * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames. - * @property {string} [parser] The absolute path for the parser. - * @property {{ [name: string]: any }} [parserOptions] Options for the parser. - * @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables. - * @property {{ [name: string]: boolean }} [env] Environments for the test case. * @property {boolean} [only] Run only this test case or the subset of test cases with this property. */ @@ -91,10 +62,7 @@ const { SourceCode } = require("../source-code"); * @property {any[]} [options] Options for the test case. * @property {{ [name: string]: any }} [settings] Settings for the test case. * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames. - * @property {string} [parser] The absolute path for the parser. - * @property {{ [name: string]: any }} [parserOptions] Options for the parser. - * @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables. - * @property {{ [name: string]: boolean }} [env] Environments for the test case. + * @property {LanguageOptions} [languageOptions] The language options to use in the test case. * @property {boolean} [only] Run only this test case or the subset of test cases with this property. */ @@ -120,7 +88,12 @@ const { SourceCode } = require("../source-code"); * the initial default configuration */ const testerDefaultConfig = { rules: {} }; -let defaultConfig = { rules: {} }; + +/* + * RuleTester uses this config as its default. This can be overwritten via + * setDefaultConfig(). + */ +let sharedDefaultConfig = { rules: {} }; /* * List every parameters possible on a test case that are not related to eslint @@ -163,42 +136,25 @@ const suggestionObjectParameters = new Set([ ]); const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`; +/* + * Ignored test case properties when checking for test case duplicates. + */ +const duplicationIgnoredParameters = new Set([ + "name", + "errors", + "output" +]); + const forbiddenMethods = [ "applyInlineConfig", "applyLanguageOptions", "finalize" ]; -const hasOwnProperty = Function.call.bind(Object.hasOwnProperty); +/** @type {Map} */ +const forbiddenMethodCalls = new Map(forbiddenMethods.map(methodName => ([methodName, new WeakSet()]))); -const DEPRECATED_SOURCECODE_PASSTHROUGHS = { - getSource: "getText", - getSourceLines: "getLines", - getAllComments: "getAllComments", - getNodeByRangeIndex: "getNodeByRangeIndex", - - // getComments: "getComments", -- already handled by a separate error - getCommentsBefore: "getCommentsBefore", - getCommentsAfter: "getCommentsAfter", - getCommentsInside: "getCommentsInside", - getJSDocComment: "getJSDocComment", - getFirstToken: "getFirstToken", - getFirstTokens: "getFirstTokens", - getLastToken: "getLastToken", - getLastTokens: "getLastTokens", - getTokenAfter: "getTokenAfter", - getTokenBefore: "getTokenBefore", - getTokenByRangeStart: "getTokenByRangeStart", - getTokens: "getTokens", - getTokensAfter: "getTokensAfter", - getTokensBefore: "getTokensBefore", - getTokensBetween: "getTokensBetween", - - getScope: "getScope", - getAncestors: "getAncestors", - getDeclaredVariables: "getDeclaredVariables", - markVariableAsUsed: "markVariableAsUsed" -}; +const hasOwnProperty = Function.call.bind(Object.hasOwnProperty); /** * Clones a given value deeply. @@ -331,23 +287,27 @@ function wrapParser(parser) { } /** - * Function to replace `SourceCode.prototype.getComments`. - * @returns {void} - * @throws {Error} Deprecation message. - */ -function getCommentsDeprecation() { - throw new Error( - "`SourceCode#getComments()` is deprecated and will be removed in a future major version. Use `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` instead." - ); -} - -/** - * Function to replace forbidden `SourceCode` methods. + * Function to replace forbidden `SourceCode` methods. Allows just one call per method. * @param {string} methodName The name of the method to forbid. + * @param {Function} prototype The prototype with the original method to call. * @returns {Function} The function that throws the error. */ -function throwForbiddenMethodError(methodName) { - return () => { +function throwForbiddenMethodError(methodName, prototype) { + + const original = prototype[methodName]; + + return function(...args) { + + const called = forbiddenMethodCalls.get(methodName); + + /* eslint-disable no-invalid-this -- needed to operate as a method. */ + if (!called.has(this)) { + called.add(this); + + return original.apply(this, args); + } + /* eslint-enable no-invalid-this -- not needed past this point */ + throw new Error( `\`SourceCode#${methodName}()\` cannot be called inside a rule.` ); @@ -355,81 +315,45 @@ function throwForbiddenMethodError(methodName) { } /** - * Emit a deprecation warning if function-style format is being used. - * @param {string} ruleName Name of the rule. - * @returns {void} + * Extracts names of {{ placeholders }} from the reported message. + * @param {string} message Reported message + * @returns {string[]} Array of placeholder names */ -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/latest/extend/custom-rules`, - "DeprecationWarning" - ); - } -} +function getMessagePlaceholders(message) { + const matcher = getPlaceholderMatcher(); -/** - * Emit a deprecation warning if rule has options but is missing the "meta.schema" property - * @param {string} ruleName Name of the rule. - * @returns {void} - */ -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/latest/extend/custom-rules#options-schemas`, - "DeprecationWarning" - ); - } + return Array.from(message.matchAll(matcher), ([, name]) => name.trim()); } /** - * Emit a deprecation warning if a rule uses a deprecated `context` method. - * @param {string} ruleName Name of the rule. - * @param {string} methodName The name of the method on `context` that was used. - * @returns {void} + * Returns the placeholders in the reported messages but + * only includes the placeholders available in the raw message and not in the provided data. + * @param {string} message The reported message + * @param {string} raw The raw message specified in the rule meta.messages + * @param {undefined|Record} data The passed + * @returns {string[]} Missing placeholder names */ -function emitDeprecatedContextMethodWarning(ruleName, methodName) { - if (!emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`]) { - emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`] = true; - process.emitWarning( - `"${ruleName}" rule is using \`context.${methodName}()\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.${DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]}()\` instead.`, - "DeprecationWarning" - ); - } -} +function getUnsubstitutedMessagePlaceholders(message, raw, data = {}) { + const unsubstituted = getMessagePlaceholders(message); -/** - * Emit a deprecation warning if rule uses CodePath#currentSegments. - * @param {string} ruleName Name of the rule. - * @returns {void} - */ -function emitCodePathCurrentSegmentsWarning(ruleName) { - if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) { - emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true; - process.emitWarning( - `"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`, - "DeprecationWarning" - ); + if (unsubstituted.length === 0) { + return []; } -} -/** - * Emit a deprecation warning if `context.parserServices` is used. - * @param {string} ruleName Name of the rule. - * @returns {void} - */ -function emitParserServicesWarning(ruleName) { - if (!emitParserServicesWarning[`warned-${ruleName}`]) { - emitParserServicesWarning[`warned-${ruleName}`] = true; - process.emitWarning( - `"${ruleName}" rule is using \`context.parserServices\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.parserServices\` instead.`, - "DeprecationWarning" - ); - } + // Remove false positives by only counting placeholders in the raw message, which were not provided in the data matcher or added with a data property + const known = getMessagePlaceholders(raw); + const provided = Object.keys(data); + + return unsubstituted.filter(name => known.includes(name) && !provided.includes(name)); } +const metaSchemaDescription = ` +\t- If the rule has options, set \`meta.schema\` to an array or non-empty object to enable options validation. +\t- If the rule doesn't have options, omit \`meta.schema\` to enforce that no options can be passed to the rule. +\t- You can also set \`meta.schema\` to \`false\` to opt-out of options validation (not recommended). + +\thttps://eslint.org/docs/latest/extend/custom-rules#options-schemas +`; //------------------------------------------------------------------------------ // Public Interface @@ -479,26 +403,20 @@ class RuleTester { * Creates a new instance of RuleTester. * @param {Object} [testerConfig] Optional, extra configuration for the tester */ - constructor(testerConfig) { + constructor(testerConfig = {}) { /** * The configuration to use for this tester. Combination of the tester * configuration and the default configuration. * @type {Object} */ - this.testerConfig = merge( - {}, - defaultConfig, + this.testerConfig = [ + sharedDefaultConfig, testerConfig, { rules: { "rule-tester/validate-ast": "error" } } - ); + ]; - /** - * Rule definitions to define before tests. - * @type {Object} - */ - this.rules = {}; - this.linter = new Linter(); + this.linter = new Linter({ configType: "flat" }); } /** @@ -511,10 +429,10 @@ class RuleTester { if (typeof config !== "object" || config === null) { throw new TypeError("RuleTester.setDefaultConfig: config must be an object"); } - defaultConfig = config; + sharedDefaultConfig = config; // Make sure the rules object exists since it is assumed to exist later - defaultConfig.rules = defaultConfig.rules || {}; + sharedDefaultConfig.rules = sharedDefaultConfig.rules || {}; } /** @@ -522,7 +440,7 @@ class RuleTester { * @returns {Object} the current configuration */ static getDefaultConfig() { - return defaultConfig; + return sharedDefaultConfig; } /** @@ -531,7 +449,11 @@ class RuleTester { * @returns {void} */ static resetDefaultConfig() { - defaultConfig = merge({}, testerDefaultConfig); + sharedDefaultConfig = { + rules: { + ...testerDefaultConfig.rules + } + }; } @@ -602,29 +524,17 @@ class RuleTester { this[IT_ONLY] = value; } - /** - * Define a rule for one particular run of tests. - * @param {string} name The name of the rule to define. - * @param {Function | Rule} rule The rule definition. - * @returns {void} - */ - defineRule(name, rule) { - if (typeof rule === "function") { - emitLegacyRuleAPIWarning(name); - } - this.rules[name] = rule; - } /** * Adds a new rule test to execute. * @param {string} ruleName The name of the rule to run. - * @param {Function | Rule} rule The rule to test. + * @param {Rule} rule The rule to test. * @param {{ * valid: (ValidTestCase | string)[], * invalid: InvalidTestCase[] * }} test The collection of tests to run. - * @throws {TypeError|Error} If non-object `test`, or if a required - * scenario of the given type is missing. + * @throws {TypeError|Error} If `rule` is not an object with a `create` method, + * or if non-object `test`, or if a required scenario of the given type is missing. * @returns {void} */ run(ruleName, rule, test) { @@ -632,7 +542,15 @@ class RuleTester { const testerConfig = this.testerConfig, requiredScenarios = ["valid", "invalid"], scenarioErrors = [], - linter = this.linter; + linter = this.linter, + ruleId = `rule-to-test/${ruleName}`; + + const seenValidTestCases = new Set(); + const seenInvalidTestCases = new Set(); + + if (!rule || typeof rule !== "object" || typeof rule.create !== "function") { + throw new TypeError("Rule must be an object with a `create` method"); + } if (!test || typeof test !== "object") { throw new TypeError(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`); @@ -650,54 +568,55 @@ class RuleTester { ].concat(scenarioErrors).join("\n")); } - if (typeof rule === "function") { - emitLegacyRuleAPIWarning(ruleName); - } + const baseConfig = [ + { files: ["**"] }, // Make sure the default config matches for all files + { + plugins: { - linter.defineRule(ruleName, Object.assign({}, rule, { - - // Create a wrapper rule that freezes the `context` properties. - create(context) { - freezeDeeply(context.options); - freezeDeeply(context.settings); - freezeDeeply(context.parserOptions); - - // wrap all deprecated methods - const newContext = Object.create( - context, - Object.fromEntries(Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).map(methodName => [ - methodName, - { - value(...args) { - - // emit deprecation warning - emitDeprecatedContextMethodWarning(ruleName, methodName); - - // call the original method - return context[methodName].call(this, ...args); - }, - enumerable: true - } - ])) - ); + // copy root plugin over + "@": { - // emit warning about context.parserServices - const parserServices = context.parserServices; + /* + * Parsers are wrapped to detect more errors, so this needs + * to be a new object for each call to run(), otherwise the + * parsers will be wrapped multiple times. + */ + parsers: { + ...defaultConfig[0].plugins["@"].parsers + }, - Object.defineProperty(newContext, "parserServices", { - get() { - emitParserServicesWarning(ruleName); - return parserServices; - } - }); + /* + * The rules key on the default plugin is a proxy to lazy-load + * just the rules that are needed. So, don't create a new object + * here, just use the default one to keep that performance + * enhancement. + */ + rules: defaultConfig[0].plugins["@"].rules + }, + "rule-to-test": { + rules: { + [ruleName]: Object.assign({}, rule, { - Object.freeze(newContext); + // Create a wrapper rule that freezes the `context` properties. + create(context) { + freezeDeeply(context.options); + freezeDeeply(context.settings); + freezeDeeply(context.parserOptions); - return (typeof rule === "function" ? rule : rule.create)(newContext); - } - })); + // freezeDeeply(context.languageOptions); - linter.defineRules(this.rules); + return rule.create(context); + } + }) + } + } + }, + languageOptions: { + ...defaultConfig[0].languageOptions + } + }, + ...defaultConfig.slice(1) + ]; /** * Run the rule for the given item @@ -707,8 +626,34 @@ class RuleTester { * @private */ function runRuleForItem(item) { - let config = merge({}, testerConfig), - code, filename, output, beforeAST, afterAST; + const flatConfigArrayOptions = { + baseConfig + }; + + if (item.filename) { + flatConfigArrayOptions.basePath = path.parse(item.filename).root; + } + + const configs = new FlatConfigArray(testerConfig, flatConfigArrayOptions); + + /* + * Modify the returned config so that the parser is wrapped to catch + * access of the start/end properties. This method is called just + * once per code snippet being tested, so each test case gets a clean + * parser. + */ + configs[ConfigArraySymbol.finalizeConfig] = function(...args) { + + // can't do super here :( + const proto = Object.getPrototypeOf(this); + const calculatedConfig = proto[ConfigArraySymbol.finalizeConfig].apply(this, args); + + // wrap the parser to catch start/end property access + calculatedConfig.languageOptions.parser = wrapParser(calculatedConfig.languageOptions.parser); + return calculatedConfig; + }; + + let code, filename, output, beforeAST, afterAST; if (typeof item === "string") { code = item; @@ -725,64 +670,97 @@ class RuleTester { delete itemConfig[parameter]; } + // wrap any parsers + if (itemConfig.languageOptions && itemConfig.languageOptions.parser) { + + const parser = itemConfig.languageOptions.parser; + + if (parser && typeof parser !== "object") { + throw new Error("Parser must be an object with a parse() or parseForESLint() method."); + } + + } + /* * Create the config object from the tester config and this item * specific configurations. */ - config = merge( - config, - itemConfig - ); + configs.push(itemConfig); } - if (item.filename) { + if (hasOwnProperty(item, "only")) { + assert.ok(typeof item.only === "boolean", "Optional test case property 'only' must be a boolean"); + } + if (hasOwnProperty(item, "filename")) { + assert.ok(typeof item.filename === "string", "Optional test case property 'filename' must be a string"); filename = item.filename; } + let ruleConfig = 1; + if (hasOwnProperty(item, "options")) { assert(Array.isArray(item.options), "options must be an array"); - if ( - item.options.length > 0 && - typeof rule === "object" && - ( - !rule.meta || (rule.meta && (typeof rule.meta.schema === "undefined" || rule.meta.schema === null)) - ) - ) { - emitMissingSchemaWarning(ruleName); + ruleConfig = [1, ...item.options]; + } + + configs.push({ + rules: { + [ruleId]: ruleConfig } - config.rules[ruleName] = [1].concat(item.options); - } else { - config.rules[ruleName] = 1; + }); + + let schema; + + try { + schema = getRuleOptionsSchema(rule); + } catch (err) { + err.message += metaSchemaDescription; + throw err; } - const schema = getRuleOptionsSchema(rule); + /* + * Check and throw an error if the schema is an empty object (`schema:{}`), because such schema + * doesn't validate or enforce anything and is therefore considered a possible error. If the intent + * was to skip options validation, `schema:false` should be set instead (explicit opt-out). + * + * For this purpose, a schema object is considered empty if it doesn't have any own enumerable string-keyed + * properties. While `ajv.compile()` does use enumerable properties from the prototype chain as well, + * it caches compiled schemas by serializing only own enumerable properties, so it's generally not a good idea + * to use inherited properties in schemas because schemas that differ only in inherited properties would end up + * having the same cache entry that would be correct for only one of them. + * + * At this point, `schema` can only be an object or `null`. + */ + if (schema && Object.keys(schema).length === 0) { + throw new Error(`\`schema: {}\` is a no-op${metaSchemaDescription}`); + } /* * Setup AST getters. * The goal is to check whether or not AST was modified when * running the rule under test. */ - linter.defineRule("rule-tester/validate-ast", { - create() { - return { - Program(node) { - beforeAST = cloneDeeplyExcludesParent(node); - }, - "Program:exit"(node) { - afterAST = node; + configs.push({ + plugins: { + "rule-tester": { + rules: { + "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"); - } else { - config.parser = espreePath; - } - - linter.defineParser(config.parser, wrapParser(require(config.parser))); - if (schema) { ajv.validateSchema(schema); @@ -809,35 +787,32 @@ class RuleTester { } } - validate(config, "rule-tester", id => (id === ruleName ? rule : null)); + // check for validation errors + try { + configs.normalizeSync(); + configs.getConfig("test.js"); + } catch (error) { + error.message = `ESLint configuration in rule-tester is invalid: ${error.message}`; + throw error; + } // Verify the code. - const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype; - const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments"); + const { applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype; let messages; try { - SourceCode.prototype.getComments = getCommentsDeprecation; - Object.defineProperty(CodePath.prototype, "currentSegments", { - get() { - emitCodePathCurrentSegmentsWarning(ruleName); - return originalCurrentSegments.get.call(this); - } - }); - forbiddenMethods.forEach(methodName => { - SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName); + SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype); }); - messages = linter.verify(code, config, filename); + messages = linter.verify(code, configs, filename); } finally { - SourceCode.prototype.getComments = getComments; - Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments); SourceCode.prototype.applyInlineConfig = applyInlineConfig; SourceCode.prototype.applyLanguageOptions = applyLanguageOptions; SourceCode.prototype.finalize = finalize; } + const fatalErrorMessage = messages.find(m => m.fatal); assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`); @@ -845,7 +820,7 @@ class RuleTester { // Verify if autofix makes a syntax error or not. if (messages.some(m => m.fix)) { output = SourceCodeFixer.applyFixes(code, messages).output; - const errorMessageInFix = linter.verify(output, config, filename).find(m => m.fatal); + const errorMessageInFix = linter.verify(output, configs, filename).find(m => m.fatal); assert(!errorMessageInFix, [ "A fatal parsing error occurred in autofix.", @@ -861,7 +836,9 @@ class RuleTester { messages, output, beforeAST, - afterAST: cloneDeeplyExcludesParent(afterAST) + afterAST: cloneDeeplyExcludesParent(afterAST), + configs, + filename }; } @@ -878,6 +855,39 @@ class RuleTester { } } + /** + * Check if this test case is a duplicate of one we have seen before. + * @param {string|Object} item test case object + * @param {Set} seenTestCases set of serialized test cases we have seen so far (managed by this function) + * @returns {void} + * @private + */ + function checkDuplicateTestCase(item, seenTestCases) { + if (!isSerializable(item)) { + + /* + * If we can't serialize a test case (because it contains a function, RegExp, etc), skip the check. + * This might happen with properties like: options, plugins, settings, languageOptions.parser, languageOptions.parserOptions. + */ + return; + } + + const normalizedItem = typeof item === "string" ? { code: item } : item; + const serializedTestCase = stringify(normalizedItem, { + replacer(key, value) { + + // "this" is the currently stringified object --> only ignore top-level properties + return (normalizedItem !== this || !duplicationIgnoredParameters.has(key)) ? value : void 0; + } + }); + + assert( + !seenTestCases.has(serializedTestCase), + "detected duplicate test case" + ); + seenTestCases.add(serializedTestCase); + } + /** * Check if the template is valid or not * all valid cases go through this @@ -893,6 +903,8 @@ class RuleTester { assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string"); } + checkDuplicateTestCase(item, seenValidTestCases); + const result = runRuleForItem(item); const messages = result.messages; @@ -944,12 +956,30 @@ class RuleTester { assert.fail("Invalid cases must have at least one error"); } + checkDuplicateTestCase(item, seenInvalidTestCases); + const ruleHasMetaMessages = hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages"); const friendlyIDList = ruleHasMetaMessages ? `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]` : null; const result = runRuleForItem(item); const messages = result.messages; + for (const message of messages) { + if (hasOwnProperty(message, "suggestions")) { + + /** @type {Map} */ + const seenMessageIndices = new Map(); + + for (let i = 0; i < message.suggestions.length; i += 1) { + const suggestionMessage = message.suggestions[i].desc; + const previous = seenMessageIndices.get(suggestionMessage); + + assert.ok(!seenMessageIndices.has(suggestionMessage), `Suggestion message '${suggestionMessage}' reported from suggestion ${i} was previously reported by suggestion ${previous}. Suggestion messages should be unique within an error.`); + seenMessageIndices.set(suggestionMessage, i); + } + } + } + if (typeof item.errors === "number") { if (item.errors === 0) { @@ -972,7 +1002,7 @@ class RuleTester { ) ); - const hasMessageOfThisRule = messages.some(m => m.ruleId === ruleName); + const hasMessageOfThisRule = messages.some(m => m.ruleId === ruleId); for (let i = 0, l = item.errors.length; i < l; i++) { const error = item.errors[i]; @@ -984,6 +1014,7 @@ class RuleTester { // Just an error message. assertMessageMatches(message.message, error); + assert.ok(message.suggestions === void 0, `Error at index ${i} has suggestions. Please convert the test error into an object and specify 'suggestions' property on it to test suggestions.`); } else if (typeof error === "object" && error !== null) { /* @@ -1016,6 +1047,18 @@ class RuleTester { error.messageId, `messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.` ); + + const unsubstitutedPlaceholders = getUnsubstitutedMessagePlaceholders( + message.message, + rule.meta.messages[message.messageId], + error.data + ); + + assert.ok( + unsubstitutedPlaceholders.length === 0, + `The reported message has ${unsubstitutedPlaceholders.length > 1 ? `unsubstituted placeholders: ${unsubstitutedPlaceholders.map(name => `'${name}'`).join(", ")}` : `an unsubstituted placeholder '${unsubstitutedPlaceholders[0]}'`}. Please provide the missing ${unsubstitutedPlaceholders.length > 1 ? "values" : "value"} via the 'data' property in the context.report() call.` + ); + if (hasOwnProperty(error, "data")) { /* @@ -1032,13 +1075,10 @@ class RuleTester { `Hydrated message "${rehydratedMessage}" does not match "${message.message}"` ); } + } else { + assert.fail("Test error must specify either a 'messageId' or 'message'."); } - assert.ok( - hasOwnProperty(error, "data") ? hasOwnProperty(error, "messageId") : true, - "Error must specify 'messageId' if 'data' is used." - ); - if (error.type) { assert.strictEqual(message.nodeType, error.type, `Error type should be ${error.type}, found ${message.nodeType}`); } @@ -1059,81 +1099,117 @@ class RuleTester { assert.strictEqual(message.endColumn, error.endColumn, `Error endColumn should be ${error.endColumn}`); } + assert.ok(!message.suggestions || hasOwnProperty(error, "suggestions"), `Error at index ${i} has suggestions. Please specify 'suggestions' property on the test error object.`); if (hasOwnProperty(error, "suggestions")) { // Support asserting there are no suggestions - if (!error.suggestions || (Array.isArray(error.suggestions) && error.suggestions.length === 0)) { - if (Array.isArray(message.suggestions) && message.suggestions.length > 0) { - assert.fail(`Error should have no suggestions on error with message: "${message.message}"`); - } - } else { - assert.strictEqual(Array.isArray(message.suggestions), true, `Error should have an array of suggestions. Instead received "${message.suggestions}" on error with message: "${message.message}"`); - assert.strictEqual(message.suggestions.length, error.suggestions.length, `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`); - - error.suggestions.forEach((expectedSuggestion, index) => { - assert.ok( - typeof expectedSuggestion === "object" && expectedSuggestion !== null, - "Test suggestion in 'suggestions' array must be an object." - ); - Object.keys(expectedSuggestion).forEach(propertyName => { - assert.ok( - suggestionObjectParameters.has(propertyName), - `Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.` - ); - }); - - const actualSuggestion = message.suggestions[index]; - const suggestionPrefix = `Error Suggestion at index ${index} :`; - - if (hasOwnProperty(expectedSuggestion, "desc")) { + const expectsSuggestions = Array.isArray(error.suggestions) ? error.suggestions.length > 0 : Boolean(error.suggestions); + const hasSuggestions = message.suggestions !== void 0; + + if (!hasSuggestions && expectsSuggestions) { + assert.ok(!error.suggestions, `Error should have suggestions on error with message: "${message.message}"`); + } else if (hasSuggestions) { + assert.ok(expectsSuggestions, `Error should have no suggestions on error with message: "${message.message}"`); + if (typeof error.suggestions === "number") { + assert.strictEqual(message.suggestions.length, error.suggestions, `Error should have ${error.suggestions} suggestions. Instead found ${message.suggestions.length} suggestions`); + } else if (Array.isArray(error.suggestions)) { + assert.strictEqual(message.suggestions.length, error.suggestions.length, `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`); + + error.suggestions.forEach((expectedSuggestion, index) => { assert.ok( - !hasOwnProperty(expectedSuggestion, "data"), - `${suggestionPrefix} Test should not specify both 'desc' and 'data'.` - ); - assert.strictEqual( - actualSuggestion.desc, - expectedSuggestion.desc, - `${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.` + typeof expectedSuggestion === "object" && expectedSuggestion !== null, + "Test suggestion in 'suggestions' array must be an object." ); - } + Object.keys(expectedSuggestion).forEach(propertyName => { + assert.ok( + suggestionObjectParameters.has(propertyName), + `Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.` + ); + }); - if (hasOwnProperty(expectedSuggestion, "messageId")) { - assert.ok( - ruleHasMetaMessages, - `${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.` - ); - assert.ok( - hasOwnProperty(rule.meta.messages, expectedSuggestion.messageId), - `${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.` - ); - assert.strictEqual( - actualSuggestion.messageId, - expectedSuggestion.messageId, - `${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.` - ); - if (hasOwnProperty(expectedSuggestion, "data")) { - const unformattedMetaMessage = rule.meta.messages[expectedSuggestion.messageId]; - const rehydratedDesc = interpolate(unformattedMetaMessage, expectedSuggestion.data); + const actualSuggestion = message.suggestions[index]; + const suggestionPrefix = `Error Suggestion at index ${index}:`; + if (hasOwnProperty(expectedSuggestion, "desc")) { + assert.ok( + !hasOwnProperty(expectedSuggestion, "data"), + `${suggestionPrefix} Test should not specify both 'desc' and 'data'.` + ); + assert.ok( + !hasOwnProperty(expectedSuggestion, "messageId"), + `${suggestionPrefix} Test should not specify both 'desc' and 'messageId'.` + ); assert.strictEqual( actualSuggestion.desc, - rehydratedDesc, - `${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".` + expectedSuggestion.desc, + `${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.` + ); + } else if (hasOwnProperty(expectedSuggestion, "messageId")) { + assert.ok( + ruleHasMetaMessages, + `${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.` + ); + assert.ok( + hasOwnProperty(rule.meta.messages, expectedSuggestion.messageId), + `${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.` + ); + assert.strictEqual( + actualSuggestion.messageId, + expectedSuggestion.messageId, + `${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.` + ); + + const unsubstitutedPlaceholders = getUnsubstitutedMessagePlaceholders( + actualSuggestion.desc, + rule.meta.messages[expectedSuggestion.messageId], + expectedSuggestion.data + ); + + assert.ok( + unsubstitutedPlaceholders.length === 0, + `The message of the suggestion has ${unsubstitutedPlaceholders.length > 1 ? `unsubstituted placeholders: ${unsubstitutedPlaceholders.map(name => `'${name}'`).join(", ")}` : `an unsubstituted placeholder '${unsubstitutedPlaceholders[0]}'`}. Please provide the missing ${unsubstitutedPlaceholders.length > 1 ? "values" : "value"} via the 'data' property for the suggestion in the context.report() call.` + ); + + if (hasOwnProperty(expectedSuggestion, "data")) { + const unformattedMetaMessage = rule.meta.messages[expectedSuggestion.messageId]; + const rehydratedDesc = interpolate(unformattedMetaMessage, expectedSuggestion.data); + + assert.strictEqual( + actualSuggestion.desc, + rehydratedDesc, + `${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".` + ); + } + } else if (hasOwnProperty(expectedSuggestion, "data")) { + assert.fail( + `${suggestionPrefix} Test must specify 'messageId' if 'data' is used.` + ); + } else { + assert.fail( + `${suggestionPrefix} Test must specify either 'messageId' or 'desc'.` ); } - } else { - assert.ok( - !hasOwnProperty(expectedSuggestion, "data"), - `${suggestionPrefix} Test must specify 'messageId' if 'data' is used.` - ); - } - if (hasOwnProperty(expectedSuggestion, "output")) { + assert.ok(hasOwnProperty(expectedSuggestion, "output"), `${suggestionPrefix} The "output" property is required.`); const codeWithAppliedSuggestion = SourceCodeFixer.applyFixes(item.code, [actualSuggestion]).output; + // Verify if suggestion fix makes a syntax error or not. + const errorMessageInSuggestion = + linter.verify(codeWithAppliedSuggestion, result.configs, result.filename).find(m => m.fatal); + + assert(!errorMessageInSuggestion, [ + "A fatal parsing error occurred in suggestion fix.", + `Error: ${errorMessageInSuggestion && errorMessageInSuggestion.message}`, + "Suggestion output:", + codeWithAppliedSuggestion + ].join("\n")); + assert.strictEqual(codeWithAppliedSuggestion, expectedSuggestion.output, `Expected the applied suggestion fix to match the test suggestion output for suggestion at index: ${index} on error with message: "${message.message}"`); - } - }); + assert.notStrictEqual(expectedSuggestion.output, item.code, `The output of a suggestion should differ from the original source code for suggestion at index: ${index} on error with message: "${message.message}"`); + }); + } else { + assert.fail("Test error object property 'suggestions' should be an array or a number"); + } } } } else { @@ -1153,6 +1229,7 @@ class RuleTester { ); } else { assert.strictEqual(result.output, item.output, "Output is incorrect."); + assert.notStrictEqual(item.code, item.output, "Test property 'output' matches 'code'. If no autofix is expected, then omit the 'output' property or set it to null."); } } else { assert.strictEqual( diff --git a/tools/node_modules/eslint/lib/rules/array-bracket-newline.js b/tools/node_modules/eslint/lib/rules/array-bracket-newline.js index 12ef5b612d6a2b..9eb725825a9a7d 100644 --- a/tools/node_modules/eslint/lib/rules/array-bracket-newline.js +++ b/tools/node_modules/eslint/lib/rules/array-bracket-newline.js @@ -74,7 +74,7 @@ module.exports = { function normalizeOptionValue(option) { let consistent = false; let multiline = false; - let minItems = 0; + let minItems; if (option) { if (option === "consistent") { diff --git a/tools/node_modules/eslint/lib/rules/array-bracket-spacing.js b/tools/node_modules/eslint/lib/rules/array-bracket-spacing.js index 9dd3ffd902cc6c..31ace985a2058a 100644 --- a/tools/node_modules/eslint/lib/rules/array-bracket-spacing.js +++ b/tools/node_modules/eslint/lib/rules/array-bracket-spacing.js @@ -199,7 +199,7 @@ module.exports = { : sourceCode.getLastToken(node), penultimate = sourceCode.getTokenBefore(last), firstElement = node.elements[0], - lastElement = node.elements[node.elements.length - 1]; + lastElement = node.elements.at(-1); const openingBracketMustBeSpaced = options.objectsInArraysException && isObjectType(firstElement) || diff --git a/tools/node_modules/eslint/lib/rules/block-scoped-var.js b/tools/node_modules/eslint/lib/rules/block-scoped-var.js index ec597d5661e6d9..d65fc074bb172c 100644 --- a/tools/node_modules/eslint/lib/rules/block-scoped-var.js +++ b/tools/node_modules/eslint/lib/rules/block-scoped-var.js @@ -79,7 +79,7 @@ module.exports = { } // Defines a predicate to check whether or not a given reference is outside of valid scope. - const scopeRange = stack[stack.length - 1]; + const scopeRange = stack.at(-1); /** * Check if a reference is out of scope diff --git a/tools/node_modules/eslint/lib/rules/callback-return.js b/tools/node_modules/eslint/lib/rules/callback-return.js index 5d441bdd9fac38..ffc34b163d617b 100644 --- a/tools/node_modules/eslint/lib/rules/callback-return.js +++ b/tools/node_modules/eslint/lib/rules/callback-return.js @@ -147,7 +147,7 @@ module.exports = { if (closestBlock.type === "BlockStatement") { // find the last item in the block - const lastItem = closestBlock.body[closestBlock.body.length - 1]; + const lastItem = closestBlock.body.at(-1); // if the callback is the last thing in a block that might be ok if (isCallbackExpression(node, lastItem)) { @@ -168,7 +168,7 @@ module.exports = { if (lastItem.type === "ReturnStatement") { // but only if the callback is immediately before - if (isCallbackExpression(node, closestBlock.body[closestBlock.body.length - 2])) { + if (isCallbackExpression(node, closestBlock.body.at(-2))) { return; } } diff --git a/tools/node_modules/eslint/lib/rules/camelcase.js b/tools/node_modules/eslint/lib/rules/camelcase.js index 51bb4122df094f..3c5a7b9cec3982 100644 --- a/tools/node_modules/eslint/lib/rules/camelcase.js +++ b/tools/node_modules/eslint/lib/rules/camelcase.js @@ -47,11 +47,9 @@ module.exports = { }, allow: { type: "array", - items: [ - { - type: "string" - } - ], + items: { + type: "string" + }, minItems: 0, uniqueItems: true } diff --git a/tools/node_modules/eslint/lib/rules/comma-dangle.js b/tools/node_modules/eslint/lib/rules/comma-dangle.js index 5f4180f12c5357..c24c1475444c61 100644 --- a/tools/node_modules/eslint/lib/rules/comma-dangle.js +++ b/tools/node_modules/eslint/lib/rules/comma-dangle.js @@ -154,7 +154,7 @@ module.exports = { * @returns {any} The last element */ function last(array) { - return array[array.length - 1]; + return array.at(-1); } switch (node.type) { diff --git a/tools/node_modules/eslint/lib/rules/comma-style.js b/tools/node_modules/eslint/lib/rules/comma-style.js index 0b51219531de19..d632a3bae56f52 100644 --- a/tools/node_modules/eslint/lib/rules/comma-style.js +++ b/tools/node_modules/eslint/lib/rules/comma-style.js @@ -66,7 +66,7 @@ module.exports = { NewExpression: true }; - if (context.options.length === 2 && Object.prototype.hasOwnProperty.call(context.options[1], "exceptions")) { + if (context.options.length === 2 && Object.hasOwn(context.options[1], "exceptions")) { const keys = Object.keys(context.options[1].exceptions); for (let i = 0; i < keys.length; i++) { @@ -218,7 +218,7 @@ module.exports = { previousItemToken = tokenAfterItem ? sourceCode.getTokenBefore(tokenAfterItem) - : sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1]; + : sourceCode.ast.tokens.at(-1); } else { previousItemToken = currentItemToken; } diff --git a/tools/node_modules/eslint/lib/rules/complexity.js b/tools/node_modules/eslint/lib/rules/complexity.js index b7925074db4b29..647de7b4d3b89f 100644 --- a/tools/node_modules/eslint/lib/rules/complexity.js +++ b/tools/node_modules/eslint/lib/rules/complexity.js @@ -64,7 +64,7 @@ module.exports = { if ( typeof option === "object" && - (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max")) + (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) ) { THRESHOLD = option.maximum || option.max; } else if (typeof option === "number") { @@ -109,6 +109,7 @@ module.exports = { IfStatement: increaseComplexity, WhileStatement: increaseComplexity, DoWhileStatement: increaseComplexity, + AssignmentPattern: increaseComplexity, // Avoid `default` "SwitchCase[test]": increaseComplexity, @@ -120,6 +121,18 @@ module.exports = { } }, + MemberExpression(node) { + if (node.optional === true) { + increaseComplexity(); + } + }, + + CallExpression(node) { + if (node.optional === true) { + increaseComplexity(); + } + }, + onCodePathEnd(codePath, node) { const complexity = complexities.pop(); diff --git a/tools/node_modules/eslint/lib/rules/constructor-super.js b/tools/node_modules/eslint/lib/rules/constructor-super.js index 330be80f386539..6f46278f781cb8 100644 --- a/tools/node_modules/eslint/lib/rules/constructor-super.js +++ b/tools/node_modules/eslint/lib/rules/constructor-super.js @@ -9,22 +9,6 @@ // Helpers //------------------------------------------------------------------------------ -/** - * Checks all segments in a set and returns true if any are reachable. - * @param {Set} segments The segments to check. - * @returns {boolean} True if any segment is reachable; false otherwise. - */ -function isAnySegmentReachable(segments) { - - for (const segment of segments) { - if (segment.reachable) { - return true; - } - } - - return false; -} - /** * Checks whether or not a given node is a constructor. * @param {ASTNode} node A node to check. This node type is one of @@ -109,7 +93,7 @@ function isPossibleConstructor(node) { ); case "SequenceExpression": { - const lastExpression = node.expressions[node.expressions.length - 1]; + const lastExpression = node.expressions.at(-1); return isPossibleConstructor(lastExpression); } @@ -119,6 +103,30 @@ function isPossibleConstructor(node) { } } +/** + * A class to store information about a code path segment. + */ +class SegmentInfo { + + /** + * Indicates if super() is called in all code paths. + * @type {boolean} + */ + calledInEveryPaths = false; + + /** + * Indicates if super() is called in any code paths. + * @type {boolean} + */ + calledInSomePaths = false; + + /** + * The nodes which have been validated and don't need to be reconsidered. + * @type {ASTNode[]} + */ + validNodes = []; +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -141,8 +149,7 @@ module.exports = { missingAll: "Expected to call 'super()'.", duplicate: "Unexpected duplicate 'super()'.", - badSuper: "Unexpected 'super()' because 'super' is not a constructor.", - unexpected: "Unexpected 'super()'." + badSuper: "Unexpected 'super()' because 'super' is not a constructor." } }, @@ -159,14 +166,10 @@ module.exports = { */ let funcInfo = null; - /* - * {Map} - * Information for each code path segment. - * - calledInSomePaths: A flag of be called `super()` in some code paths. - * - calledInEveryPaths: A flag of be called `super()` in all code paths. - * - validNodes: + /** + * @type {Record} */ - let segInfoMap = Object.create(null); + const segInfoMap = Object.create(null); /** * Gets the flag which shows `super()` is called in some paths. @@ -177,23 +180,21 @@ module.exports = { return segment.reachable && segInfoMap[segment.id].calledInSomePaths; } + /** + * Determines if a segment has been seen in the traversal. + * @param {CodePathSegment} segment A code path segment to check. + * @returns {boolean} `true` if the segment has been seen. + */ + function hasSegmentBeenSeen(segment) { + return !!segInfoMap[segment.id]; + } + /** * Gets the flag which shows `super()` is called in all paths. * @param {CodePathSegment} segment A code path segment to get. * @returns {boolean} The flag which shows `super()` is called in all paths. */ function isCalledInEveryPath(segment) { - - /* - * If specific segment is the looped segment of the current segment, - * skip the segment. - * If not skipped, this never becomes true after a loop. - */ - if (segment.nextSegments.length === 1 && - segment.nextSegments[0].isLoopedPrevSegment(segment) - ) { - return true; - } return segment.reachable && segInfoMap[segment.id].calledInEveryPaths; } @@ -250,9 +251,9 @@ module.exports = { } // Reports if `super()` lacked. - const segments = codePath.returnedSegments; - const calledInEveryPaths = segments.every(isCalledInEveryPath); - const calledInSomePaths = segments.some(isCalledInSomePath); + const returnedSegments = codePath.returnedSegments; + const calledInEveryPaths = returnedSegments.every(isCalledInEveryPath); + const calledInSomePaths = returnedSegments.some(isCalledInSomePath); if (!calledInEveryPaths) { context.report({ @@ -267,29 +268,37 @@ module.exports = { /** * Initialize information of a given code path segment. * @param {CodePathSegment} segment A code path segment to initialize. + * @param {CodePathSegment} node Node that starts the segment. * @returns {void} */ - onCodePathSegmentStart(segment) { + onCodePathSegmentStart(segment, node) { funcInfo.currentSegments.add(segment); - if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { + if (!(funcInfo.isConstructor && funcInfo.hasExtends)) { return; } // Initialize info. - const info = segInfoMap[segment.id] = { - calledInSomePaths: false, - calledInEveryPaths: false, - validNodes: [] - }; + const info = segInfoMap[segment.id] = new SegmentInfo(); + + const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen); // When there are previous segments, aggregates these. - const prevSegments = segment.prevSegments; + if (seenPrevSegments.length > 0) { + info.calledInSomePaths = seenPrevSegments.some(isCalledInSomePath); + info.calledInEveryPaths = seenPrevSegments.every(isCalledInEveryPath); + } - if (prevSegments.length > 0) { - info.calledInSomePaths = prevSegments.some(isCalledInSomePath); - info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath); + /* + * ForStatement > *.update segments are a special case as they are created in advance, + * without seen previous segments. Since they logically don't affect `calledInEveryPaths` + * calculations, and they can never be a lone previous segment of another one, we'll set + * their `calledInEveryPaths` to `true` to effectively ignore them in those calculations. + * . + */ + if (node.parent && node.parent.type === "ForStatement" && node.parent.update === node) { + info.calledInEveryPaths = true; } }, @@ -316,25 +325,30 @@ module.exports = { * @returns {void} */ onCodePathSegmentLoop(fromSegment, toSegment) { - if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { + if (!(funcInfo.isConstructor && funcInfo.hasExtends)) { return; } - // Update information inside of the loop. - const isRealLoop = toSegment.prevSegments.length >= 2; - funcInfo.codePath.traverseSegments( { first: toSegment, last: fromSegment }, - segment => { + (segment, controller) => { const info = segInfoMap[segment.id]; - const prevSegments = segment.prevSegments; - // Updates flags. - info.calledInSomePaths = prevSegments.some(isCalledInSomePath); - info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath); + // skip segments after the loop + if (!info) { + controller.skip(); + return; + } + + const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen); + const calledInSomePreviousPaths = seenPrevSegments.some(isCalledInSomePath); + const calledInEveryPreviousPaths = seenPrevSegments.every(isCalledInEveryPath); + + info.calledInSomePaths ||= calledInSomePreviousPaths; + info.calledInEveryPaths ||= calledInEveryPreviousPaths; // If flags become true anew, reports the valid nodes. - if (info.calledInSomePaths || isRealLoop) { + if (calledInSomePreviousPaths) { const nodes = info.validNodes; info.validNodes = []; @@ -358,7 +372,7 @@ module.exports = { * @returns {void} */ "CallExpression:exit"(node) { - if (!(funcInfo && funcInfo.isConstructor)) { + if (!(funcInfo.isConstructor && funcInfo.hasExtends)) { return; } @@ -368,41 +382,34 @@ module.exports = { } // Reports if needed. - if (funcInfo.hasExtends) { - const segments = funcInfo.currentSegments; - let duplicate = false; - let info = null; + const segments = funcInfo.currentSegments; + let duplicate = false; + let info = null; - for (const segment of segments) { + for (const segment of segments) { - if (segment.reachable) { - info = segInfoMap[segment.id]; + if (segment.reachable) { + info = segInfoMap[segment.id]; - duplicate = duplicate || info.calledInSomePaths; - info.calledInSomePaths = info.calledInEveryPaths = true; - } + duplicate = duplicate || info.calledInSomePaths; + info.calledInSomePaths = info.calledInEveryPaths = true; } + } - if (info) { - if (duplicate) { - context.report({ - messageId: "duplicate", - node - }); - } else if (!funcInfo.superIsConstructor) { - context.report({ - messageId: "badSuper", - node - }); - } else { - info.validNodes.push(node); - } + if (info) { + if (duplicate) { + context.report({ + messageId: "duplicate", + node + }); + } else if (!funcInfo.superIsConstructor) { + context.report({ + messageId: "badSuper", + node + }); + } else { + info.validNodes.push(node); } - } else if (isAnySegmentReachable(funcInfo.currentSegments)) { - context.report({ - messageId: "unexpected", - node - }); } }, @@ -412,7 +419,7 @@ module.exports = { * @returns {void} */ ReturnStatement(node) { - if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { + if (!(funcInfo.isConstructor && funcInfo.hasExtends)) { return; } @@ -432,14 +439,6 @@ module.exports = { info.calledInSomePaths = info.calledInEveryPaths = true; } } - }, - - /** - * Resets state. - * @returns {void} - */ - "Program:exit"() { - segInfoMap = Object.create(null); } }; } diff --git a/tools/node_modules/eslint/lib/rules/default-case.js b/tools/node_modules/eslint/lib/rules/default-case.js index 4f2fad0c4f8d19..1f83cef3b11355 100644 --- a/tools/node_modules/eslint/lib/rules/default-case.js +++ b/tools/node_modules/eslint/lib/rules/default-case.js @@ -54,7 +54,7 @@ module.exports = { * @returns {any} Last element */ function last(collection) { - return collection[collection.length - 1]; + return collection.at(-1); } //-------------------------------------------------------------------------- diff --git a/tools/node_modules/eslint/lib/rules/eol-last.js b/tools/node_modules/eslint/lib/rules/eol-last.js index 03487b039f352e..c46ff4eff0937a 100644 --- a/tools/node_modules/eslint/lib/rules/eol-last.js +++ b/tools/node_modules/eslint/lib/rules/eol-last.js @@ -45,7 +45,7 @@ module.exports = { Program: function checkBadEOF(node) { const sourceCode = context.sourceCode, src = sourceCode.getText(), - lastLine = sourceCode.lines[sourceCode.lines.length - 1], + lastLine = sourceCode.lines.at(-1), location = { column: lastLine.length, line: sourceCode.lines.length @@ -89,7 +89,7 @@ module.exports = { }); } else if (mode === "never" && endsWithNewline) { - const secondLastLine = sourceCode.lines[sourceCode.lines.length - 2]; + const secondLastLine = sourceCode.lines.at(-2); // File is newline-terminated, but shouldn't be context.report({ diff --git a/tools/node_modules/eslint/lib/rules/function-paren-newline.js b/tools/node_modules/eslint/lib/rules/function-paren-newline.js index de315a0204b3f8..86268e52a1d557 100644 --- a/tools/node_modules/eslint/lib/rules/function-paren-newline.js +++ b/tools/node_modules/eslint/lib/rules/function-paren-newline.js @@ -218,7 +218,7 @@ module.exports = { case "FunctionExpression": { const leftParen = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken); const rightParen = node.params.length - ? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken) + ? sourceCode.getTokenAfter(node.params.at(-1), astUtils.isClosingParenToken) : sourceCode.getTokenAfter(leftParen); return { leftParen, rightParen }; @@ -234,7 +234,7 @@ module.exports = { } const rightParen = node.params.length - ? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken) + ? sourceCode.getTokenAfter(node.params.at(-1), astUtils.isClosingParenToken) : sourceCode.getTokenAfter(firstToken); return { diff --git a/tools/node_modules/eslint/lib/rules/indent-legacy.js b/tools/node_modules/eslint/lib/rules/indent-legacy.js index 78bf965cb3fcf6..4adb4230e8db5e 100644 --- a/tools/node_modules/eslint/lib/rules/indent-legacy.js +++ b/tools/node_modules/eslint/lib/rules/indent-legacy.js @@ -789,7 +789,7 @@ module.exports = { if (elements.length > 0) { // Skip last block line check if last item in same line - if (elements[elements.length - 1].loc.end.line === node.loc.end.line) { + if (elements.at(-1).loc.end.line === node.loc.end.line) { return; } } @@ -830,7 +830,7 @@ module.exports = { } let indent; - let nodesToCheck = []; + let nodesToCheck; /* * For this statements we should check indent from statement beginning, @@ -873,7 +873,7 @@ module.exports = { */ function filterOutSameLineVars(node) { return node.declarations.reduce((finalCollection, elem) => { - const lastElem = finalCollection[finalCollection.length - 1]; + const lastElem = finalCollection.at(-1); if ((elem.loc.start.line !== node.loc.start.line && !lastElem) || (lastElem && lastElem.loc.start.line !== elem.loc.start.line)) { @@ -892,7 +892,7 @@ module.exports = { function checkIndentInVariableDeclarations(node) { const elements = filterOutSameLineVars(node); const nodeIndent = getNodeIndent(node).goodChar; - const lastElement = elements[elements.length - 1]; + const lastElement = elements.at(-1); const elementsIndent = nodeIndent + indentSize * options.VariableDeclarator[node.kind]; @@ -999,7 +999,7 @@ module.exports = { }, VariableDeclaration(node) { - if (node.declarations[node.declarations.length - 1].loc.start.line > node.declarations[0].loc.start.line) { + if (node.declarations.at(-1).loc.start.line > node.declarations[0].loc.start.line) { checkIndentInVariableDeclarations(node); } }, diff --git a/tools/node_modules/eslint/lib/rules/indent.js b/tools/node_modules/eslint/lib/rules/indent.js index 9bcbd640c4deec..bc812b13c3e37c 100644 --- a/tools/node_modules/eslint/lib/rules/indent.js +++ b/tools/node_modules/eslint/lib/rules/indent.js @@ -1077,7 +1077,7 @@ module.exports = { "ObjectExpression, ObjectPattern"(node) { const openingCurly = sourceCode.getFirstToken(node); const closingCurly = sourceCode.getTokenAfter( - node.properties.length ? node.properties[node.properties.length - 1] : openingCurly, + node.properties.length ? node.properties.at(-1) : openingCurly, astUtils.isClosingBraceToken ); @@ -1407,7 +1407,7 @@ module.exports = { PropertyDefinition(node) { const firstToken = sourceCode.getFirstToken(node); const maybeSemicolonToken = sourceCode.getLastToken(node); - let keyLastToken = null; + let keyLastToken; // Indent key. if (node.computed) { @@ -1458,7 +1458,7 @@ module.exports = { if (node.cases.length) { sourceCode.getTokensBetween( - node.cases[node.cases.length - 1], + node.cases.at(-1), closingCurly, { includeComments: true, filter: astUtils.isCommentToken } ).forEach(token => offsets.ignoreToken(token)); @@ -1488,7 +1488,7 @@ module.exports = { }, VariableDeclaration(node) { - let variableIndent = Object.prototype.hasOwnProperty.call(options.VariableDeclarator, node.kind) + let variableIndent = Object.hasOwn(options.VariableDeclarator, node.kind) ? options.VariableDeclarator[node.kind] : DEFAULT_VARIABLE_INDENT; @@ -1509,7 +1509,7 @@ module.exports = { variableIndent = DEFAULT_VARIABLE_INDENT; } - if (node.declarations[node.declarations.length - 1].loc.start.line > node.loc.start.line) { + if (node.declarations.at(-1).loc.start.line > node.loc.start.line) { /* * VariableDeclarator indentation is a bit different from other forms of indentation, in that the diff --git a/tools/node_modules/eslint/lib/rules/index.js b/tools/node_modules/eslint/lib/rules/index.js index 840abe73b0fbfe..5ff7b6f5dde261 100644 --- a/tools/node_modules/eslint/lib/rules/index.js +++ b/tools/node_modules/eslint/lib/rules/index.js @@ -229,6 +229,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "no-unused-private-class-members": () => require("./no-unused-private-class-members"), "no-unused-vars": () => require("./no-unused-vars"), "no-use-before-define": () => require("./no-use-before-define"), + "no-useless-assignment": () => require("./no-useless-assignment"), "no-useless-backreference": () => require("./no-useless-backreference"), "no-useless-call": () => require("./no-useless-call"), "no-useless-catch": () => require("./no-useless-catch"), @@ -273,7 +274,6 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ radix: () => require("./radix"), "require-atomic-updates": () => require("./require-atomic-updates"), "require-await": () => require("./require-await"), - "require-jsdoc": () => require("./require-jsdoc"), "require-unicode-regexp": () => require("./require-unicode-regexp"), "require-yield": () => require("./require-yield"), "rest-spread-spacing": () => require("./rest-spread-spacing"), @@ -296,7 +296,6 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "template-tag-spacing": () => require("./template-tag-spacing"), "unicode-bom": () => require("./unicode-bom"), "use-isnan": () => require("./use-isnan"), - "valid-jsdoc": () => require("./valid-jsdoc"), "valid-typeof": () => require("./valid-typeof"), "vars-on-top": () => require("./vars-on-top"), "wrap-iife": () => require("./wrap-iife"), diff --git a/tools/node_modules/eslint/lib/rules/key-spacing.js b/tools/node_modules/eslint/lib/rules/key-spacing.js index 19fc0167ae0d1b..aa8fd37b8ad333 100644 --- a/tools/node_modules/eslint/lib/rules/key-spacing.js +++ b/tools/node_modules/eslint/lib/rules/key-spacing.js @@ -28,7 +28,7 @@ function containsLineTerminator(str) { * @returns {any} Last element of arr. */ function last(arr) { - return arr[arr.length - 1]; + return arr.at(-1); } /** @@ -489,7 +489,7 @@ module.exports = { } } - let messageId = ""; + let messageId; if (isExtra) { messageId = side === "key" ? "extraKey" : "extraValue"; diff --git a/tools/node_modules/eslint/lib/rules/line-comment-position.js b/tools/node_modules/eslint/lib/rules/line-comment-position.js index 314fac16731774..ce7e35da57c6fa 100644 --- a/tools/node_modules/eslint/lib/rules/line-comment-position.js +++ b/tools/node_modules/eslint/lib/rules/line-comment-position.js @@ -68,7 +68,7 @@ module.exports = { above = !options.position || options.position === "above"; ignorePattern = options.ignorePattern; - if (Object.prototype.hasOwnProperty.call(options, "applyDefaultIgnorePatterns")) { + if (Object.hasOwn(options, "applyDefaultIgnorePatterns")) { applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns; } else { applyDefaultIgnorePatterns = options.applyDefaultPatterns !== false; diff --git a/tools/node_modules/eslint/lib/rules/lines-around-directive.js b/tools/node_modules/eslint/lib/rules/lines-around-directive.js index 1c82d8f98d62c5..fb6871d3e8af25 100644 --- a/tools/node_modules/eslint/lib/rules/lines-around-directive.js +++ b/tools/node_modules/eslint/lib/rules/lines-around-directive.js @@ -166,7 +166,7 @@ module.exports = { reportError(firstDirective, "before", false); } - const lastDirective = directives[directives.length - 1]; + const lastDirective = directives.at(-1); const statements = node.type === "Program" ? node.body : node.body.body; /* @@ -174,7 +174,7 @@ module.exports = { * contains a directive prologue and isn't followed by a comment to ensure * this rule behaves well with padded-blocks. */ - if (lastDirective === statements[statements.length - 1] && !lastDirective.trailingComments) { + if (lastDirective === statements.at(-1) && !lastDirective.trailingComments) { return; } diff --git a/tools/node_modules/eslint/lib/rules/max-depth.js b/tools/node_modules/eslint/lib/rules/max-depth.js index 7a8e9f94ef1cd0..e92c6b4acdd649 100644 --- a/tools/node_modules/eslint/lib/rules/max-depth.js +++ b/tools/node_modules/eslint/lib/rules/max-depth.js @@ -61,7 +61,7 @@ module.exports = { if ( typeof option === "object" && - (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max")) + (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) ) { maxDepth = option.maximum || option.max; } diff --git a/tools/node_modules/eslint/lib/rules/max-len.js b/tools/node_modules/eslint/lib/rules/max-len.js index 138a0f239fd341..15910d5d95d118 100644 --- a/tools/node_modules/eslint/lib/rules/max-len.js +++ b/tools/node_modules/eslint/lib/rules/max-len.js @@ -124,7 +124,7 @@ module.exports = { } // The options object must be the last option specified… - const options = Object.assign({}, context.options[context.options.length - 1]); + const options = Object.assign({}, context.options.at(-1)); // …but max code length… if (typeof context.options[0] === "number") { @@ -290,7 +290,7 @@ module.exports = { if (isJSXEmptyExpressionInSingleLineContainer(containingNode)) { // push a unique node only - if (comments[comments.length - 1] !== containingNode.parent) { + if (comments.at(-1) !== containingNode.parent) { comments.push(containingNode.parent); } } else { @@ -344,7 +344,7 @@ module.exports = { * comments to check. */ if (commentsIndex < comments.length) { - let comment = null; + let comment; // iterate over comments until we find one past the current line do { diff --git a/tools/node_modules/eslint/lib/rules/max-lines.js b/tools/node_modules/eslint/lib/rules/max-lines.js index e85d44290814b5..d8215794cb05de 100644 --- a/tools/node_modules/eslint/lib/rules/max-lines.js +++ b/tools/node_modules/eslint/lib/rules/max-lines.js @@ -77,7 +77,7 @@ module.exports = { if ( typeof option === "object" && - Object.prototype.hasOwnProperty.call(option, "max") + Object.hasOwn(option, "max") ) { max = option.max; } else if (typeof option === "number") { @@ -148,7 +148,7 @@ module.exports = { * If file ends with a linebreak, `sourceCode.lines` will have one extra empty line at the end. * That isn't a real line, so we shouldn't count it. */ - if (lines.length > 1 && lines[lines.length - 1].text === "") { + if (lines.length > 1 && lines.at(-1).text === "") { lines.pop(); } @@ -174,7 +174,7 @@ module.exports = { }, end: { line: sourceCode.lines.length, - column: sourceCode.lines[sourceCode.lines.length - 1].length + column: sourceCode.lines.at(-1).length } }; diff --git a/tools/node_modules/eslint/lib/rules/max-nested-callbacks.js b/tools/node_modules/eslint/lib/rules/max-nested-callbacks.js index d8f380b3c589ee..448c0bd44333b2 100644 --- a/tools/node_modules/eslint/lib/rules/max-nested-callbacks.js +++ b/tools/node_modules/eslint/lib/rules/max-nested-callbacks.js @@ -59,7 +59,7 @@ module.exports = { if ( typeof option === "object" && - (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max")) + (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) ) { THRESHOLD = option.maximum || option.max; } else if (typeof option === "number") { diff --git a/tools/node_modules/eslint/lib/rules/max-params.js b/tools/node_modules/eslint/lib/rules/max-params.js index 213477a15c64ce..eb043ab190b224 100644 --- a/tools/node_modules/eslint/lib/rules/max-params.js +++ b/tools/node_modules/eslint/lib/rules/max-params.js @@ -63,7 +63,7 @@ module.exports = { if ( typeof option === "object" && - (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max")) + (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) ) { numParams = option.maximum || option.max; } diff --git a/tools/node_modules/eslint/lib/rules/max-statements.js b/tools/node_modules/eslint/lib/rules/max-statements.js index 78053831f27522..96d5ea7b8ccbd8 100644 --- a/tools/node_modules/eslint/lib/rules/max-statements.js +++ b/tools/node_modules/eslint/lib/rules/max-statements.js @@ -79,7 +79,7 @@ module.exports = { if ( typeof option === "object" && - (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max")) + (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) ) { maxStatements = option.maximum || option.max; } else if (typeof option === "number") { diff --git a/tools/node_modules/eslint/lib/rules/multiline-comment-style.js b/tools/node_modules/eslint/lib/rules/multiline-comment-style.js index 6da9862015c541..19caa8384afe64 100644 --- a/tools/node_modules/eslint/lib/rules/multiline-comment-style.js +++ b/tools/node_modules/eslint/lib/rules/multiline-comment-style.js @@ -113,7 +113,7 @@ module.exports = { return /^\*\s*$/u.test(lines[0]) && lines.slice(1, -1).every(line => /^\s* /u.test(line)) && - /^\s*$/u.test(lines[lines.length - 1]); + /^\s*$/u.test(lines.at(-1)); } /** @@ -272,11 +272,11 @@ module.exports = { context.report({ loc: { start: firstComment.loc.start, - end: commentGroup[commentGroup.length - 1].loc.end + end: commentGroup.at(-1).loc.end }, messageId: "expectedBlock", fix(fixer) { - const range = [firstComment.range[0], commentGroup[commentGroup.length - 1].range[1]]; + const range = [firstComment.range[0], commentGroup.at(-1).range[1]]; return commentLines.some(value => value.startsWith("/")) ? null @@ -301,7 +301,7 @@ module.exports = { }); } - if (!/^\s*$/u.test(lines[lines.length - 1])) { + if (!/^\s*$/u.test(lines.at(-1))) { context.report({ loc: { start: { line: firstComment.loc.end.line, column: firstComment.loc.end.column - 2 }, @@ -408,12 +408,12 @@ module.exports = { context.report({ loc: { start: firstComment.loc.start, - end: commentGroup[commentGroup.length - 1].loc.end + end: commentGroup.at(-1).loc.end }, messageId: "expectedBlock", fix(fixer) { return fixer.replaceTextRange( - [firstComment.range[0], commentGroup[commentGroup.length - 1].range[1]], + [firstComment.range[0], commentGroup.at(-1).range[1]], convertToBlock(firstComment, commentLines) ); } @@ -459,7 +459,7 @@ module.exports = { tokenBefore && tokenBefore.loc.end.line === comment.loc.start.line - 1 && tokenBefore === commentList[index - 1] ) { - commentGroups[commentGroups.length - 1].push(comment); + commentGroups.at(-1).push(comment); } else { commentGroups.push([comment]); } diff --git a/tools/node_modules/eslint/lib/rules/new-cap.js b/tools/node_modules/eslint/lib/rules/new-cap.js index f81e42fd0c8406..638e64c0a2283a 100644 --- a/tools/node_modules/eslint/lib/rules/new-cap.js +++ b/tools/node_modules/eslint/lib/rules/new-cap.js @@ -40,7 +40,7 @@ const CAPS_ALLOWED = [ function checkArray(obj, key, fallback) { /* c8 ignore start */ - if (Object.prototype.hasOwnProperty.call(obj, key) && !Array.isArray(obj[key])) { + if (Object.hasOwn(obj, key) && !Array.isArray(obj[key])) { throw new TypeError(`${key}, if provided, must be an Array`); }/* c8 ignore stop */ return obj[key] || fallback; diff --git a/tools/node_modules/eslint/lib/rules/newline-after-var.js b/tools/node_modules/eslint/lib/rules/newline-after-var.js index dc8b24d473820c..6e4476412d9966 100644 --- a/tools/node_modules/eslint/lib/rules/newline-after-var.js +++ b/tools/node_modules/eslint/lib/rules/newline-after-var.js @@ -215,7 +215,7 @@ module.exports = { fix(fixer) { const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(astUtils.LINEBREAK_MATCHER); - return fixer.replaceTextRange([lastToken.range[1], nextToken.range[0]], `${linesBetween.slice(0, -1).join("")}\n${linesBetween[linesBetween.length - 1]}`); + return fixer.replaceTextRange([lastToken.range[1], nextToken.range[0]], `${linesBetween.slice(0, -1).join("")}\n${linesBetween.at(-1)}`); } }); } diff --git a/tools/node_modules/eslint/lib/rules/newline-before-return.js b/tools/node_modules/eslint/lib/rules/newline-before-return.js index 73d8ef99ff3ebd..21e9faf1492b93 100644 --- a/tools/node_modules/eslint/lib/rules/newline-before-return.js +++ b/tools/node_modules/eslint/lib/rules/newline-before-return.js @@ -166,7 +166,7 @@ module.exports = { */ function canFix(node) { const leadingComments = sourceCode.getCommentsBefore(node); - const lastLeadingComment = leadingComments[leadingComments.length - 1]; + const lastLeadingComment = leadingComments.at(-1); const tokenBefore = sourceCode.getTokenBefore(node); if (leadingComments.length === 0) { diff --git a/tools/node_modules/eslint/lib/rules/no-constant-binary-expression.js b/tools/node_modules/eslint/lib/rules/no-constant-binary-expression.js index 845255a0cc2caa..2f3013b74f6484 100644 --- a/tools/node_modules/eslint/lib/rules/no-constant-binary-expression.js +++ b/tools/node_modules/eslint/lib/rules/no-constant-binary-expression.js @@ -5,8 +5,7 @@ "use strict"; -const globals = require("globals"); -const { isNullLiteral, isConstant, isReferenceToGlobalVariable, isLogicalAssignmentOperator } = require("./utils/ast-utils"); +const { isNullLiteral, isConstant, isReferenceToGlobalVariable, isLogicalAssignmentOperator, ECMASCRIPT_GLOBALS } = require("./utils/ast-utils"); const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set(["+", "-", "*", "/", "%", "|", "^", "&", "**", "<<", ">>", ">>>"]); @@ -103,7 +102,7 @@ function hasConstantNullishness(scope, node, nonNullish) { return true; case "SequenceExpression": { - const last = node.expressions[node.expressions.length - 1]; + const last = node.expressions.at(-1); return hasConstantNullishness(scope, last, nonNullish); } @@ -248,7 +247,7 @@ function hasConstantLooseBooleanComparison(scope, node) { */ return false; case "SequenceExpression": { - const last = node.expressions[node.expressions.length - 1]; + const last = node.expressions.at(-1); return hasConstantLooseBooleanComparison(scope, last); } @@ -299,7 +298,7 @@ function hasConstantStrictBooleanComparison(scope, node) { return true; } case "SequenceExpression": { - const last = node.expressions[node.expressions.length - 1]; + const last = node.expressions.at(-1); return hasConstantStrictBooleanComparison(scope, last); } @@ -376,7 +375,7 @@ function isAlwaysNew(scope, node) { * 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) && + return Object.hasOwn(ECMASCRIPT_GLOBALS, node.callee.name) && isReferenceToGlobalVariable(scope, node.callee); } case "Literal": @@ -384,7 +383,7 @@ function isAlwaysNew(scope, node) { // Regular expressions are objects, and thus always new return typeof node.regex === "object"; case "SequenceExpression": { - const last = node.expressions[node.expressions.length - 1]; + const last = node.expressions.at(-1); return isAlwaysNew(scope, last); } @@ -440,7 +439,7 @@ module.exports = { type: "problem", docs: { description: "Disallow expressions where the operation doesn't affect the value", - recommended: false, + recommended: true, url: "https://eslint.org/docs/latest/rules/no-constant-binary-expression" }, schema: [], diff --git a/tools/node_modules/eslint/lib/rules/no-constructor-return.js b/tools/node_modules/eslint/lib/rules/no-constructor-return.js index d7d98939b9a55e..ccce10ad636e3b 100644 --- a/tools/node_modules/eslint/lib/rules/no-constructor-return.js +++ b/tools/node_modules/eslint/lib/rules/no-constructor-return.js @@ -20,7 +20,7 @@ module.exports = { url: "https://eslint.org/docs/latest/rules/no-constructor-return" }, - schema: {}, + schema: [], fixable: null, @@ -40,7 +40,7 @@ module.exports = { stack.pop(); }, ReturnStatement(node) { - const last = stack[stack.length - 1]; + const last = stack.at(-1); if (!last.parent) { return; diff --git a/tools/node_modules/eslint/lib/rules/no-dupe-class-members.js b/tools/node_modules/eslint/lib/rules/no-dupe-class-members.js index 2a7a9e810dc449..c33553217590b0 100644 --- a/tools/node_modules/eslint/lib/rules/no-dupe-class-members.js +++ b/tools/node_modules/eslint/lib/rules/no-dupe-class-members.js @@ -42,7 +42,7 @@ module.exports = { * - retv.set {boolean} A flag which shows the name is declared as setter. */ function getState(name, isStatic) { - const stateMap = stack[stack.length - 1]; + const stateMap = stack.at(-1); const key = `$${name}`; // to avoid "__proto__". if (!stateMap[key]) { @@ -82,7 +82,7 @@ module.exports = { } const state = getState(name, node.static); - let isDuplicate = false; + let isDuplicate; if (kind === "get") { isDuplicate = (state.init || state.get); diff --git a/tools/node_modules/eslint/lib/rules/no-else-return.js b/tools/node_modules/eslint/lib/rules/no-else-return.js index 9dbf569651c5ab..6e6bf476dd861c 100644 --- a/tools/node_modules/eslint/lib/rules/no-else-return.js +++ b/tools/node_modules/eslint/lib/rules/no-else-return.js @@ -270,7 +270,7 @@ module.exports = { function naiveHasReturn(node) { if (node.type === "BlockStatement") { const body = node.body, - lastChildNode = body[body.length - 1]; + lastChildNode = body.at(-1); return lastChildNode && checkForReturn(lastChildNode); } diff --git a/tools/node_modules/eslint/lib/rules/no-empty-function.js b/tools/node_modules/eslint/lib/rules/no-empty-function.js index 2fcb75534ac334..b7dee94c4ea587 100644 --- a/tools/node_modules/eslint/lib/rules/no-empty-function.js +++ b/tools/node_modules/eslint/lib/rules/no-empty-function.js @@ -40,7 +40,7 @@ const ALLOW_OPTIONS = Object.freeze([ */ function getKind(node) { const parent = node.parent; - let kind = ""; + let kind; if (node.type === "ArrowFunctionExpression") { return "arrowFunctions"; @@ -73,7 +73,7 @@ function getKind(node) { } // Detects prefix. - let prefix = ""; + let prefix; if (node.generator) { prefix = "generator"; diff --git a/tools/node_modules/eslint/lib/rules/no-empty-static-block.js b/tools/node_modules/eslint/lib/rules/no-empty-static-block.js index 81fc449b8cf45f..558c4fad4552e1 100644 --- a/tools/node_modules/eslint/lib/rules/no-empty-static-block.js +++ b/tools/node_modules/eslint/lib/rules/no-empty-static-block.js @@ -15,7 +15,7 @@ module.exports = { docs: { description: "Disallow empty static blocks", - recommended: false, + recommended: true, url: "https://eslint.org/docs/latest/rules/no-empty-static-block" }, diff --git a/tools/node_modules/eslint/lib/rules/no-extend-native.js b/tools/node_modules/eslint/lib/rules/no-extend-native.js index fcbb3855725a6f..a69dd6900391c7 100644 --- a/tools/node_modules/eslint/lib/rules/no-extend-native.js +++ b/tools/node_modules/eslint/lib/rules/no-extend-native.js @@ -10,7 +10,6 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const globals = require("globals"); //------------------------------------------------------------------------------ // Rule Definition @@ -54,7 +53,7 @@ module.exports = { const sourceCode = context.sourceCode; const exceptions = new Set(config.exceptions || []); const modifiedBuiltins = new Set( - Object.keys(globals.builtin) + Object.keys(astUtils.ECMASCRIPT_GLOBALS) .filter(builtin => builtin[0].toUpperCase() === builtin[0]) .filter(builtin => !exceptions.has(builtin)) ); diff --git a/tools/node_modules/eslint/lib/rules/no-extra-semi.js b/tools/node_modules/eslint/lib/rules/no-extra-semi.js index af7eb88884560d..1daf2242249c0e 100644 --- a/tools/node_modules/eslint/lib/rules/no-extra-semi.js +++ b/tools/node_modules/eslint/lib/rules/no-extra-semi.js @@ -26,7 +26,7 @@ module.exports = { docs: { description: "Disallow unnecessary semicolons", - recommended: true, + recommended: false, url: "https://eslint.org/docs/latest/rules/no-extra-semi" }, diff --git a/tools/node_modules/eslint/lib/rules/no-fallthrough.js b/tools/node_modules/eslint/lib/rules/no-fallthrough.js index 91da12120222be..4c98ddde52545f 100644 --- a/tools/node_modules/eslint/lib/rules/no-fallthrough.js +++ b/tools/node_modules/eslint/lib/rules/no-fallthrough.js @@ -48,9 +48,9 @@ function isFallThroughComment(comment, fallthroughCommentPattern) { * @param {ASTNode} subsequentCase The case after caseWhichFallsThrough. * @param {RuleContext} context A rule context which stores comments. * @param {RegExp} fallthroughCommentPattern A pattern to match comment to. - * @returns {boolean} `true` if the case has a valid fallthrough comment. + * @returns {null | object} the comment if the case has a valid fallthrough comment, otherwise null */ -function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) { +function getFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) { const sourceCode = context.sourceCode; if (caseWhichFallsThrough.consequent.length === 1 && caseWhichFallsThrough.consequent[0].type === "BlockStatement") { @@ -58,13 +58,17 @@ function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, f const commentInBlock = sourceCode.getCommentsBefore(trailingCloseBrace).pop(); if (commentInBlock && isFallThroughComment(commentInBlock.value, fallthroughCommentPattern)) { - return true; + return commentInBlock; } } const comment = sourceCode.getCommentsBefore(subsequentCase).pop(); - return Boolean(comment && isFallThroughComment(comment.value, fallthroughCommentPattern)); + if (comment && isFallThroughComment(comment.value, fallthroughCommentPattern)) { + return comment; + } + + return null; } /** @@ -103,12 +107,17 @@ module.exports = { allowEmptyCase: { type: "boolean", default: false + }, + reportUnusedFallthroughComment: { + type: "boolean", + default: false } }, additionalProperties: false } ], messages: { + unusedFallthroughComment: "Found a comment that would permit fallthrough, but case cannot fall through.", case: "Expected a 'break' statement before 'case'.", default: "Expected a 'break' statement before 'default'." } @@ -120,12 +129,13 @@ module.exports = { let currentCodePathSegments = new Set(); const sourceCode = context.sourceCode; const allowEmptyCase = options.allowEmptyCase || false; + const reportUnusedFallthroughComment = options.reportUnusedFallthroughComment || false; /* * We need to use leading comments of the next SwitchCase node because * trailing comments is wrong if semicolons are omitted. */ - let fallthroughCase = null; + let previousCase = null; let fallthroughCommentPattern = null; if (options.commentPattern) { @@ -168,13 +178,23 @@ module.exports = { * And reports the previous fallthrough node if that does not exist. */ - if (fallthroughCase && (!hasFallthroughComment(fallthroughCase, node, context, fallthroughCommentPattern))) { - context.report({ - messageId: node.test ? "case" : "default", - node - }); + if (previousCase && previousCase.node.parent === node.parent) { + const previousCaseFallthroughComment = getFallthroughComment(previousCase.node, node, context, fallthroughCommentPattern); + + if (previousCase.isFallthrough && !(previousCaseFallthroughComment)) { + context.report({ + messageId: node.test ? "case" : "default", + node + }); + } else if (reportUnusedFallthroughComment && !previousCase.isSwitchExitReachable && previousCaseFallthroughComment) { + context.report({ + messageId: "unusedFallthroughComment", + node: previousCaseFallthroughComment + }); + } + } - fallthroughCase = null; + previousCase = null; }, "SwitchCase:exit"(node) { @@ -185,11 +205,16 @@ module.exports = { * `break`, `return`, or `throw` are unreachable. * And allows empty cases and the last case. */ - if (isAnySegmentReachable(currentCodePathSegments) && - (node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) && - node.parent.cases[node.parent.cases.length - 1] !== node) { - fallthroughCase = node; - } + const isSwitchExitReachable = isAnySegmentReachable(currentCodePathSegments); + const isFallthrough = isSwitchExitReachable && (node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) && + node.parent.cases.at(-1) !== node; + + previousCase = { + node, + isSwitchExitReachable, + isFallthrough + }; + } }; } diff --git a/tools/node_modules/eslint/lib/rules/no-implicit-coercion.js b/tools/node_modules/eslint/lib/rules/no-implicit-coercion.js index 36baad3835e4b8..3f8a7c0f9417f7 100644 --- a/tools/node_modules/eslint/lib/rules/no-implicit-coercion.js +++ b/tools/node_modules/eslint/lib/rules/no-implicit-coercion.js @@ -12,7 +12,7 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const INDEX_OF_PATTERN = /^(?:i|lastI)ndexOf$/u; -const ALLOWABLE_OPERATORS = ["~", "!!", "+", "*"]; +const ALLOWABLE_OPERATORS = ["~", "!!", "+", "- -", "-", "*"]; /** * Parses and normalizes an option object. @@ -188,6 +188,7 @@ function getNonEmptyOperand(node) { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + hasSuggestions: true, type: "suggestion", docs: { @@ -229,7 +230,8 @@ module.exports = { }], messages: { - useRecommendation: "use `{{recommendation}}` instead." + implicitCoercion: "Unexpected implicit coercion encountered. Use `{{recommendation}}` instead.", + useRecommendation: "Use `{{recommendation}}` instead." } }, @@ -241,32 +243,54 @@ module.exports = { * Reports an error and autofixes the node * @param {ASTNode} node An ast node to report the error on. * @param {string} recommendation The recommended code for the issue + * @param {bool} shouldSuggest Whether this report should offer a suggestion * @param {bool} shouldFix Whether this report should fix the node * @returns {void} */ - function report(node, recommendation, shouldFix) { + function report(node, recommendation, shouldSuggest, shouldFix) { + + /** + * Fix function + * @param {RuleFixer} fixer The fixer to fix. + * @returns {Fix} The fix object. + */ + function fix(fixer) { + const tokenBefore = sourceCode.getTokenBefore(node); + + if ( + tokenBefore?.range[1] === node.range[0] && + !astUtils.canTokensBeAdjacent(tokenBefore, recommendation) + ) { + return fixer.replaceText(node, ` ${recommendation}`); + } + + return fixer.replaceText(node, recommendation); + } + context.report({ node, - messageId: "useRecommendation", - data: { - recommendation - }, + messageId: "implicitCoercion", + data: { recommendation }, fix(fixer) { if (!shouldFix) { return null; } - const tokenBefore = sourceCode.getTokenBefore(node); - - if ( - tokenBefore && - tokenBefore.range[1] === node.range[0] && - !astUtils.canTokensBeAdjacent(tokenBefore, recommendation) - ) { - return fixer.replaceText(node, ` ${recommendation}`); + return fix(fixer); + }, + suggest: [ + { + messageId: "useRecommendation", + data: { recommendation }, + fix(fixer) { + if (shouldFix || !shouldSuggest) { + return null; + } + + return fix(fixer); + } } - return fixer.replaceText(node, recommendation); - } + ] }); } @@ -278,8 +302,10 @@ module.exports = { operatorAllowed = options.allow.includes("!!"); if (!operatorAllowed && options.boolean && isDoubleLogicalNegating(node)) { const recommendation = `Boolean(${sourceCode.getText(node.argument.argument)})`; + const variable = astUtils.getVariableByName(sourceCode.getScope(node), "Boolean"); + const booleanExists = variable?.identifiers.length === 0; - report(node, recommendation, true); + report(node, recommendation, true, booleanExists); } // ~foo.indexOf(bar) @@ -290,7 +316,7 @@ module.exports = { const comparison = node.argument.type === "ChainExpression" ? ">= 0" : "!== -1"; const recommendation = `${sourceCode.getText(node.argument)} ${comparison}`; - report(node, recommendation, false); + report(node, recommendation, false, false); } // +foo @@ -298,7 +324,15 @@ module.exports = { if (!operatorAllowed && options.number && node.operator === "+" && !isNumeric(node.argument)) { const recommendation = `Number(${sourceCode.getText(node.argument)})`; - report(node, recommendation, true); + report(node, recommendation, true, false); + } + + // -(-foo) + operatorAllowed = options.allow.includes("- -"); + if (!operatorAllowed && options.number && node.operator === "-" && node.argument.type === "UnaryExpression" && node.argument.operator === "-" && !isNumeric(node.argument.argument)) { + const recommendation = `Number(${sourceCode.getText(node.argument.argument)})`; + + report(node, recommendation, true, false); } }, @@ -314,7 +348,15 @@ module.exports = { if (nonNumericOperand) { const recommendation = `Number(${sourceCode.getText(nonNumericOperand)})`; - report(node, recommendation, true); + report(node, recommendation, true, false); + } + + // foo - 0 + operatorAllowed = options.allow.includes("-"); + if (!operatorAllowed && options.number && node.operator === "-" && node.right.type === "Literal" && node.right.value === 0 && !isNumeric(node.left)) { + const recommendation = `Number(${sourceCode.getText(node.left)})`; + + report(node, recommendation, true, false); } // "" + foo @@ -322,7 +364,7 @@ module.exports = { if (!operatorAllowed && options.string && isConcatWithEmptyString(node)) { const recommendation = `String(${sourceCode.getText(getNonEmptyOperand(node))})`; - report(node, recommendation, true); + report(node, recommendation, true, false); } }, @@ -335,7 +377,7 @@ module.exports = { const code = sourceCode.getText(getNonEmptyOperand(node)); const recommendation = `${code} = String(${code})`; - report(node, recommendation, true); + report(node, recommendation, true, false); } }, @@ -373,7 +415,7 @@ module.exports = { const code = sourceCode.getText(node.expressions[0]); const recommendation = `String(${code})`; - report(node, recommendation, true); + report(node, recommendation, true, false); } }; } diff --git a/tools/node_modules/eslint/lib/rules/no-inner-declarations.js b/tools/node_modules/eslint/lib/rules/no-inner-declarations.js index f4bae43e58dabf..a690d95bb9c30a 100644 --- a/tools/node_modules/eslint/lib/rules/no-inner-declarations.js +++ b/tools/node_modules/eslint/lib/rules/no-inner-declarations.js @@ -49,13 +49,22 @@ module.exports = { docs: { description: "Disallow variable or `function` declarations in nested blocks", - recommended: true, + recommended: false, url: "https://eslint.org/docs/latest/rules/no-inner-declarations" }, schema: [ { enum: ["functions", "both"] + }, + { + type: "object", + properties: { + blockScopedFunctions: { + enum: ["allow", "disallow"] + } + }, + additionalProperties: false } ], @@ -66,6 +75,10 @@ module.exports = { create(context) { + const sourceCode = context.sourceCode; + const ecmaVersion = context.languageOptions.ecmaVersion; + const blockScopedFunctions = context.options[1]?.blockScopedFunctions ?? "allow"; + /** * Ensure that a given node is at a program or function body's root. * @param {ASTNode} node Declaration node to check. @@ -97,7 +110,15 @@ module.exports = { return { - FunctionDeclaration: check, + FunctionDeclaration(node) { + const isInStrictCode = sourceCode.getScope(node).upper.isStrict; + + if (blockScopedFunctions === "allow" && ecmaVersion >= 2015 && isInStrictCode) { + return; + } + + check(node); + }, VariableDeclaration(node) { if (context.options[0] === "both" && node.kind === "var") { check(node); diff --git a/tools/node_modules/eslint/lib/rules/no-invalid-regexp.js b/tools/node_modules/eslint/lib/rules/no-invalid-regexp.js index 3c42a68e8a3ad0..99fa6295af27f6 100644 --- a/tools/node_modules/eslint/lib/rules/no-invalid-regexp.js +++ b/tools/node_modules/eslint/lib/rules/no-invalid-regexp.js @@ -55,7 +55,7 @@ module.exports = { const temp = options.allowConstructorFlags.join("").replace(validFlags, ""); if (temp) { - allowedFlags = new RegExp(`[${temp}]`, "giu"); + allowedFlags = new RegExp(`[${temp}]`, "gu"); } } diff --git a/tools/node_modules/eslint/lib/rules/no-invalid-this.js b/tools/node_modules/eslint/lib/rules/no-invalid-this.js index 9e214035c33820..4ea228898525bf 100644 --- a/tools/node_modules/eslint/lib/rules/no-invalid-this.js +++ b/tools/node_modules/eslint/lib/rules/no-invalid-this.js @@ -74,7 +74,7 @@ module.exports = { * an object which has a flag that whether or not `this` keyword is valid. */ stack.getCurrent = function() { - const current = this[this.length - 1]; + const current = this.at(-1); if (!current.init) { current.init = true; diff --git a/tools/node_modules/eslint/lib/rules/no-lone-blocks.js b/tools/node_modules/eslint/lib/rules/no-lone-blocks.js index 767eec2becf5fe..2e27089269be24 100644 --- a/tools/node_modules/eslint/lib/rules/no-lone-blocks.js +++ b/tools/node_modules/eslint/lib/rules/no-lone-blocks.js @@ -78,7 +78,7 @@ module.exports = { const block = node.parent; - if (loneBlocks[loneBlocks.length - 1] === block) { + if (loneBlocks.at(-1) === block) { loneBlocks.pop(); } } @@ -101,7 +101,7 @@ module.exports = { } }, "BlockStatement:exit"(node) { - if (loneBlocks.length > 0 && loneBlocks[loneBlocks.length - 1] === node) { + if (loneBlocks.length > 0 && loneBlocks.at(-1) === node) { loneBlocks.pop(); report(node); } else if ( @@ -117,7 +117,7 @@ module.exports = { }; ruleDef.VariableDeclaration = function(node) { - if (node.kind === "let" || node.kind === "const") { + if (node.kind !== "var") { markLoneBlock(node); } }; diff --git a/tools/node_modules/eslint/lib/rules/no-loss-of-precision.js b/tools/node_modules/eslint/lib/rules/no-loss-of-precision.js index b3635e3d509fed..c50d8a890555b3 100644 --- a/tools/node_modules/eslint/lib/rules/no-loss-of-precision.js +++ b/tools/node_modules/eslint/lib/rules/no-loss-of-precision.js @@ -64,7 +64,7 @@ module.exports = { */ function notBaseTenLosesPrecision(node) { const rawString = getRaw(node).toUpperCase(); - let base = 0; + let base; if (rawString.startsWith("0B")) { base = 2; diff --git a/tools/node_modules/eslint/lib/rules/no-misleading-character-class.js b/tools/node_modules/eslint/lib/rules/no-misleading-character-class.js index 20591df2cc9952..fa50e226f97e8c 100644 --- a/tools/node_modules/eslint/lib/rules/no-misleading-character-class.js +++ b/tools/node_modules/eslint/lib/rules/no-misleading-character-class.js @@ -3,11 +3,18 @@ */ "use strict"; -const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils"); +const { + CALL, + CONSTRUCT, + ReferenceTracker, + getStaticValue, + 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"); +const { parseStringLiteral, parseTemplateToken } = require("./utils/char-source"); //------------------------------------------------------------------------------ // Helpers @@ -62,7 +69,6 @@ function *iterateCharacterSequence(nodes) { } } - /** * Checks whether the given character node is a Unicode code point escape or not. * @param {Character} char the character node to check. @@ -73,80 +79,153 @@ function isUnicodeCodePointEscape(char) { } /** - * Each function returns `true` if it detects that kind of problem. - * @type {Record boolean>} + * Each function returns matched characters if it detects that kind of problem. + * @type {Record IterableIterator>} */ -const hasCharacterSequence = { - surrogatePairWithoutUFlag(chars) { - return chars.some((c, i) => { - if (i === 0) { - return false; +const findCharacterSequences = { + *surrogatePairWithoutUFlag(chars) { + for (const [index, char] of chars.entries()) { + if (index === 0) { + continue; + } + const previous = chars[index - 1]; + + if ( + isSurrogatePair(previous.value, char.value) && + !isUnicodeCodePointEscape(previous) && + !isUnicodeCodePointEscape(char) + ) { + yield [previous, char]; } - const c1 = chars[i - 1]; - - return ( - isSurrogatePair(c1.value, c.value) && - !isUnicodeCodePointEscape(c1) && - !isUnicodeCodePointEscape(c) - ); - }); + } }, - surrogatePair(chars) { - return chars.some((c, i) => { - if (i === 0) { - return false; + *surrogatePair(chars) { + for (const [index, char] of chars.entries()) { + if (index === 0) { + continue; } - const c1 = chars[i - 1]; + const previous = chars[index - 1]; - return ( - isSurrogatePair(c1.value, c.value) && + if ( + isSurrogatePair(previous.value, char.value) && ( - isUnicodeCodePointEscape(c1) || - isUnicodeCodePointEscape(c) + isUnicodeCodePointEscape(previous) || + isUnicodeCodePointEscape(char) ) - ); - }); + ) { + yield [previous, char]; + } + } }, - combiningClass(chars) { - return chars.some((c, i) => ( - i !== 0 && - isCombiningCharacter(c.value) && - !isCombiningCharacter(chars[i - 1].value) - )); + *combiningClass(chars) { + for (const [index, char] of chars.entries()) { + if (index === 0) { + continue; + } + const previous = chars[index - 1]; + + if ( + isCombiningCharacter(char.value) && + !isCombiningCharacter(previous.value) + ) { + yield [previous, char]; + } + } }, - emojiModifier(chars) { - return chars.some((c, i) => ( - i !== 0 && - isEmojiModifier(c.value) && - !isEmojiModifier(chars[i - 1].value) - )); + *emojiModifier(chars) { + for (const [index, char] of chars.entries()) { + if (index === 0) { + continue; + } + const previous = chars[index - 1]; + + if ( + isEmojiModifier(char.value) && + !isEmojiModifier(previous.value) + ) { + yield [previous, char]; + } + } }, - regionalIndicatorSymbol(chars) { - return chars.some((c, i) => ( - i !== 0 && - isRegionalIndicatorSymbol(c.value) && - isRegionalIndicatorSymbol(chars[i - 1].value) - )); + *regionalIndicatorSymbol(chars) { + for (const [index, char] of chars.entries()) { + if (index === 0) { + continue; + } + const previous = chars[index - 1]; + + if ( + isRegionalIndicatorSymbol(char.value) && + isRegionalIndicatorSymbol(previous.value) + ) { + yield [previous, char]; + } + } }, - zwj(chars) { - const lastIndex = chars.length - 1; + *zwj(chars) { + let sequence = null; + + for (const [index, char] of chars.entries()) { + if (index === 0 || index === chars.length - 1) { + continue; + } + if ( + char.value === 0x200d && + chars[index - 1].value !== 0x200d && + chars[index + 1].value !== 0x200d + ) { + if (sequence) { + if (sequence.at(-1) === chars[index - 1]) { + sequence.push(char, chars[index + 1]); // append to the sequence + } else { + yield sequence; + sequence = chars.slice(index - 1, index + 2); + } + } else { + sequence = chars.slice(index - 1, index + 2); + } + } + } - return chars.some((c, i) => ( - i !== 0 && - i !== lastIndex && - c.value === 0x200d && - chars[i - 1].value !== 0x200d && - chars[i + 1].value !== 0x200d - )); + if (sequence) { + yield sequence; + } } }; -const kinds = Object.keys(hasCharacterSequence); +const kinds = Object.keys(findCharacterSequences); + +/** + * Gets the value of the given node if it's a static value other than a regular expression object, + * or the node's `regex` property. + * The purpose of this method is to provide a replacement for `getStaticValue` in environments where certain regular expressions cannot be evaluated. + * A known example is Node.js 18 which does not support the `v` flag. + * Calling `getStaticValue` on a regular expression node with the `v` flag on Node.js 18 always returns `null`. + * A limitation of this method is that it can only detect a regular expression if the specified node is itself a regular expression literal node. + * @param {ASTNode | undefined} node The node to be inspected. + * @param {Scope} initialScope Scope to start finding variables. This function tries to resolve identifier references which are in the given scope. + * @returns {{ value: any } | { regex: { pattern: string, flags: string } } | null} The static value of the node, or `null`. + */ +function getStaticValueOrRegex(node, initialScope) { + if (!node) { + return null; + } + if (node.type === "Literal" && node.regex) { + return { regex: node.regex }; + } + + const staticValue = getStaticValue(node, initialScope); + + if (staticValue?.value instanceof RegExp) { + return null; + } + return staticValue; +} //------------------------------------------------------------------------------ // Rule Definition @@ -180,6 +259,7 @@ module.exports = { create(context) { const sourceCode = context.sourceCode; const parser = new RegExpParser(); + const checkedPatternNodes = new Set(); /** * Verify a given regular expression. @@ -208,21 +288,70 @@ module.exports = { return; } - const foundKinds = new Set(); + const foundKindMatches = new Map(); visitRegExpAST(patternNode, { onCharacterClassEnter(ccNode) { for (const chars of iterateCharacterSequence(ccNode.elements)) { for (const kind of kinds) { - if (hasCharacterSequence[kind](chars)) { - foundKinds.add(kind); + if (foundKindMatches.has(kind)) { + foundKindMatches.get(kind).push(...findCharacterSequences[kind](chars)); + } else { + foundKindMatches.set(kind, [...findCharacterSequences[kind](chars)]); } } } } }); - for (const kind of foundKinds) { + let codeUnits = null; + + /** + * Finds the report loc(s) for a range of matches. + * Only literals and expression-less templates generate granular errors. + * @param {Character[][]} matches Lists of individual characters being reported on. + * @returns {Location[]} locs for context.report. + * @see https://github.com/eslint/eslint/pull/17515 + */ + function getNodeReportLocations(matches) { + if (!astUtils.isStaticTemplateLiteral(node) && node.type !== "Literal") { + return matches.length ? [node.loc] : []; + } + return matches.map(chars => { + const firstIndex = chars[0].start; + const lastIndex = chars.at(-1).end - 1; + let start; + let end; + + if (node.type === "TemplateLiteral") { + const source = sourceCode.getText(node); + const offset = node.range[0]; + + codeUnits ??= parseTemplateToken(source); + start = offset + codeUnits[firstIndex].start; + end = offset + codeUnits[lastIndex].end; + } else if (typeof node.value === "string") { // String Literal + const source = node.raw; + const offset = node.range[0]; + + codeUnits ??= parseStringLiteral(source); + start = offset + codeUnits[firstIndex].start; + end = offset + codeUnits[lastIndex].end; + } else { // RegExp Literal + const offset = node.range[0] + 1; // Add 1 to skip the leading slash. + + start = offset + firstIndex; + end = offset + lastIndex + 1; + } + + return { + start: sourceCode.getLocFromIndex(start), + end: sourceCode.getLocFromIndex(end) + }; + }); + } + + for (const [kind, matches] of foundKindMatches) { let suggest; if (kind === "surrogatePairWithoutUFlag") { @@ -232,16 +361,24 @@ module.exports = { }]; } - context.report({ - node, - messageId: kind, - suggest - }); + const locs = getNodeReportLocations(matches); + + for (const loc of locs) { + context.report({ + node, + loc, + messageId: kind, + suggest + }); + } } } return { "Literal[regex]"(node) { + if (checkedPatternNodes.has(node)) { + return; + } verify(node, node.regex.pattern, node.regex.flags, fixer => { if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)) { return null; @@ -262,12 +399,31 @@ module.exports = { for (const { node: refNode } of tracker.iterateGlobalReferences({ RegExp: { [CALL]: true, [CONSTRUCT]: true } })) { + let pattern, flags; const [patternNode, flagsNode] = refNode.arguments; - const pattern = getStringIfConstant(patternNode, scope); - const flags = getStringIfConstant(flagsNode, scope); + const evaluatedPattern = getStaticValueOrRegex(patternNode, scope); + + if (!evaluatedPattern) { + continue; + } + if (flagsNode) { + if (evaluatedPattern.regex) { + pattern = evaluatedPattern.regex.pattern; + checkedPatternNodes.add(patternNode); + } else { + pattern = String(evaluatedPattern.value); + } + flags = getStringIfConstant(flagsNode, scope); + } else { + if (evaluatedPattern.regex) { + continue; + } + pattern = String(evaluatedPattern.value); + flags = ""; + } - if (typeof pattern === "string") { - verify(refNode, pattern, flags || "", fixer => { + if (typeof flags === "string") { + verify(patternNode, pattern, flags, fixer => { if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)) { return null; diff --git a/tools/node_modules/eslint/lib/rules/no-mixed-spaces-and-tabs.js b/tools/node_modules/eslint/lib/rules/no-mixed-spaces-and-tabs.js index 7698b5da7fa9ce..18e6114a045a67 100644 --- a/tools/node_modules/eslint/lib/rules/no-mixed-spaces-and-tabs.js +++ b/tools/node_modules/eslint/lib/rules/no-mixed-spaces-and-tabs.js @@ -18,7 +18,7 @@ module.exports = { docs: { description: "Disallow mixed spaces and tabs for indentation", - recommended: true, + recommended: false, url: "https://eslint.org/docs/latest/rules/no-mixed-spaces-and-tabs" }, diff --git a/tools/node_modules/eslint/lib/rules/no-multiple-empty-lines.js b/tools/node_modules/eslint/lib/rules/no-multiple-empty-lines.js index 5d038ff05b2e29..b597138e8f4ef2 100644 --- a/tools/node_modules/eslint/lib/rules/no-multiple-empty-lines.js +++ b/tools/node_modules/eslint/lib/rules/no-multiple-empty-lines.js @@ -70,7 +70,7 @@ module.exports = { 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; + const allLines = sourceCode.lines.at(-1) === "" ? sourceCode.lines.slice(0, -1) : sourceCode.lines; const templateLiteralLines = new Set(); //-------------------------------------------------------------------------- diff --git a/tools/node_modules/eslint/lib/rules/no-new-native-nonconstructor.js b/tools/node_modules/eslint/lib/rules/no-new-native-nonconstructor.js index ee70d281d759c5..699390dd5a19d8 100644 --- a/tools/node_modules/eslint/lib/rules/no-new-native-nonconstructor.js +++ b/tools/node_modules/eslint/lib/rules/no-new-native-nonconstructor.js @@ -22,7 +22,7 @@ module.exports = { docs: { description: "Disallow `new` operators with global non-constructor functions", - recommended: false, + recommended: true, url: "https://eslint.org/docs/latest/rules/no-new-native-nonconstructor" }, diff --git a/tools/node_modules/eslint/lib/rules/no-new-symbol.js b/tools/node_modules/eslint/lib/rules/no-new-symbol.js index 99830220244e12..85f9e62a6d71ea 100644 --- a/tools/node_modules/eslint/lib/rules/no-new-symbol.js +++ b/tools/node_modules/eslint/lib/rules/no-new-symbol.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to disallow use of the new operator with the `Symbol` object * @author Alberto Rodríguez + * @deprecated in ESLint v9.0.0 */ "use strict"; @@ -16,10 +17,16 @@ module.exports = { docs: { description: "Disallow `new` operators with the `Symbol` object", - recommended: true, + recommended: false, url: "https://eslint.org/docs/latest/rules/no-new-symbol" }, + deprecated: true, + + replacedBy: [ + "no-new-native-nonconstructor" + ], + schema: [], messages: { diff --git a/tools/node_modules/eslint/lib/rules/no-restricted-globals.js b/tools/node_modules/eslint/lib/rules/no-restricted-globals.js index 919a8ee0ad8e3b..060d50a043717b 100644 --- a/tools/node_modules/eslint/lib/rules/no-restricted-globals.js +++ b/tools/node_modules/eslint/lib/rules/no-restricted-globals.js @@ -97,7 +97,7 @@ module.exports = { * @private */ function isRestricted(name) { - return Object.prototype.hasOwnProperty.call(restrictedGlobalMessages, name); + return Object.hasOwn(restrictedGlobalMessages, name); } return { diff --git a/tools/node_modules/eslint/lib/rules/no-restricted-imports.js b/tools/node_modules/eslint/lib/rules/no-restricted-imports.js index eb59f4c23a9b56..062be909ef0ca9 100644 --- a/tools/node_modules/eslint/lib/rules/no-restricted-imports.js +++ b/tools/node_modules/eslint/lib/rules/no-restricted-imports.js @@ -34,10 +34,17 @@ const arrayOfStringsOrObjects = { items: { type: "string" } + }, + allowImportNames: { + type: "array", + items: { + type: "string" + } } }, additionalProperties: false, - required: ["name"] + required: ["name"], + not: { required: ["importNames", "allowImportNames"] } } ] }, @@ -66,6 +73,14 @@ const arrayOfStringsOrObjectPatterns = { minItems: 1, uniqueItems: true }, + allowImportNames: { + type: "array", + items: { + type: "string" + }, + minItems: 1, + uniqueItems: true + }, group: { type: "array", items: { @@ -77,6 +92,9 @@ const arrayOfStringsOrObjectPatterns = { importNamePattern: { type: "string" }, + allowImportNamePattern: { + type: "string" + }, message: { type: "string", minLength: 1 @@ -86,7 +104,16 @@ const arrayOfStringsOrObjectPatterns = { } }, additionalProperties: false, - required: ["group"] + required: ["group"], + not: { + anyOf: [ + { required: ["importNames", "allowImportNames"] }, + { required: ["importNamePattern", "allowImportNamePattern"] }, + { required: ["importNames", "allowImportNamePattern"] }, + { required: ["importNamePattern", "allowImportNames"] }, + { required: ["allowImportNames", "allowImportNamePattern"] } + ] + } }, uniqueItems: true } @@ -131,7 +158,23 @@ module.exports = { importName: "'{{importName}}' import from '{{importSource}}' is restricted.", // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - importNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}" + importNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}", + + allowedImportName: "'{{importName}}' import from '{{importSource}}' is restricted because only '{{allowedImportNames}}' import(s) is/are allowed.", + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + allowedImportNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted because only '{{allowedImportNames}}' import(s) is/are allowed. {{customMessage}}", + + everythingWithAllowImportNames: "* import is invalid because only '{{allowedImportNames}}' from '{{importSource}}' is/are allowed.", + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + everythingWithAllowImportNamesAndCustomMessage: "* import is invalid because only '{{allowedImportNames}}' from '{{importSource}}' is/are allowed. {{customMessage}}", + + allowedImportNamePattern: "'{{importName}}' import from '{{importSource}}' is restricted because only imports that match the pattern '{{allowedImportNamePattern}}' are allowed from '{{importSource}}'.", + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + allowedImportNamePatternWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted because only imports that match the pattern '{{allowedImportNamePattern}}' are allowed from '{{importSource}}'. {{customMessage}}", + + everythingWithAllowedImportNamePattern: "* import is invalid because only imports that match the pattern '{{allowedImportNamePattern}}' from '{{importSource}}' are allowed.", + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + everythingWithAllowedImportNamePatternWithCustomMessage: "* import is invalid because only imports that match the pattern '{{allowedImportNamePattern}}' from '{{importSource}}' are allowed. {{customMessage}}" }, schema: { @@ -158,20 +201,29 @@ module.exports = { const options = Array.isArray(context.options) ? context.options : []; const isPathAndPatternsObject = typeof options[0] === "object" && - (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns")); + (Object.hasOwn(options[0], "paths") || Object.hasOwn(options[0], "patterns")); const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || []; - const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => { + const groupedRestrictedPaths = restrictedPaths.reduce((memo, importSource) => { + const path = typeof importSource === "string" + ? importSource + : importSource.name; + + if (!memo[path]) { + memo[path] = []; + } + if (typeof importSource === "string") { - memo[importSource] = { message: null }; + memo[path].push({}); } else { - memo[importSource.name] = { + memo[path].push({ message: importSource.message, - importNames: importSource.importNames - }; + importNames: importSource.importNames, + allowImportNames: importSource.allowImportNames + }); } return memo; - }, {}); + }, Object.create(null)); // Handle patterns too, either as strings or groups let restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || []; @@ -182,12 +234,18 @@ module.exports = { } // relative paths are supported for this rule - const restrictedPatternGroups = restrictedPatterns.map(({ group, message, caseSensitive, importNames, importNamePattern }) => ({ - matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group), - customMessage: message, - importNames, - importNamePattern - })); + const restrictedPatternGroups = restrictedPatterns.map( + ({ group, message, caseSensitive, importNames, importNamePattern, allowImportNames, allowImportNamePattern }) => ( + { + matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group), + customMessage: message, + importNames, + importNamePattern, + allowImportNames, + allowImportNamePattern + } + ) + ); // if no imports are restricted we don't need to check if (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) { @@ -203,33 +261,60 @@ module.exports = { * @private */ function checkRestrictedPathAndReport(importSource, importNames, node) { - if (!Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) { + if (!Object.hasOwn(groupedRestrictedPaths, importSource)) { return; } - const customMessage = restrictedPathMessages[importSource].message; - const restrictedImportNames = restrictedPathMessages[importSource].importNames; - - if (restrictedImportNames) { - if (importNames.has("*")) { - const specifierData = importNames.get("*")[0]; + groupedRestrictedPaths[importSource].forEach(restrictedPathEntry => { + const customMessage = restrictedPathEntry.message; + const restrictedImportNames = restrictedPathEntry.importNames; + const allowedImportNames = restrictedPathEntry.allowImportNames; + if (!restrictedImportNames && !allowedImportNames) { context.report({ node, - messageId: customMessage ? "everythingWithCustomMessage" : "everything", - loc: specifierData.loc, + messageId: customMessage ? "pathWithCustomMessage" : "path", data: { importSource, - importNames: restrictedImportNames, customMessage } }); + + return; } - restrictedImportNames.forEach(importName => { - if (importNames.has(importName)) { - const specifiers = importNames.get(importName); + importNames.forEach((specifiers, importName) => { + if (importName === "*") { + const [specifier] = specifiers; + if (restrictedImportNames) { + context.report({ + node, + messageId: customMessage ? "everythingWithCustomMessage" : "everything", + loc: specifier.loc, + data: { + importSource, + importNames: restrictedImportNames, + customMessage + } + }); + } else if (allowedImportNames) { + context.report({ + node, + messageId: customMessage ? "everythingWithAllowImportNamesAndCustomMessage" : "everythingWithAllowImportNames", + loc: specifier.loc, + data: { + importSource, + allowedImportNames, + customMessage + } + }); + } + + return; + } + + if (restrictedImportNames && restrictedImportNames.includes(importName)) { specifiers.forEach(specifier => { context.report({ node, @@ -243,17 +328,24 @@ module.exports = { }); }); } - }); - } else { - context.report({ - node, - messageId: customMessage ? "pathWithCustomMessage" : "path", - data: { - importSource, - customMessage + + if (allowedImportNames && !allowedImportNames.includes(importName)) { + specifiers.forEach(specifier => { + context.report({ + node, + loc: specifier.loc, + messageId: customMessage ? "allowedImportNameWithCustomMessage" : "allowedImportName", + data: { + importSource, + customMessage, + importName, + allowedImportNames + } + }); + }); } }); - } + }); } /** @@ -271,12 +363,14 @@ module.exports = { const customMessage = group.customMessage; const restrictedImportNames = group.importNames; const restrictedImportNamePattern = group.importNamePattern ? new RegExp(group.importNamePattern, "u") : null; + const allowedImportNames = group.allowImportNames; + const allowedImportNamePattern = group.allowImportNamePattern ? new RegExp(group.allowImportNamePattern, "u") : null; - /* + /** * If we are not restricting to any specific import names and just the pattern itself, * report the error and move on */ - if (!restrictedImportNames && !restrictedImportNamePattern) { + if (!restrictedImportNames && !allowedImportNames && !restrictedImportNamePattern && !allowedImportNamePattern) { context.report({ node, messageId: customMessage ? "patternWithCustomMessage" : "patterns", @@ -303,6 +397,28 @@ module.exports = { customMessage } }); + } else if (allowedImportNames) { + context.report({ + node, + messageId: customMessage ? "everythingWithAllowImportNamesAndCustomMessage" : "everythingWithAllowImportNames", + loc: specifier.loc, + data: { + importSource, + allowedImportNames, + customMessage + } + }); + } else if (allowedImportNamePattern) { + context.report({ + node, + messageId: customMessage ? "everythingWithAllowedImportNamePatternWithCustomMessage" : "everythingWithAllowedImportNamePattern", + loc: specifier.loc, + data: { + importSource, + allowedImportNamePattern, + customMessage + } + }); } else { context.report({ node, @@ -336,6 +452,36 @@ module.exports = { }); }); } + + if (allowedImportNames && !allowedImportNames.includes(importName)) { + specifiers.forEach(specifier => { + context.report({ + node, + messageId: customMessage ? "allowedImportNameWithCustomMessage" : "allowedImportName", + loc: specifier.loc, + data: { + importSource, + customMessage, + importName, + allowedImportNames + } + }); + }); + } else if (allowedImportNamePattern && !allowedImportNamePattern.test(importName)) { + specifiers.forEach(specifier => { + context.report({ + node, + messageId: customMessage ? "allowedImportNamePatternWithCustomMessage" : "allowedImportNamePattern", + loc: specifier.loc, + data: { + importSource, + customMessage, + importName, + allowedImportNamePattern + } + }); + }); + } }); } diff --git a/tools/node_modules/eslint/lib/rules/no-restricted-modules.js b/tools/node_modules/eslint/lib/rules/no-restricted-modules.js index 4a9b0a4c0368a9..d36f2d289d6577 100644 --- a/tools/node_modules/eslint/lib/rules/no-restricted-modules.js +++ b/tools/node_modules/eslint/lib/rules/no-restricted-modules.js @@ -90,7 +90,7 @@ module.exports = { const options = Array.isArray(context.options) ? context.options : []; const isPathAndPatternsObject = typeof options[0] === "object" && - (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns")); + (Object.hasOwn(options[0], "paths") || Object.hasOwn(options[0], "patterns")); const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || []; const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || []; @@ -178,7 +178,7 @@ module.exports = { * @private */ function isRestrictedPath(name) { - return Object.prototype.hasOwnProperty.call(restrictedPathMessages, name); + return Object.hasOwn(restrictedPathMessages, name); } return { diff --git a/tools/node_modules/eslint/lib/rules/no-return-await.js b/tools/node_modules/eslint/lib/rules/no-return-await.js index 77abda0cadfd1b..0c297411621027 100644 --- a/tools/node_modules/eslint/lib/rules/no-return-await.js +++ b/tools/node_modules/eslint/lib/rules/no-return-await.js @@ -118,7 +118,7 @@ module.exports = { if (node.parent.type === "LogicalExpression" && node === node.parent.right) { return isInTailCallPosition(node.parent); } - if (node.parent.type === "SequenceExpression" && node === node.parent.expressions[node.parent.expressions.length - 1]) { + if (node.parent.type === "SequenceExpression" && node === node.parent.expressions.at(-1)) { return isInTailCallPosition(node.parent); } return false; diff --git a/tools/node_modules/eslint/lib/rules/no-sequences.js b/tools/node_modules/eslint/lib/rules/no-sequences.js index cd21fc7842ca3e..5822a24542fa77 100644 --- a/tools/node_modules/eslint/lib/rules/no-sequences.js +++ b/tools/node_modules/eslint/lib/rules/no-sequences.js @@ -35,6 +35,7 @@ module.exports = { }, schema: [{ + type: "object", properties: { allowInParentheses: { type: "boolean", diff --git a/tools/node_modules/eslint/lib/rules/no-this-before-super.js b/tools/node_modules/eslint/lib/rules/no-this-before-super.js index f96d8ace81d27e..01e355acbd14a8 100644 --- a/tools/node_modules/eslint/lib/rules/no-this-before-super.js +++ b/tools/node_modules/eslint/lib/rules/no-this-before-super.js @@ -30,6 +30,29 @@ function isConstructorFunction(node) { ); } +/* + * Information for each code path segment. + * - superCalled: The flag which shows `super()` called in all code paths. + * - invalidNodes: The array of invalid ThisExpression and Super nodes. + */ +/** + * + */ +class SegmentInfo { + + /** + * Indicates whether `super()` is called in all code paths. + * @type {boolean} + */ + superCalled = false; + + /** + * The array of invalid ThisExpression and Super nodes. + * @type {ASTNode[]} + */ + invalidNodes = []; +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -64,13 +87,7 @@ module.exports = { */ let funcInfo = null; - /* - * Information for each code path segment. - * Each key is the id of a code path segment. - * Each value is an object: - * - superCalled: The flag which shows `super()` called in all code paths. - * - invalidNodes: The array of invalid ThisExpression and Super nodes. - */ + /** @type {Record} */ let segInfoMap = Object.create(null); /** @@ -79,7 +96,7 @@ module.exports = { * @returns {boolean} `true` if `super()` is called. */ function isCalled(segment) { - return !segment.reachable || segInfoMap[segment.id].superCalled; + return !segment.reachable || segInfoMap[segment.id]?.superCalled; } /** @@ -197,11 +214,26 @@ module.exports = { return; } + /** + * A collection of nodes to avoid duplicate reports. + * @type {Set} + */ + const reported = new Set(); + codePath.traverseSegments((segment, controller) => { const info = segInfoMap[segment.id]; + const invalidNodes = info.invalidNodes + .filter( - for (let i = 0; i < info.invalidNodes.length; ++i) { - const invalidNode = info.invalidNodes[i]; + /* + * Avoid duplicate reports. + * When there is a `finally`, invalidNodes may contain already reported node. + */ + node => !reported.has(node) + ); + + for (const invalidNode of invalidNodes) { + reported.add(invalidNode); context.report({ messageId: "noBeforeSuper", @@ -270,18 +302,18 @@ module.exports = { funcInfo.codePath.traverseSegments( { first: toSegment, last: fromSegment }, (segment, controller) => { - const info = segInfoMap[segment.id]; + const info = segInfoMap[segment.id] ?? new SegmentInfo(); if (info.superCalled) { - info.invalidNodes = []; controller.skip(); } else if ( segment.prevSegments.length > 0 && segment.prevSegments.every(isCalled) ) { info.superCalled = true; - info.invalidNodes = []; } + + segInfoMap[segment.id] = info; } ); }, diff --git a/tools/node_modules/eslint/lib/rules/no-trailing-spaces.js b/tools/node_modules/eslint/lib/rules/no-trailing-spaces.js index eede46c8634633..c188b1fa899cb8 100644 --- a/tools/node_modules/eslint/lib/rules/no-trailing-spaces.js +++ b/tools/node_modules/eslint/lib/rules/no-trailing-spaces.js @@ -129,8 +129,7 @@ module.exports = { comments = sourceCode.getAllComments(), commentLineNumbers = getCommentLineNumbers(comments); - let totalLength = 0, - fixRange = []; + let totalLength = 0; for (let i = 0, ii = lines.length; i < ii; i++) { const lineNumber = i + 1; @@ -177,7 +176,7 @@ module.exports = { continue; } - fixRange = [rangeStart, rangeEnd]; + const fixRange = [rangeStart, rangeEnd]; if (!ignoreComments || !commentLineNumbers.has(lineNumber)) { report(node, location, fixRange); diff --git a/tools/node_modules/eslint/lib/rules/no-unneeded-ternary.js b/tools/node_modules/eslint/lib/rules/no-unneeded-ternary.js index ab1bdc59cbf884..9bff7fb6da4593 100644 --- a/tools/node_modules/eslint/lib/rules/no-unneeded-ternary.js +++ b/tools/node_modules/eslint/lib/rules/no-unneeded-ternary.js @@ -76,7 +76,7 @@ module.exports = { * @returns {string} A string representing an inverted expression */ function invertExpression(node) { - if (node.type === "BinaryExpression" && Object.prototype.hasOwnProperty.call(OPERATOR_INVERSES, node.operator)) { + if (node.type === "BinaryExpression" && Object.hasOwn(OPERATOR_INVERSES, node.operator)) { const operatorToken = sourceCode.getFirstTokenBetween( node.left, node.right, diff --git a/tools/node_modules/eslint/lib/rules/no-unsafe-optional-chaining.js b/tools/node_modules/eslint/lib/rules/no-unsafe-optional-chaining.js index fe2bead856ed67..fa787939649f4a 100644 --- a/tools/node_modules/eslint/lib/rules/no-unsafe-optional-chaining.js +++ b/tools/node_modules/eslint/lib/rules/no-unsafe-optional-chaining.js @@ -94,7 +94,7 @@ module.exports = { break; case "SequenceExpression": checkUndefinedShortCircuit( - node.expressions[node.expressions.length - 1], + node.expressions.at(-1), reportFunc ); break; diff --git a/tools/node_modules/eslint/lib/rules/no-unused-private-class-members.js b/tools/node_modules/eslint/lib/rules/no-unused-private-class-members.js index 037be7d3eaae0f..bc05cd25180754 100644 --- a/tools/node_modules/eslint/lib/rules/no-unused-private-class-members.js +++ b/tools/node_modules/eslint/lib/rules/no-unused-private-class-members.js @@ -16,7 +16,7 @@ module.exports = { docs: { description: "Disallow unused private class members", - recommended: false, + recommended: true, url: "https://eslint.org/docs/latest/rules/no-unused-private-class-members" }, diff --git a/tools/node_modules/eslint/lib/rules/no-unused-vars.js b/tools/node_modules/eslint/lib/rules/no-unused-vars.js index f29e678d6fdd0a..d17253acf745c2 100644 --- a/tools/node_modules/eslint/lib/rules/no-unused-vars.js +++ b/tools/node_modules/eslint/lib/rules/no-unused-vars.js @@ -15,6 +15,11 @@ const astUtils = require("./utils/ast-utils"); // Typedefs //------------------------------------------------------------------------------ +/** + * A simple name for the types of variables that this rule supports + * @typedef {'array-destructure'|'catch-clause'|'parameter'|'variable'} VariableType + */ + /** * Bag of data used for formatting the `unusedVar` lint message. * @typedef {Object} UnusedVarMessageData @@ -23,6 +28,13 @@ const astUtils = require("./utils/ast-utils"); * @property {string} additional Any additional info to be appended at the end. */ +/** + * Bag of data used for formatting the `usedIgnoredVar` lint message. + * @typedef {Object} UsedIgnoredVarMessageData + * @property {string} varName The name of the unused var. + * @property {string} additional Any additional info to be appended at the end. + */ + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -70,6 +82,12 @@ module.exports = { }, destructuredArrayIgnorePattern: { type: "string" + }, + ignoreClassWithStaticInitBlock: { + type: "boolean" + }, + reportUsedIgnorePattern: { + type: "boolean" } }, additionalProperties: false @@ -79,7 +97,8 @@ module.exports = { ], messages: { - unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}." + unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}.", + usedIgnoredVar: "'{{varName}}' is marked as ignored but is used{{additional}}." } }, @@ -92,7 +111,9 @@ module.exports = { vars: "all", args: "after-used", ignoreRestSiblings: false, - caughtErrors: "none" + caughtErrors: "all", + ignoreClassWithStaticInitBlock: false, + reportUsedIgnorePattern: false }; const firstOption = context.options[0]; @@ -105,6 +126,8 @@ module.exports = { config.args = firstOption.args || config.args; config.ignoreRestSiblings = firstOption.ignoreRestSiblings || config.ignoreRestSiblings; config.caughtErrors = firstOption.caughtErrors || config.caughtErrors; + config.ignoreClassWithStaticInitBlock = firstOption.ignoreClassWithStaticInitBlock || config.ignoreClassWithStaticInitBlock; + config.reportUsedIgnorePattern = firstOption.reportUsedIgnorePattern || config.reportUsedIgnorePattern; if (firstOption.varsIgnorePattern) { config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern, "u"); @@ -124,6 +147,50 @@ module.exports = { } } + /** + * Gets a given variable's description and configured ignore pattern + * based on the provided variableType + * @param {VariableType} variableType a simple name for the types of variables that this rule supports + * @throws {Error} (Unreachable) + * @returns {[string | undefined, string | undefined]} the given variable's description and + * ignore pattern + */ + function getVariableDescription(variableType) { + let pattern; + let variableDescription; + + switch (variableType) { + case "array-destructure": + pattern = config.destructuredArrayIgnorePattern; + variableDescription = "elements of array destructuring"; + break; + + case "catch-clause": + pattern = config.caughtErrorsIgnorePattern; + variableDescription = "args"; + break; + + case "parameter": + pattern = config.argsIgnorePattern; + variableDescription = "args"; + break; + + case "variable": + pattern = config.varsIgnorePattern; + variableDescription = "vars"; + break; + + default: + throw new Error(`Unexpected variable type: ${variableType}`); + } + + if (pattern) { + pattern = pattern.toString(); + } + + return [variableDescription, pattern]; + } + /** * Generates the message data about the variable being defined and unused, * including the ignore pattern if configured. @@ -131,27 +198,42 @@ module.exports = { * @returns {UnusedVarMessageData} The message data to be used with this unused variable. */ function getDefinedMessageData(unusedVar) { - const defType = unusedVar.defs && unusedVar.defs[0] && unusedVar.defs[0].type; - let type; - let pattern; + const def = unusedVar.defs && unusedVar.defs[0]; + let additionalMessageData = ""; - if (defType === "CatchClause" && config.caughtErrorsIgnorePattern) { - type = "args"; - pattern = config.caughtErrorsIgnorePattern.toString(); - } else if (defType === "Parameter" && config.argsIgnorePattern) { - type = "args"; - pattern = config.argsIgnorePattern.toString(); - } else if (defType !== "Parameter" && config.varsIgnorePattern) { - type = "vars"; - pattern = config.varsIgnorePattern.toString(); - } + if (def) { + let pattern; + let variableDescription; - const additional = type ? `. Allowed unused ${type} must match ${pattern}` : ""; + switch (def.type) { + case "CatchClause": + if (config.caughtErrorsIgnorePattern) { + [variableDescription, pattern] = getVariableDescription("catch-clause"); + } + break; + + case "Parameter": + if (config.argsIgnorePattern) { + [variableDescription, pattern] = getVariableDescription("parameter"); + } + break; + + default: + if (config.varsIgnorePattern) { + [variableDescription, pattern] = getVariableDescription("variable"); + } + break; + } + + if (pattern && variableDescription) { + additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`; + } + } return { varName: unusedVar.name, action: "defined", - additional + additional: additionalMessageData }; } @@ -162,19 +244,51 @@ module.exports = { * @returns {UnusedVarMessageData} The message data to be used with this unused variable. */ function getAssignedMessageData(unusedVar) { - const def = unusedVar.defs[0]; - let additional = ""; + const def = unusedVar.defs && unusedVar.defs[0]; + let additionalMessageData = ""; + + if (def) { + let pattern; + let variableDescription; - if (config.destructuredArrayIgnorePattern && def && def.name.parent.type === "ArrayPattern") { - additional = `. Allowed unused elements of array destructuring patterns must match ${config.destructuredArrayIgnorePattern.toString()}`; - } else if (config.varsIgnorePattern) { - additional = `. Allowed unused vars must match ${config.varsIgnorePattern.toString()}`; + if (def.name.parent.type === "ArrayPattern" && config.destructuredArrayIgnorePattern) { + [variableDescription, pattern] = getVariableDescription("array-destructure"); + } else if (config.varsIgnorePattern) { + [variableDescription, pattern] = getVariableDescription("variable"); + } + + if (pattern && variableDescription) { + additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`; + } } return { varName: unusedVar.name, action: "assigned a value", - additional + additional: additionalMessageData + }; + } + + /** + * Generate the warning message about a variable being used even though + * it is marked as being ignored. + * @param {Variable} variable eslint-scope variable object + * @param {VariableType} variableType a simple name for the types of variables that this rule supports + * @returns {UsedIgnoredVarMessageData} The message data to be used with + * this used ignored variable. + */ + function getUsedIgnoredMessageData(variable, variableType) { + const [variableDescription, pattern] = getVariableDescription(variableType); + + let additionalMessageData = ""; + + if (pattern && variableDescription) { + additionalMessageData = `. Used ${variableDescription} must not match ${pattern}`; + } + + return { + varName: variable.name, + additional: additionalMessageData }; } @@ -218,7 +332,7 @@ module.exports = { function hasRestSibling(node) { return node.type === "Property" && node.parent.type === "ObjectPattern" && - REST_PROPERTY_TYPE.test(node.parent.properties[node.parent.properties.length - 1].type); + REST_PROPERTY_TYPE.test(node.parent.properties.at(-1).type); } /** @@ -323,7 +437,7 @@ module.exports = { } if (parent.type === "SequenceExpression") { - const isLastExpression = parent.expressions[parent.expressions.length - 1] === node; + const isLastExpression = parent.expressions.at(-1) === node; if (!isLastExpression) { return true; @@ -392,7 +506,7 @@ module.exports = { while (parent && isInside(parent, rhsNode)) { switch (parent.type) { case "SequenceExpression": - if (parent.expressions[parent.expressions.length - 1] !== node) { + if (parent.expressions.at(-1) !== node) { return false; } break; @@ -527,8 +641,13 @@ module.exports = { * @private */ function isUsedVariable(variable) { - const functionNodes = getFunctionDefinitions(variable), - isFunctionDefinition = functionNodes.length > 0; + if (variable.eslintUsed) { + return true; + } + + const functionNodes = getFunctionDefinitions(variable); + const isFunctionDefinition = functionNodes.length > 0; + let rhsNode = null; return variable.references.some(ref => { @@ -584,8 +703,13 @@ module.exports = { continue; } - // skip function expression names and variables marked with markVariableAsUsed() - if (scope.functionExpressionScope || variable.eslintUsed) { + // skip function expression names + if (scope.functionExpressionScope) { + continue; + } + + // skip variables marked with markVariableAsUsed() + if (!config.reportUsedIgnorePattern && variable.eslintUsed) { continue; } @@ -610,9 +734,25 @@ module.exports = { config.destructuredArrayIgnorePattern && config.destructuredArrayIgnorePattern.test(def.name.name) ) { + if (config.reportUsedIgnorePattern && isUsedVariable(variable)) { + context.report({ + node: def.name, + messageId: "usedIgnoredVar", + data: getUsedIgnoredMessageData(variable, "array-destructure") + }); + } + continue; } + if (type === "ClassName") { + const hasStaticBlock = def.node.body.body.some(node => node.type === "StaticBlock"); + + if (config.ignoreClassWithStaticInitBlock && hasStaticBlock) { + continue; + } + } + // skip catch variables if (type === "CatchClause") { if (config.caughtErrors === "none") { @@ -621,11 +761,17 @@ module.exports = { // skip ignored parameters if (config.caughtErrorsIgnorePattern && config.caughtErrorsIgnorePattern.test(def.name.name)) { + if (config.reportUsedIgnorePattern && isUsedVariable(variable)) { + context.report({ + node: def.name, + messageId: "usedIgnoredVar", + data: getUsedIgnoredMessageData(variable, "catch-clause") + }); + } + continue; } - } - - if (type === "Parameter") { + } else if (type === "Parameter") { // skip any setter argument if ((def.node.parent.type === "Property" || def.node.parent.type === "MethodDefinition") && def.node.parent.kind === "set") { @@ -639,6 +785,14 @@ module.exports = { // skip ignored parameters if (config.argsIgnorePattern && config.argsIgnorePattern.test(def.name.name)) { + if (config.reportUsedIgnorePattern && isUsedVariable(variable)) { + context.report({ + node: def.name, + messageId: "usedIgnoredVar", + data: getUsedIgnoredMessageData(variable, "parameter") + }); + } + continue; } @@ -650,6 +804,14 @@ module.exports = { // skip ignored variables if (config.varsIgnorePattern && config.varsIgnorePattern.test(def.name.name)) { + if (config.reportUsedIgnorePattern && isUsedVariable(variable)) { + context.report({ + node: def.name, + messageId: "usedIgnoredVar", + data: getUsedIgnoredMessageData(variable, "variable") + }); + } + continue; } } @@ -688,7 +850,7 @@ module.exports = { let referenceToReport; if (writeReferences.length > 0) { - referenceToReport = writeReferences[writeReferences.length - 1]; + referenceToReport = writeReferences.at(-1); } context.report({ @@ -713,6 +875,5 @@ module.exports = { } } }; - } }; diff --git a/tools/node_modules/eslint/lib/rules/no-useless-assignment.js b/tools/node_modules/eslint/lib/rules/no-useless-assignment.js new file mode 100644 index 00000000000000..cac8ba1fcd1d6c --- /dev/null +++ b/tools/node_modules/eslint/lib/rules/no-useless-assignment.js @@ -0,0 +1,566 @@ +/** + * @fileoverview A rule to disallow unnecessary assignments`. + * @author Yosuke Ota + */ + +"use strict"; + +const { findVariable } = require("@eslint-community/eslint-utils"); + +//------------------------------------------------------------------------------ +// Types +//------------------------------------------------------------------------------ + +/** @typedef {import("estree").Node} ASTNode */ +/** @typedef {import("estree").Pattern} Pattern */ +/** @typedef {import("estree").Identifier} Identifier */ +/** @typedef {import("estree").VariableDeclarator} VariableDeclarator */ +/** @typedef {import("estree").AssignmentExpression} AssignmentExpression */ +/** @typedef {import("estree").UpdateExpression} UpdateExpression */ +/** @typedef {import("estree").Expression} Expression */ +/** @typedef {import("eslint-scope").Scope} Scope */ +/** @typedef {import("eslint-scope").Variable} Variable */ +/** @typedef {import("../linter/code-path-analysis/code-path")} CodePath */ +/** @typedef {import("../linter/code-path-analysis/code-path-segment")} CodePathSegment */ + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Extract identifier from the given pattern node used on the left-hand side of the assignment. + * @param {Pattern} pattern The pattern node to extract identifier + * @returns {Iterable} The extracted identifier + */ +function *extractIdentifiersFromPattern(pattern) { + switch (pattern.type) { + case "Identifier": + yield pattern; + return; + case "ObjectPattern": + for (const property of pattern.properties) { + yield* extractIdentifiersFromPattern(property.type === "Property" ? property.value : property); + } + return; + case "ArrayPattern": + for (const element of pattern.elements) { + if (!element) { + continue; + } + yield* extractIdentifiersFromPattern(element); + } + return; + case "RestElement": + yield* extractIdentifiersFromPattern(pattern.argument); + return; + case "AssignmentPattern": + yield* extractIdentifiersFromPattern(pattern.left); + + // no default + } +} + + +/** + * Checks whether the given identifier node is evaluated after the assignment identifier. + * @param {AssignmentInfo} assignment The assignment info. + * @param {Identifier} identifier The identifier to check. + * @returns {boolean} `true` if the given identifier node is evaluated after the assignment identifier. + */ +function isIdentifierEvaluatedAfterAssignment(assignment, identifier) { + if (identifier.range[0] < assignment.identifier.range[1]) { + return false; + } + if ( + assignment.expression && + assignment.expression.range[0] <= identifier.range[0] && + identifier.range[1] <= assignment.expression.range[1] + ) { + + /* + * The identifier node is in an expression that is evaluated before the assignment. + * e.g. x = id; + * ^^ identifier to check + * ^ assignment identifier + */ + return false; + } + + /* + * e.g. + * x = 42; id; + * ^^ identifier to check + * ^ assignment identifier + * let { x, y = id } = obj; + * ^^ identifier to check + * ^ assignment identifier + */ + return true; +} + +/** + * Checks whether the given identifier node is used between the assigned identifier and the equal sign. + * + * e.g. let { x, y = x } = obj; + * ^ identifier to check + * ^ assigned identifier + * @param {AssignmentInfo} assignment The assignment info. + * @param {Identifier} identifier The identifier to check. + * @returns {boolean} `true` if the given identifier node is used between the assigned identifier and the equal sign. + */ +function isIdentifierUsedBetweenAssignedAndEqualSign(assignment, identifier) { + if (!assignment.expression) { + return false; + } + return ( + assignment.identifier.range[1] <= identifier.range[0] && + identifier.range[1] <= assignment.expression.range[0] + ); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +/** @type {import('../shared/types').Rule} */ +module.exports = { + meta: { + type: "problem", + + docs: { + description: "Disallow variable assignments when the value is not used", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-useless-assignment" + }, + + schema: [], + + messages: { + unnecessaryAssignment: "This assigned value is not used in subsequent statements." + } + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * @typedef {Object} ScopeStack + * @property {CodePath} codePath The code path of this scope stack. + * @property {Scope} scope The scope of this scope stack. + * @property {ScopeStack} upper The upper scope stack. + * @property {Record} segments The map of ScopeStackSegmentInfo. + * @property {Set} currentSegments The current CodePathSegments. + * @property {Map} assignments The map of list of AssignmentInfo for each variable. + */ + /** + * @typedef {Object} ScopeStackSegmentInfo + * @property {CodePathSegment} segment The code path segment. + * @property {Identifier|null} first The first identifier that appears within the segment. + * @property {Identifier|null} last The last identifier that appears within the segment. + * `first` and `last` are used to determine whether an identifier exists within the segment position range. + * Since it is used as a range of segments, we should originally hold all nodes, not just identifiers, + * but since the only nodes to be judged are identifiers, it is sufficient to have a range of identifiers. + */ + /** + * @typedef {Object} AssignmentInfo + * @property {Variable} variable The variable that is assigned. + * @property {Identifier} identifier The identifier that is assigned. + * @property {VariableDeclarator|AssignmentExpression|UpdateExpression} node The node where the variable was updated. + * @property {Expression|null} expression The expression that is evaluated before the assignment. + * @property {CodePathSegment[]} segments The code path segments where the assignment was made. + */ + + /** @type {ScopeStack} */ + let scopeStack = null; + + /** @type {Set} */ + const codePathStartScopes = new Set(); + + /** + * Gets the scope of code path start from given scope + * @param {Scope} scope The initial scope + * @returns {Scope} The scope of code path start + * @throws {Error} Unexpected error + */ + function getCodePathStartScope(scope) { + let target = scope; + + while (target) { + if (codePathStartScopes.has(target)) { + return target; + } + target = target.upper; + } + + // Should be unreachable + return null; + } + + /** + * Verify the given scope stack. + * @param {ScopeStack} target The scope stack to verify. + * @returns {void} + */ + function verify(target) { + + /** + * Checks whether the given identifier is used in the segment. + * @param {CodePathSegment} segment The code path segment. + * @param {Identifier} identifier The identifier to check. + * @returns {boolean} `true` if the identifier is used in the segment. + */ + function isIdentifierUsedInSegment(segment, identifier) { + const segmentInfo = target.segments[segment.id]; + + return ( + segmentInfo.first && + segmentInfo.last && + segmentInfo.first.range[0] <= identifier.range[0] && + identifier.range[1] <= segmentInfo.last.range[1] + ); + } + + /** + * Verifies whether the given assignment info is an used assignment. + * Report if it is an unused assignment. + * @param {AssignmentInfo} targetAssignment The assignment info to verify. + * @param {AssignmentInfo[]} allAssignments The list of all assignment info for variables. + * @returns {void} + */ + function verifyAssignmentIsUsed(targetAssignment, allAssignments) { + + /** + * @typedef {Object} SubsequentSegmentData + * @property {CodePathSegment} segment The code path segment + * @property {AssignmentInfo} [assignment] The first occurrence of the assignment within the segment. + * There is no need to check if the variable is used after this assignment, + * as the value it was assigned will be used. + */ + + /** + * Information used in `getSubsequentSegments()`. + * To avoid unnecessary iterations, cache information that has already been iterated over, + * and if additional iterations are needed, start iterating from the retained position. + */ + const subsequentSegmentData = { + + /** + * Cache of subsequent segment information list that have already been iterated. + * @type {SubsequentSegmentData[]} + */ + results: [], + + /** + * Subsequent segments that have already been iterated on. Used to avoid infinite loops. + * @type {Set} + */ + subsequentSegments: new Set(), + + /** + * Unexplored code path segment. + * If additional iterations are needed, consume this information and iterate. + * @type {CodePathSegment[]} + */ + queueSegments: targetAssignment.segments.flatMap(segment => segment.nextSegments) + }; + + + /** + * Gets the subsequent segments from the segment of + * the assignment currently being validated (targetAssignment). + * @returns {Iterable} the subsequent segments + */ + function *getSubsequentSegments() { + yield* subsequentSegmentData.results; + + while (subsequentSegmentData.queueSegments.length > 0) { + const nextSegment = subsequentSegmentData.queueSegments.shift(); + + if (subsequentSegmentData.subsequentSegments.has(nextSegment)) { + continue; + } + subsequentSegmentData.subsequentSegments.add(nextSegment); + + const assignmentInSegment = allAssignments + .find(otherAssignment => ( + otherAssignment.segments.includes(nextSegment) && + !isIdentifierUsedBetweenAssignedAndEqualSign(otherAssignment, targetAssignment.identifier) + )); + + if (!assignmentInSegment) { + + /* + * Stores the next segment to explore. + * If `assignmentInSegment` exists, + * we are guarding it because we don't need to explore the next segment. + */ + subsequentSegmentData.queueSegments.push(...nextSegment.nextSegments); + } + + /** @type {SubsequentSegmentData} */ + const result = { + segment: nextSegment, + assignment: assignmentInSegment + }; + + subsequentSegmentData.results.push(result); + yield result; + } + } + + + const readReferences = targetAssignment.variable.references.filter(reference => reference.isRead()); + + if (!readReferences.length) { + + /* + * It is not just an unnecessary assignment, but an unnecessary (unused) variable + * and thus should not be reported by this rule because it is reported by `no-unused-vars`. + */ + return; + } + + /** + * Other assignment on the current segment and after current assignment. + */ + const otherAssignmentAfterTargetAssignment = allAssignments + .find(assignment => { + if ( + assignment === targetAssignment || + assignment.segments.length && assignment.segments.every(segment => !targetAssignment.segments.includes(segment)) + ) { + return false; + } + if (isIdentifierEvaluatedAfterAssignment(targetAssignment, assignment.identifier)) { + return true; + } + if ( + assignment.expression && + assignment.expression.range[0] <= targetAssignment.identifier.range[0] && + targetAssignment.identifier.range[1] <= assignment.expression.range[1] + ) { + + /* + * The target assignment is in an expression that is evaluated before the assignment. + * e.g. x=(x=1); + * ^^^ targetAssignment + * ^^^^^^^ assignment + */ + return true; + } + + return false; + }); + + for (const reference of readReferences) { + + /* + * If the scope of the reference is outside the current code path scope, + * we cannot track whether this assignment is not used. + * For example, it can also be called asynchronously. + */ + if (target.scope !== getCodePathStartScope(reference.from)) { + return; + } + + // Checks if it is used in the same segment as the target assignment. + if ( + isIdentifierEvaluatedAfterAssignment(targetAssignment, reference.identifier) && + ( + isIdentifierUsedBetweenAssignedAndEqualSign(targetAssignment, reference.identifier) || + targetAssignment.segments.some(segment => isIdentifierUsedInSegment(segment, reference.identifier)) + ) + ) { + + if ( + otherAssignmentAfterTargetAssignment && + isIdentifierEvaluatedAfterAssignment(otherAssignmentAfterTargetAssignment, reference.identifier) + ) { + + // There was another assignment before the reference. Therefore, it has not been used yet. + continue; + } + + // Uses in statements after the written identifier. + return; + } + + if (otherAssignmentAfterTargetAssignment) { + + /* + * The assignment was followed by another assignment in the same segment. + * Therefore, there is no need to check the next segment. + */ + continue; + } + + // Check subsequent segments. + for (const subsequentSegment of getSubsequentSegments()) { + if (isIdentifierUsedInSegment(subsequentSegment.segment, reference.identifier)) { + if ( + subsequentSegment.assignment && + isIdentifierEvaluatedAfterAssignment(subsequentSegment.assignment, reference.identifier) + ) { + + // There was another assignment before the reference. Therefore, it has not been used yet. + continue; + } + + // It is used + return; + } + } + } + context.report({ + node: targetAssignment.identifier, + messageId: "unnecessaryAssignment" + }); + } + + // Verify that each assignment in the code path is used. + for (const assignments of target.assignments.values()) { + assignments.sort((a, b) => a.identifier.range[0] - b.identifier.range[0]); + for (const assignment of assignments) { + verifyAssignmentIsUsed(assignment, assignments); + } + } + } + + return { + onCodePathStart(codePath, node) { + const scope = sourceCode.getScope(node); + + scopeStack = { + upper: scopeStack, + codePath, + scope, + segments: Object.create(null), + currentSegments: new Set(), + assignments: new Map() + }; + codePathStartScopes.add(scopeStack.scope); + }, + onCodePathEnd() { + verify(scopeStack); + + scopeStack = scopeStack.upper; + }, + onCodePathSegmentStart(segment) { + const segmentInfo = { segment, first: null, last: null }; + + scopeStack.segments[segment.id] = segmentInfo; + scopeStack.currentSegments.add(segment); + }, + onCodePathSegmentEnd(segment) { + scopeStack.currentSegments.delete(segment); + }, + Identifier(node) { + for (const segment of scopeStack.currentSegments) { + const segmentInfo = scopeStack.segments[segment.id]; + + if (!segmentInfo.first) { + segmentInfo.first = node; + } + segmentInfo.last = node; + } + }, + ":matches(VariableDeclarator[init!=null], AssignmentExpression, UpdateExpression):exit"(node) { + if (scopeStack.currentSegments.size === 0) { + + // Ignore unreachable segments + return; + } + + const assignments = scopeStack.assignments; + + let pattern; + let expression = null; + + if (node.type === "VariableDeclarator") { + pattern = node.id; + expression = node.init; + } else if (node.type === "AssignmentExpression") { + pattern = node.left; + expression = node.right; + } else { // UpdateExpression + pattern = node.argument; + } + + for (const identifier of extractIdentifiersFromPattern(pattern)) { + const scope = sourceCode.getScope(identifier); + + /** @type {Variable} */ + const variable = findVariable(scope, identifier); + + if (!variable) { + continue; + } + + // We don't know where global variables are used. + if (variable.scope.type === "global" && variable.defs.length === 0) { + continue; + } + + /* + * If the scope of the variable is outside the current code path scope, + * we cannot track whether this assignment is not used. + */ + if (scopeStack.scope !== getCodePathStartScope(variable.scope)) { + continue; + } + + // Variables marked by `markVariableAsUsed()` or + // exported by "exported" block comment. + if (variable.eslintUsed) { + continue; + } + + // Variables exported by ESM export syntax + if (variable.scope.type === "module") { + if ( + variable.defs + .some(def => ( + (def.type === "Variable" && def.parent.parent.type === "ExportNamedDeclaration") || + ( + def.type === "FunctionName" && + ( + def.node.parent.type === "ExportNamedDeclaration" || + def.node.parent.type === "ExportDefaultDeclaration" + ) + ) || + ( + def.type === "ClassName" && + ( + def.node.parent.type === "ExportNamedDeclaration" || + def.node.parent.type === "ExportDefaultDeclaration" + ) + ) + )) + ) { + continue; + } + if (variable.references.some(reference => reference.identifier.parent.type === "ExportSpecifier")) { + + // It have `export { ... }` reference. + continue; + } + } + + let list = assignments.get(variable); + + if (!list) { + list = []; + assignments.set(variable, list); + } + list.push({ + variable, + identifier, + node, + expression, + segments: [...scopeStack.currentSegments] + }); + } + } + }; + } +}; diff --git a/tools/node_modules/eslint/lib/rules/no-useless-backreference.js b/tools/node_modules/eslint/lib/rules/no-useless-backreference.js index 7ca43c8b260777..2a54de2e3b8a07 100644 --- a/tools/node_modules/eslint/lib/rules/no-useless-backreference.js +++ b/tools/node_modules/eslint/lib/rules/no-useless-backreference.js @@ -138,7 +138,7 @@ module.exports = { // the opposite of the previous when the regex is matching backward in a lookbehind context. messageId = "backward"; - } else if (groupCut[groupCut.length - 1].type === "Alternative") { + } else if (groupCut.at(-1).type === "Alternative") { // group's and bref's ancestor nodes below the lowest common ancestor are sibling alternatives => they're disjunctive. messageId = "disjunctive"; diff --git a/tools/node_modules/eslint/lib/rules/no-useless-computed-key.js b/tools/node_modules/eslint/lib/rules/no-useless-computed-key.js index f2d9f3341f507b..5cc652bea265e8 100644 --- a/tools/node_modules/eslint/lib/rules/no-useless-computed-key.js +++ b/tools/node_modules/eslint/lib/rules/no-useless-computed-key.js @@ -101,7 +101,7 @@ module.exports = { properties: { enforceForClassMembers: { type: "boolean", - default: false + default: true } }, additionalProperties: false @@ -114,7 +114,7 @@ module.exports = { }, create(context) { const sourceCode = context.sourceCode; - const enforceForClassMembers = context.options[0] && context.options[0].enforceForClassMembers; + const enforceForClassMembers = context.options[0]?.enforceForClassMembers ?? true; /** * Reports a given node if it violated this rule. diff --git a/tools/node_modules/eslint/lib/rules/no-useless-return.js b/tools/node_modules/eslint/lib/rules/no-useless-return.js index 81d61051053e87..1f85cdb9aafc4d 100644 --- a/tools/node_modules/eslint/lib/rules/no-useless-return.js +++ b/tools/node_modules/eslint/lib/rules/no-useless-return.js @@ -146,7 +146,9 @@ module.exports = { continue; } - uselessReturns.push(...segmentInfoMap.get(segment).uselessReturns); + if (segmentInfoMap.has(segment)) { + uselessReturns.push(...segmentInfoMap.get(segment).uselessReturns); + } } return uselessReturns; @@ -182,6 +184,10 @@ module.exports = { const info = segmentInfoMap.get(segment); + if (!info) { + return; + } + info.uselessReturns = info.uselessReturns.filter(node => { if (scopeInfo.traversedTryBlockStatements && scopeInfo.traversedTryBlockStatements.length > 0) { const returnInitialRange = node.range[0]; @@ -275,7 +281,6 @@ module.exports = { * NOTE: This event is notified for only reachable segments. */ onCodePathSegmentStart(segment) { - scopeInfo.currentSegments.add(segment); const info = { diff --git a/tools/node_modules/eslint/lib/rules/object-curly-spacing.js b/tools/node_modules/eslint/lib/rules/object-curly-spacing.js index 4463bcd5af175c..6cb0b0f931359c 100644 --- a/tools/node_modules/eslint/lib/rules/object-curly-spacing.js +++ b/tools/node_modules/eslint/lib/rules/object-curly-spacing.js @@ -217,7 +217,7 @@ module.exports = { * @returns {Token} '}' token. */ function getClosingBraceOfObject(node) { - const lastProperty = node.properties[node.properties.length - 1]; + const lastProperty = node.properties.at(-1); return sourceCode.getTokenAfter(lastProperty, astUtils.isClosingBraceToken); } @@ -251,7 +251,7 @@ module.exports = { } let firstSpecifier = node.specifiers[0]; - const lastSpecifier = node.specifiers[node.specifiers.length - 1]; + const lastSpecifier = node.specifiers.at(-1); if (lastSpecifier.type !== "ImportSpecifier") { return; @@ -279,7 +279,7 @@ module.exports = { } const firstSpecifier = node.specifiers[0], - lastSpecifier = node.specifiers[node.specifiers.length - 1], + lastSpecifier = node.specifiers.at(-1), first = sourceCode.getTokenBefore(firstSpecifier), last = sourceCode.getTokenAfter(lastSpecifier, astUtils.isNotCommaToken), second = sourceCode.getTokenAfter(first, { includeComments: true }), diff --git a/tools/node_modules/eslint/lib/rules/object-property-newline.js b/tools/node_modules/eslint/lib/rules/object-property-newline.js index 6ffa06421f022e..8a08676cfa1480 100644 --- a/tools/node_modules/eslint/lib/rules/object-property-newline.js +++ b/tools/node_modules/eslint/lib/rules/object-property-newline.js @@ -63,7 +63,7 @@ module.exports = { if (allowSameLine) { if (node.properties.length > 1) { const firstTokenOfFirstProperty = sourceCode.getFirstToken(node.properties[0]); - const lastTokenOfLastProperty = sourceCode.getLastToken(node.properties[node.properties.length - 1]); + const lastTokenOfLastProperty = sourceCode.getLastToken(node.properties.at(-1)); if (firstTokenOfFirstProperty.loc.end.line === lastTokenOfLastProperty.loc.start.line) { diff --git a/tools/node_modules/eslint/lib/rules/one-var.js b/tools/node_modules/eslint/lib/rules/one-var.js index abb1525b1a5c9a..ba461a407cac03 100644 --- a/tools/node_modules/eslint/lib/rules/one-var.js +++ b/tools/node_modules/eslint/lib/rules/one-var.js @@ -109,12 +109,12 @@ module.exports = { options.var = { uninitialized: mode.var, initialized: mode.var }; options.let = { uninitialized: mode.let, initialized: mode.let }; options.const = { uninitialized: mode.const, initialized: mode.const }; - if (Object.prototype.hasOwnProperty.call(mode, "uninitialized")) { + if (Object.hasOwn(mode, "uninitialized")) { options.var.uninitialized = mode.uninitialized; options.let.uninitialized = mode.uninitialized; options.const.uninitialized = mode.uninitialized; } - if (Object.prototype.hasOwnProperty.call(mode, "initialized")) { + if (Object.hasOwn(mode, "initialized")) { options.var.initialized = mode.initialized; options.let.initialized = mode.initialized; options.const.initialized = mode.initialized; @@ -216,11 +216,11 @@ module.exports = { let currentScope; if (statementType === "var") { - currentScope = functionStack[functionStack.length - 1]; + currentScope = functionStack.at(-1); } else if (statementType === "let") { - currentScope = blockStack[blockStack.length - 1].let; + currentScope = blockStack.at(-1).let; } else if (statementType === "const") { - currentScope = blockStack[blockStack.length - 1].const; + currentScope = blockStack.at(-1).const; } return currentScope; } diff --git a/tools/node_modules/eslint/lib/rules/padded-blocks.js b/tools/node_modules/eslint/lib/rules/padded-blocks.js index ec4756ba739a08..d0c045430e4891 100644 --- a/tools/node_modules/eslint/lib/rules/padded-blocks.js +++ b/tools/node_modules/eslint/lib/rules/padded-blocks.js @@ -84,18 +84,18 @@ module.exports = { options.switches = shouldHavePadding; options.classes = shouldHavePadding; } else { - if (Object.prototype.hasOwnProperty.call(typeOptions, "blocks")) { + if (Object.hasOwn(typeOptions, "blocks")) { options.blocks = typeOptions.blocks === "always"; } - if (Object.prototype.hasOwnProperty.call(typeOptions, "switches")) { + if (Object.hasOwn(typeOptions, "switches")) { options.switches = typeOptions.switches === "always"; } - if (Object.prototype.hasOwnProperty.call(typeOptions, "classes")) { + if (Object.hasOwn(typeOptions, "classes")) { options.classes = typeOptions.classes === "always"; } } - if (Object.prototype.hasOwnProperty.call(exceptOptions, "allowSingleLineBlocks")) { + if (Object.hasOwn(exceptOptions, "allowSingleLineBlocks")) { options.allowSingleLineBlocks = exceptOptions.allowSingleLineBlocks === true; } @@ -277,7 +277,7 @@ module.exports = { const rule = {}; - if (Object.prototype.hasOwnProperty.call(options, "switches")) { + if (Object.hasOwn(options, "switches")) { rule.SwitchStatement = function(node) { if (node.cases.length === 0) { return; @@ -286,7 +286,7 @@ module.exports = { }; } - if (Object.prototype.hasOwnProperty.call(options, "blocks")) { + if (Object.hasOwn(options, "blocks")) { rule.BlockStatement = function(node) { if (node.body.length === 0) { return; @@ -296,7 +296,7 @@ module.exports = { rule.StaticBlock = rule.BlockStatement; } - if (Object.prototype.hasOwnProperty.call(options, "classes")) { + if (Object.hasOwn(options, "classes")) { rule.ClassBody = function(node) { if (node.body.length === 0) { return; diff --git a/tools/node_modules/eslint/lib/rules/prefer-arrow-callback.js b/tools/node_modules/eslint/lib/rules/prefer-arrow-callback.js index d22e508beb0500..b23696dd64cd9b 100644 --- a/tools/node_modules/eslint/lib/rules/prefer-arrow-callback.js +++ b/tools/node_modules/eslint/lib/rules/prefer-arrow-callback.js @@ -220,7 +220,7 @@ module.exports = { // If there are below, it cannot replace with arrow functions merely. ThisExpression() { - const info = stack[stack.length - 1]; + const info = stack.at(-1); if (info) { info.this = true; @@ -228,7 +228,7 @@ module.exports = { }, Super() { - const info = stack[stack.length - 1]; + const info = stack.at(-1); if (info) { info.super = true; @@ -236,7 +236,7 @@ module.exports = { }, MetaProperty(node) { - const info = stack[stack.length - 1]; + const info = stack.at(-1); if (info && checkMetaProperty(node, "new", "target")) { info.meta = true; diff --git a/tools/node_modules/eslint/lib/rules/prefer-reflect.js b/tools/node_modules/eslint/lib/rules/prefer-reflect.js index d579b486b78c33..7da45237c89762 100644 --- a/tools/node_modules/eslint/lib/rules/prefer-reflect.js +++ b/tools/node_modules/eslint/lib/rules/prefer-reflect.js @@ -105,7 +105,7 @@ module.exports = { CallExpression(node) { const methodName = (node.callee.property || {}).name; const isReflectCall = (node.callee.object || {}).name === "Reflect"; - const hasReflectSubstitute = Object.prototype.hasOwnProperty.call(reflectSubstitutes, methodName); + const hasReflectSubstitute = Object.hasOwn(reflectSubstitutes, methodName); const userConfiguredException = exceptions.includes(methodName); if (hasReflectSubstitute && !isReflectCall && !userConfiguredException) { diff --git a/tools/node_modules/eslint/lib/rules/prefer-regex-literals.js b/tools/node_modules/eslint/lib/rules/prefer-regex-literals.js index ffaaeac3f2792a..dd18f7ec2e3a91 100644 --- a/tools/node_modules/eslint/lib/rules/prefer-regex-literals.js +++ b/tools/node_modules/eslint/lib/rules/prefer-regex-literals.js @@ -34,7 +34,7 @@ function isStringLiteral(node) { * @returns {boolean} True if the node is a regex literal. */ function isRegexLiteral(node) { - return node.type === "Literal" && Object.prototype.hasOwnProperty.call(node, "regex"); + return node.type === "Literal" && Object.hasOwn(node, "regex"); } const validPrecedingTokens = new Set([ diff --git a/tools/node_modules/eslint/lib/rules/prefer-template.js b/tools/node_modules/eslint/lib/rules/prefer-template.js index a2c8c72413549b..d7d70c50640d64 100644 --- a/tools/node_modules/eslint/lib/rules/prefer-template.js +++ b/tools/node_modules/eslint/lib/rules/prefer-template.js @@ -113,7 +113,7 @@ function endsWithTemplateCurly(node) { return startsWithTemplateCurly(node.right); } if (node.type === "TemplateLiteral") { - return node.expressions.length && node.quasis.length && node.quasis[node.quasis.length - 1].range[0] === node.quasis[node.quasis.length - 1].range[1]; + return node.expressions.length && node.quasis.length && node.quasis.at(-1).range[0] === node.quasis.at(-1).range[1]; } return node.type !== "Literal" || typeof node.value !== "string"; } diff --git a/tools/node_modules/eslint/lib/rules/radix.js b/tools/node_modules/eslint/lib/rules/radix.js index 7df97d98602529..efae749690a73e 100644 --- a/tools/node_modules/eslint/lib/rules/radix.js +++ b/tools/node_modules/eslint/lib/rules/radix.js @@ -133,8 +133,8 @@ module.exports = { messageId: "addRadixParameter10", fix(fixer) { const tokens = sourceCode.getTokens(node); - const lastToken = tokens[tokens.length - 1]; // Parenthesis. - const secondToLastToken = tokens[tokens.length - 2]; // May or may not be a comma. + const lastToken = tokens.at(-1); // Parenthesis. + const secondToLastToken = tokens.at(-2); // May or may not be a comma. const hasTrailingComma = secondToLastToken.type === "Punctuator" && secondToLastToken.value === ","; return fixer.insertTextBefore(lastToken, hasTrailingComma ? " 10," : ", 10"); diff --git a/tools/node_modules/eslint/lib/rules/require-jsdoc.js b/tools/node_modules/eslint/lib/rules/require-jsdoc.js deleted file mode 100644 index b6fedf2289f5e2..00000000000000 --- a/tools/node_modules/eslint/lib/rules/require-jsdoc.js +++ /dev/null @@ -1,122 +0,0 @@ -/** - * @fileoverview Rule to check for jsdoc presence. - * @author Gyandeep Singh - * @deprecated in ESLint v5.10.0 - */ -"use strict"; - -/** @type {import('../shared/types').Rule} */ -module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Require JSDoc comments", - recommended: false, - url: "https://eslint.org/docs/latest/rules/require-jsdoc" - }, - - schema: [ - { - type: "object", - properties: { - require: { - type: "object", - properties: { - ClassDeclaration: { - type: "boolean", - default: false - }, - MethodDefinition: { - type: "boolean", - default: false - }, - FunctionDeclaration: { - type: "boolean", - default: true - }, - ArrowFunctionExpression: { - type: "boolean", - default: false - }, - FunctionExpression: { - type: "boolean", - default: false - } - }, - additionalProperties: false, - default: {} - } - }, - additionalProperties: false - } - ], - - deprecated: true, - replacedBy: [], - - messages: { - missingJSDocComment: "Missing JSDoc comment." - } - }, - - create(context) { - const source = context.sourceCode; - const DEFAULT_OPTIONS = { - FunctionDeclaration: true, - MethodDefinition: false, - ClassDeclaration: false, - ArrowFunctionExpression: false, - FunctionExpression: false - }; - const options = Object.assign(DEFAULT_OPTIONS, context.options[0] && context.options[0].require); - - /** - * Report the error message - * @param {ASTNode} node node to report - * @returns {void} - */ - function report(node) { - context.report({ node, messageId: "missingJSDocComment" }); - } - - /** - * Check if the jsdoc comment is present or not. - * @param {ASTNode} node node to examine - * @returns {void} - */ - function checkJsDoc(node) { - const jsdocComment = source.getJSDocComment(node); - - if (!jsdocComment) { - report(node); - } - } - - return { - FunctionDeclaration(node) { - if (options.FunctionDeclaration) { - checkJsDoc(node); - } - }, - FunctionExpression(node) { - if ( - (options.MethodDefinition && node.parent.type === "MethodDefinition") || - (options.FunctionExpression && (node.parent.type === "VariableDeclarator" || (node.parent.type === "Property" && node === node.parent.value))) - ) { - checkJsDoc(node); - } - }, - ClassDeclaration(node) { - if (options.ClassDeclaration) { - checkJsDoc(node); - } - }, - ArrowFunctionExpression(node) { - if (options.ArrowFunctionExpression && node.parent.type === "VariableDeclarator") { - checkJsDoc(node); - } - } - }; - } -}; diff --git a/tools/node_modules/eslint/lib/rules/semi-style.js b/tools/node_modules/eslint/lib/rules/semi-style.js index caf2224df3d451..11caaf0b94527e 100644 --- a/tools/node_modules/eslint/lib/rules/semi-style.js +++ b/tools/node_modules/eslint/lib/rules/semi-style.js @@ -65,7 +65,7 @@ function isLastChild(node) { } const nodeList = getChildren(node.parent); - return nodeList !== null && nodeList[nodeList.length - 1] === node; // before `}` or etc. + return nodeList !== null && nodeList.at(-1) === node; // before `}` or etc. } /** @type {import('../shared/types').Rule} */ diff --git a/tools/node_modules/eslint/lib/rules/sort-imports.js b/tools/node_modules/eslint/lib/rules/sort-imports.js index 04814ed6fedcb6..9deaf1d4c97437 100644 --- a/tools/node_modules/eslint/lib/rules/sort-imports.js +++ b/tools/node_modules/eslint/lib/rules/sort-imports.js @@ -208,7 +208,7 @@ module.exports = { } return fixer.replaceTextRange( - [importSpecifiers[0].range[0], importSpecifiers[importSpecifiers.length - 1].range[1]], + [importSpecifiers[0].range[0], importSpecifiers.at(-1).range[1]], importSpecifiers // Clone the importSpecifiers array to avoid mutating it diff --git a/tools/node_modules/eslint/lib/rules/sort-keys.js b/tools/node_modules/eslint/lib/rules/sort-keys.js index 088b5890f30c09..e355e8afdc8d71 100644 --- a/tools/node_modules/eslint/lib/rules/sort-keys.js +++ b/tools/node_modules/eslint/lib/rules/sort-keys.js @@ -185,7 +185,7 @@ module.exports = { }); // check blank line between the current node and the last token - if (!isBlankLineBetweenNodes && (node.loc.start.line - tokens[tokens.length - 1].loc.end.line > 1)) { + if (!isBlankLineBetweenNodes && (node.loc.start.line - tokens.at(-1).loc.end.line > 1)) { isBlankLineBetweenNodes = true; } diff --git a/tools/node_modules/eslint/lib/rules/sort-vars.js b/tools/node_modules/eslint/lib/rules/sort-vars.js index 8fd723fd4e5b06..21bfb88e8dd1cc 100644 --- a/tools/node_modules/eslint/lib/rules/sort-vars.js +++ b/tools/node_modules/eslint/lib/rules/sort-vars.js @@ -66,7 +66,7 @@ module.exports = { return null; } return fixer.replaceTextRange( - [idDeclarations[0].range[0], idDeclarations[idDeclarations.length - 1].range[1]], + [idDeclarations[0].range[0], idDeclarations.at(-1).range[1]], idDeclarations // Clone the idDeclarations array to avoid mutating it diff --git a/tools/node_modules/eslint/lib/rules/space-unary-ops.js b/tools/node_modules/eslint/lib/rules/space-unary-ops.js index aed43e7249eb9b..ab6c3cb6cd9235 100644 --- a/tools/node_modules/eslint/lib/rules/space-unary-ops.js +++ b/tools/node_modules/eslint/lib/rules/space-unary-ops.js @@ -87,7 +87,7 @@ module.exports = { * @returns {boolean} Whether or not an override has been provided for the operator */ function overrideExistsForOperator(operator) { - return options.overrides && Object.prototype.hasOwnProperty.call(options.overrides, operator); + return options.overrides && Object.hasOwn(options.overrides, operator); } /** diff --git a/tools/node_modules/eslint/lib/rules/strict.js b/tools/node_modules/eslint/lib/rules/strict.js index f9dd7500be3ffc..fd970f279e18ca 100644 --- a/tools/node_modules/eslint/lib/rules/strict.js +++ b/tools/node_modules/eslint/lib/rules/strict.js @@ -173,7 +173,7 @@ module.exports = { function enterFunctionInFunctionMode(node, useStrictDirectives) { const isInClass = classScopes.length > 0, isParentGlobal = scopes.length === 0 && classScopes.length === 0, - isParentStrict = scopes.length > 0 && scopes[scopes.length - 1], + isParentStrict = scopes.length > 0 && scopes.at(-1), isStrict = useStrictDirectives.length > 0; if (isStrict) { diff --git a/tools/node_modules/eslint/lib/rules/use-isnan.js b/tools/node_modules/eslint/lib/rules/use-isnan.js index 21dc395290273d..5c0b65feefbad8 100644 --- a/tools/node_modules/eslint/lib/rules/use-isnan.js +++ b/tools/node_modules/eslint/lib/rules/use-isnan.js @@ -21,9 +21,17 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the node is 'NaN' identifier. */ function isNaNIdentifier(node) { - return Boolean(node) && ( - astUtils.isSpecificId(node, "NaN") || - astUtils.isSpecificMemberAccess(node, "Number", "NaN") + if (!node) { + return false; + } + + const nodeToCheck = node.type === "SequenceExpression" + ? node.expressions.at(-1) + : node; + + return ( + astUtils.isSpecificId(nodeToCheck, "NaN") || + astUtils.isSpecificMemberAccess(nodeToCheck, "Number", "NaN") ); } @@ -34,6 +42,7 @@ function isNaNIdentifier(node) { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + hasSuggestions: true, type: "problem", docs: { @@ -63,7 +72,10 @@ module.exports = { comparisonWithNaN: "Use the isNaN function to compare with NaN.", switchNaN: "'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch.", caseNaN: "'case NaN' can never match. Use Number.isNaN before the switch.", - indexOfNaN: "Array prototype method '{{ methodName }}' cannot find NaN." + indexOfNaN: "Array prototype method '{{ methodName }}' cannot find NaN.", + replaceWithIsNaN: "Replace with Number.isNaN.", + replaceWithCastingAndIsNaN: "Replace with Number.isNaN and cast to a Number.", + replaceWithFindIndex: "Replace with Array.prototype.{{ methodName }}." } }, @@ -71,6 +83,35 @@ module.exports = { const enforceForSwitchCase = !context.options[0] || context.options[0].enforceForSwitchCase; const enforceForIndexOf = context.options[0] && context.options[0].enforceForIndexOf; + const sourceCode = context.sourceCode; + + const fixableOperators = new Set(["==", "===", "!=", "!=="]); + const castableOperators = new Set(["==", "!="]); + + /** + * Get a fixer for a binary expression that compares to NaN. + * @param {ASTNode} node The node to fix. + * @param {function(string): string} wrapValue A function that wraps the compared value with a fix. + * @returns {function(Fixer): Fix} The fixer function. + */ + function getBinaryExpressionFixer(node, wrapValue) { + return fixer => { + const comparedValue = isNaNIdentifier(node.left) ? node.right : node.left; + const shouldWrap = comparedValue.type === "SequenceExpression"; + const shouldNegate = node.operator[0] === "!"; + + const negation = shouldNegate ? "!" : ""; + let comparedValueText = sourceCode.getText(comparedValue); + + if (shouldWrap) { + comparedValueText = `(${comparedValueText})`; + } + + const fixedValue = wrapValue(comparedValueText); + + return fixer.replaceText(node, `${negation}${fixedValue}`); + }; + } /** * Checks the given `BinaryExpression` node for `foo === NaN` and other comparisons. @@ -82,7 +123,32 @@ module.exports = { /^(?:[<>]|[!=]=)=?$/u.test(node.operator) && (isNaNIdentifier(node.left) || isNaNIdentifier(node.right)) ) { - context.report({ node, messageId: "comparisonWithNaN" }); + const suggestedFixes = []; + const NaNNode = isNaNIdentifier(node.left) ? node.left : node.right; + + const isSequenceExpression = NaNNode.type === "SequenceExpression"; + const isSuggestable = fixableOperators.has(node.operator) && !isSequenceExpression; + const isCastable = castableOperators.has(node.operator); + + if (isSuggestable) { + suggestedFixes.push({ + messageId: "replaceWithIsNaN", + fix: getBinaryExpressionFixer(node, value => `Number.isNaN(${value})`) + }); + + if (isCastable) { + suggestedFixes.push({ + messageId: "replaceWithCastingAndIsNaN", + fix: getBinaryExpressionFixer(node, value => `Number.isNaN(Number(${value}))`) + }); + } + } + + context.report({ + node, + messageId: "comparisonWithNaN", + suggest: suggestedFixes + }); } } @@ -116,10 +182,38 @@ module.exports = { if ( (methodName === "indexOf" || methodName === "lastIndexOf") && - node.arguments.length === 1 && + node.arguments.length <= 2 && isNaNIdentifier(node.arguments[0]) ) { - context.report({ node, messageId: "indexOfNaN", data: { methodName } }); + + /* + * To retain side effects, it's essential to address `NaN` beforehand, which + * is not possible with fixes like `arr.findIndex(Number.isNaN)`. + */ + const isSuggestable = node.arguments[0].type !== "SequenceExpression" && !node.arguments[1]; + const suggestedFixes = []; + + if (isSuggestable) { + const shouldWrap = callee.computed; + const findIndexMethod = methodName === "indexOf" ? "findIndex" : "findLastIndex"; + const propertyName = shouldWrap ? `"${findIndexMethod}"` : findIndexMethod; + + suggestedFixes.push({ + messageId: "replaceWithFindIndex", + data: { methodName: findIndexMethod }, + fix: fixer => [ + fixer.replaceText(callee.property, propertyName), + fixer.replaceText(node.arguments[0], "Number.isNaN") + ] + }); + } + + context.report({ + node, + messageId: "indexOfNaN", + data: { methodName }, + suggest: suggestedFixes + }); } } } diff --git a/tools/node_modules/eslint/lib/rules/utils/ast-utils.js b/tools/node_modules/eslint/lib/rules/utils/ast-utils.js index 962bdde0af1b44..ed9a31af34cfe8 100644 --- a/tools/node_modules/eslint/lib/rules/utils/ast-utils.js +++ b/tools/node_modules/eslint/lib/rules/utils/ast-utils.js @@ -19,6 +19,8 @@ const { lineBreakPattern, shebangPattern } = require("../../shared/ast-utils"); +const globals = require("../../../conf/globals"); +const { LATEST_ECMA_VERSION } = require("../../../conf/ecma-version"); //------------------------------------------------------------------------------ // Helpers @@ -46,6 +48,12 @@ const OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN = /^(?:[^\\]|\\.)*\\(?:[1-9]|0[0 const LOGICAL_ASSIGNMENT_OPERATORS = new Set(["&&=", "||=", "??="]); +/** + * All builtin global variables defined in the latest ECMAScript specification. + * @type {Record} Key is the name of the variable. Value is `true` if the variable is considered writable, `false` otherwise. + */ +const ECMASCRIPT_GLOBALS = globals[`es${LATEST_ECMA_VERSION}`]; + /** * Checks reference if is non initializer and writable. * @param {Reference} reference A reference to check. @@ -969,7 +977,7 @@ function isConstant(scope, node, inBooleanPosition) { return false; case "SequenceExpression": - return isConstant(scope, node.expressions[node.expressions.length - 1], inBooleanPosition); + return isConstant(scope, node.expressions.at(-1), inBooleanPosition); case "SpreadElement": return isConstant(scope, node.argument, inBooleanPosition); case "CallExpression": @@ -1133,6 +1141,7 @@ module.exports = { LINEBREAK_MATCHER: lineBreakPattern, SHEBANG_MATCHER: shebangPattern, STATEMENT_LIST_PARENTS, + ECMASCRIPT_GLOBALS, /** * Determines whether two adjacent tokens are on the same line. @@ -1231,7 +1240,7 @@ module.exports = { * @private */ isSurroundedBy(val, character) { - return val[0] === character && val[val.length - 1] === character; + return val[0] === character && val.at(-1) === character; }, /** @@ -1909,8 +1918,8 @@ module.exports = { */ getFunctionHeadLoc(node, sourceCode) { const parent = node.parent; - let start = null; - let end = null; + let start; + let end; if (parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") { start = parent.loc.start; @@ -2055,7 +2064,7 @@ module.exports = { case "SequenceExpression": { const exprs = node.expressions; - return exprs.length !== 0 && module.exports.couldBeError(exprs[exprs.length - 1]); + return exprs.length !== 0 && module.exports.couldBeError(exprs.at(-1)); } case "LogicalExpression": @@ -2119,9 +2128,9 @@ module.exports = { const comments = tokens.comments; - leftToken = tokens[tokens.length - 1]; + leftToken = tokens.at(-1); if (comments.length) { - const lastComment = comments[comments.length - 1]; + const lastComment = comments.at(-1); if (!leftToken || lastComment.range[0] > leftToken.range[0]) { leftToken = lastComment; diff --git a/tools/node_modules/eslint/lib/rules/utils/char-source.js b/tools/node_modules/eslint/lib/rules/utils/char-source.js new file mode 100644 index 00000000000000..70738625b94548 --- /dev/null +++ b/tools/node_modules/eslint/lib/rules/utils/char-source.js @@ -0,0 +1,240 @@ +/** + * @fileoverview Utility functions to locate the source text of each code unit in the value of a string literal or template token. + * @author Francesco Trotta + */ + +"use strict"; + +/** + * Represents a code unit produced by the evaluation of a JavaScript common token like a string + * literal or template token. + */ +class CodeUnit { + constructor(start, source) { + this.start = start; + this.source = source; + } + + get end() { + return this.start + this.length; + } + + get length() { + return this.source.length; + } +} + +/** + * An object used to keep track of the position in a source text where the next characters will be read. + */ +class TextReader { + constructor(source) { + this.source = source; + this.pos = 0; + } + + /** + * Advances the reading position of the specified number of characters. + * @param {number} length Number of characters to advance. + * @returns {void} + */ + advance(length) { + this.pos += length; + } + + /** + * Reads characters from the source. + * @param {number} [offset=0] The offset where reading starts, relative to the current position. + * @param {number} [length=1] Number of characters to read. + * @returns {string} A substring of source characters. + */ + read(offset = 0, length = 1) { + const start = offset + this.pos; + + return this.source.slice(start, start + length); + } +} + +const SIMPLE_ESCAPE_SEQUENCES = +{ __proto__: null, b: "\b", f: "\f", n: "\n", r: "\r", t: "\t", v: "\v" }; + +/** + * Reads a hex escape sequence. + * @param {TextReader} reader The reader should be positioned on the first hexadecimal digit. + * @param {number} length The number of hexadecimal digits. + * @returns {string} A code unit. + */ +function readHexSequence(reader, length) { + const str = reader.read(0, length); + const charCode = parseInt(str, 16); + + reader.advance(length); + return String.fromCharCode(charCode); +} + +/** + * Reads a Unicode escape sequence. + * @param {TextReader} reader The reader should be positioned after the "u". + * @returns {string} A code unit. + */ +function readUnicodeSequence(reader) { + const regExp = /\{(?[\dA-Fa-f]+)\}/uy; + + regExp.lastIndex = reader.pos; + const match = regExp.exec(reader.source); + + if (match) { + const codePoint = parseInt(match.groups.hexDigits, 16); + + reader.pos = regExp.lastIndex; + return String.fromCodePoint(codePoint); + } + return readHexSequence(reader, 4); +} + +/** + * Reads an octal escape sequence. + * @param {TextReader} reader The reader should be positioned after the first octal digit. + * @param {number} maxLength The maximum number of octal digits. + * @returns {string} A code unit. + */ +function readOctalSequence(reader, maxLength) { + const [octalStr] = reader.read(-1, maxLength).match(/^[0-7]+/u); + + reader.advance(octalStr.length - 1); + const octal = parseInt(octalStr, 8); + + return String.fromCharCode(octal); +} + +/** + * Reads an escape sequence or line continuation. + * @param {TextReader} reader The reader should be positioned on the backslash. + * @returns {string} A string of zero, one or two code units. + */ +function readEscapeSequenceOrLineContinuation(reader) { + const char = reader.read(1); + + reader.advance(2); + const unitChar = SIMPLE_ESCAPE_SEQUENCES[char]; + + if (unitChar) { + return unitChar; + } + switch (char) { + case "x": + return readHexSequence(reader, 2); + case "u": + return readUnicodeSequence(reader); + case "\r": + if (reader.read() === "\n") { + reader.advance(1); + } + + // fallthrough + case "\n": + case "\u2028": + case "\u2029": + return ""; + case "0": + case "1": + case "2": + case "3": + return readOctalSequence(reader, 3); + case "4": + case "5": + case "6": + case "7": + return readOctalSequence(reader, 2); + default: + return char; + } +} + +/** + * Reads an escape sequence or line continuation and generates the respective `CodeUnit` elements. + * @param {TextReader} reader The reader should be positioned on the backslash. + * @returns {Generator} Zero, one or two `CodeUnit` elements. + */ +function *mapEscapeSequenceOrLineContinuation(reader) { + const start = reader.pos; + const str = readEscapeSequenceOrLineContinuation(reader); + const end = reader.pos; + const source = reader.source.slice(start, end); + + switch (str.length) { + case 0: + break; + case 1: + yield new CodeUnit(start, source); + break; + default: + yield new CodeUnit(start, source); + yield new CodeUnit(start, source); + break; + } +} + +/** + * Parses a string literal. + * @param {string} source The string literal to parse, including the delimiting quotes. + * @returns {CodeUnit[]} A list of code units produced by the string literal. + */ +function parseStringLiteral(source) { + const reader = new TextReader(source); + const quote = reader.read(); + + reader.advance(1); + const codeUnits = []; + + for (;;) { + const char = reader.read(); + + if (char === quote) { + break; + } + if (char === "\\") { + codeUnits.push(...mapEscapeSequenceOrLineContinuation(reader)); + } else { + codeUnits.push(new CodeUnit(reader.pos, char)); + reader.advance(1); + } + } + return codeUnits; +} + +/** + * Parses a template token. + * @param {string} source The template token to parse, including the delimiting sequences `` ` ``, `${` and `}`. + * @returns {CodeUnit[]} A list of code units produced by the template token. + */ +function parseTemplateToken(source) { + const reader = new TextReader(source); + + reader.advance(1); + const codeUnits = []; + + for (;;) { + const char = reader.read(); + + if (char === "`" || char === "$" && reader.read(1) === "{") { + break; + } + if (char === "\\") { + codeUnits.push(...mapEscapeSequenceOrLineContinuation(reader)); + } else { + let unitSource; + + if (char === "\r" && reader.read(1) === "\n") { + unitSource = "\r\n"; + } else { + unitSource = char; + } + codeUnits.push(new CodeUnit(reader.pos, unitSource)); + reader.advance(unitSource.length); + } + } + return codeUnits; +} + +module.exports = { parseStringLiteral, parseTemplateToken }; diff --git a/tools/node_modules/eslint/lib/rules/utils/lazy-loading-rule-map.js b/tools/node_modules/eslint/lib/rules/utils/lazy-loading-rule-map.js index 7f116a2684fef7..3fa5d83b0e8309 100644 --- a/tools/node_modules/eslint/lib/rules/utils/lazy-loading-rule-map.js +++ b/tools/node_modules/eslint/lib/rules/utils/lazy-loading-rule-map.js @@ -6,7 +6,7 @@ const debug = require("debug")("eslint:rules"); -/** @typedef {import("./types").Rule} Rule */ +/** @typedef {import("../../shared/types").Rule} Rule */ /** * The `Map` object that loads each rule when it's accessed. diff --git a/tools/node_modules/eslint/lib/rules/utils/unicode/index.js b/tools/node_modules/eslint/lib/rules/utils/unicode/index.js index 02eea502dfe669..d35d812e1e97f0 100644 --- a/tools/node_modules/eslint/lib/rules/utils/unicode/index.js +++ b/tools/node_modules/eslint/lib/rules/utils/unicode/index.js @@ -3,9 +3,14 @@ */ "use strict"; +const isCombiningCharacter = require("./is-combining-character"); +const isEmojiModifier = require("./is-emoji-modifier"); +const isRegionalIndicatorSymbol = require("./is-regional-indicator-symbol"); +const isSurrogatePair = require("./is-surrogate-pair"); + module.exports = { - isCombiningCharacter: require("./is-combining-character"), - isEmojiModifier: require("./is-emoji-modifier"), - isRegionalIndicatorSymbol: require("./is-regional-indicator-symbol"), - isSurrogatePair: require("./is-surrogate-pair") + isCombiningCharacter, + isEmojiModifier, + isRegionalIndicatorSymbol, + isSurrogatePair }; diff --git a/tools/node_modules/eslint/lib/rules/valid-jsdoc.js b/tools/node_modules/eslint/lib/rules/valid-jsdoc.js deleted file mode 100644 index 919975fe101f73..00000000000000 --- a/tools/node_modules/eslint/lib/rules/valid-jsdoc.js +++ /dev/null @@ -1,516 +0,0 @@ -/** - * @fileoverview Validates JSDoc comments are syntactically correct - * @author Nicholas C. Zakas - * @deprecated in ESLint v5.10.0 - */ -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const doctrine = require("doctrine"); - -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ - -/** @type {import('../shared/types').Rule} */ -module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Enforce valid JSDoc comments", - recommended: false, - url: "https://eslint.org/docs/latest/rules/valid-jsdoc" - }, - - schema: [ - { - type: "object", - properties: { - prefer: { - type: "object", - additionalProperties: { - type: "string" - } - }, - preferType: { - type: "object", - additionalProperties: { - type: "string" - } - }, - requireReturn: { - type: "boolean", - default: true - }, - requireParamDescription: { - type: "boolean", - default: true - }, - requireReturnDescription: { - type: "boolean", - default: true - }, - matchDescription: { - type: "string" - }, - requireReturnType: { - type: "boolean", - default: true - }, - requireParamType: { - type: "boolean", - default: true - } - }, - additionalProperties: false - } - ], - - fixable: "code", - messages: { - unexpectedTag: "Unexpected @{{title}} tag; function has no return statement.", - expected: "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.", - use: "Use @{{name}} instead.", - useType: "Use '{{expectedTypeName}}' instead of '{{currentTypeName}}'.", - syntaxError: "JSDoc syntax error.", - missingBrace: "JSDoc type missing brace.", - missingParamDesc: "Missing JSDoc parameter description for '{{name}}'.", - missingParamType: "Missing JSDoc parameter type for '{{name}}'.", - missingReturnType: "Missing JSDoc return type.", - missingReturnDesc: "Missing JSDoc return description.", - missingReturn: "Missing JSDoc @{{returns}} for function.", - missingParam: "Missing JSDoc for parameter '{{name}}'.", - duplicateParam: "Duplicate JSDoc parameter '{{name}}'.", - unsatisfiedDesc: "JSDoc description does not satisfy the regex pattern." - }, - - deprecated: true, - replacedBy: [] - }, - - create(context) { - - const options = context.options[0] || {}, - prefer = options.prefer || {}, - sourceCode = context.sourceCode, - - // these both default to true, so you have to explicitly make them false - requireReturn = options.requireReturn !== false, - requireParamDescription = options.requireParamDescription !== false, - requireReturnDescription = options.requireReturnDescription !== false, - requireReturnType = options.requireReturnType !== false, - requireParamType = options.requireParamType !== false, - preferType = options.preferType || {}, - checkPreferType = Object.keys(preferType).length !== 0; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - // Using a stack to store if a function returns or not (handling nested functions) - const fns = []; - - /** - * Check if node type is a Class - * @param {ASTNode} node node to check. - * @returns {boolean} True is its a class - * @private - */ - function isTypeClass(node) { - return node.type === "ClassExpression" || node.type === "ClassDeclaration"; - } - - /** - * When parsing a new function, store it in our function stack. - * @param {ASTNode} node A function node to check. - * @returns {void} - * @private - */ - function startFunction(node) { - fns.push({ - returnPresent: (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") || - isTypeClass(node) || node.async - }); - } - - /** - * Indicate that return has been found in the current function. - * @param {ASTNode} node The return node. - * @returns {void} - * @private - */ - function addReturn(node) { - const functionState = fns[fns.length - 1]; - - if (functionState && node.argument !== null) { - functionState.returnPresent = true; - } - } - - /** - * Check if return tag type is void or undefined - * @param {Object} tag JSDoc tag - * @returns {boolean} True if its of type void or undefined - * @private - */ - function isValidReturnType(tag) { - return tag.type === null || tag.type.name === "void" || tag.type.type === "UndefinedLiteral"; - } - - /** - * Check if type should be validated based on some exceptions - * @param {Object} type JSDoc tag - * @returns {boolean} True if it can be validated - * @private - */ - function canTypeBeValidated(type) { - return type !== "UndefinedLiteral" && // {undefined} as there is no name property available. - type !== "NullLiteral" && // {null} - type !== "NullableLiteral" && // {?} - type !== "FunctionType" && // {function(a)} - type !== "AllLiteral"; // {*} - } - - /** - * Extract the current and expected type based on the input type object - * @param {Object} type JSDoc tag - * @returns {{currentType: Doctrine.Type, expectedTypeName: string}} The current type annotation and - * the expected name of the annotation - * @private - */ - function getCurrentExpectedTypes(type) { - let currentType; - - if (type.name) { - currentType = type; - } else if (type.expression) { - currentType = type.expression; - } - - return { - currentType, - expectedTypeName: currentType && preferType[currentType.name] - }; - } - - /** - * Gets the location of a JSDoc node in a file - * @param {Token} jsdocComment The comment that this node is parsed from - * @param {{range: number[]}} parsedJsdocNode A tag or other node which was parsed from this comment - * @returns {{start: SourceLocation, end: SourceLocation}} The 0-based source location for the tag - */ - function getAbsoluteRange(jsdocComment, parsedJsdocNode) { - return { - start: sourceCode.getLocFromIndex(jsdocComment.range[0] + 2 + parsedJsdocNode.range[0]), - end: sourceCode.getLocFromIndex(jsdocComment.range[0] + 2 + parsedJsdocNode.range[1]) - }; - } - - /** - * Validate type for a given JSDoc node - * @param {Object} jsdocNode JSDoc node - * @param {Object} type JSDoc tag - * @returns {void} - * @private - */ - function validateType(jsdocNode, type) { - if (!type || !canTypeBeValidated(type.type)) { - return; - } - - const typesToCheck = []; - let elements = []; - - switch (type.type) { - case "TypeApplication": // {Array.} - elements = type.applications[0].type === "UnionType" ? type.applications[0].elements : type.applications; - typesToCheck.push(getCurrentExpectedTypes(type)); - break; - case "RecordType": // {{20:String}} - elements = type.fields; - break; - case "UnionType": // {String|number|Test} - case "ArrayType": // {[String, number, Test]} - elements = type.elements; - break; - case "FieldType": // Array.<{count: number, votes: number}> - if (type.value) { - typesToCheck.push(getCurrentExpectedTypes(type.value)); - } - break; - default: - typesToCheck.push(getCurrentExpectedTypes(type)); - } - - elements.forEach(validateType.bind(null, jsdocNode)); - - typesToCheck.forEach(typeToCheck => { - if (typeToCheck.expectedTypeName && - typeToCheck.expectedTypeName !== typeToCheck.currentType.name) { - context.report({ - node: jsdocNode, - messageId: "useType", - loc: getAbsoluteRange(jsdocNode, typeToCheck.currentType), - data: { - currentTypeName: typeToCheck.currentType.name, - expectedTypeName: typeToCheck.expectedTypeName - }, - fix(fixer) { - return fixer.replaceTextRange( - typeToCheck.currentType.range.map(indexInComment => jsdocNode.range[0] + 2 + indexInComment), - typeToCheck.expectedTypeName - ); - } - }); - } - }); - } - - /** - * Validate the JSDoc node and output warnings if anything is wrong. - * @param {ASTNode} node The AST node to check. - * @returns {void} - * @private - */ - function checkJSDoc(node) { - const jsdocNode = sourceCode.getJSDocComment(node), - functionData = fns.pop(), - paramTagsByName = Object.create(null), - paramTags = []; - let hasReturns = false, - returnsTag, - hasConstructor = false, - isInterface = false, - isOverride = false, - isAbstract = false; - - // make sure only to validate JSDoc comments - if (jsdocNode) { - let jsdoc; - - try { - jsdoc = doctrine.parse(jsdocNode.value, { - strict: true, - unwrap: true, - sloppy: true, - range: true - }); - } catch (ex) { - - if (/braces/iu.test(ex.message)) { - context.report({ node: jsdocNode, messageId: "missingBrace" }); - } else { - context.report({ node: jsdocNode, messageId: "syntaxError" }); - } - - return; - } - - jsdoc.tags.forEach(tag => { - - switch (tag.title.toLowerCase()) { - - case "param": - case "arg": - case "argument": - paramTags.push(tag); - break; - - case "return": - case "returns": - hasReturns = true; - returnsTag = tag; - break; - - case "constructor": - case "class": - hasConstructor = true; - break; - - case "override": - case "inheritdoc": - isOverride = true; - break; - - case "abstract": - case "virtual": - isAbstract = true; - break; - - case "interface": - isInterface = true; - break; - - // no default - } - - // check tag preferences - if (Object.prototype.hasOwnProperty.call(prefer, tag.title) && tag.title !== prefer[tag.title]) { - const entireTagRange = getAbsoluteRange(jsdocNode, tag); - - context.report({ - node: jsdocNode, - messageId: "use", - loc: { - start: entireTagRange.start, - end: { - line: entireTagRange.start.line, - column: entireTagRange.start.column + `@${tag.title}`.length - } - }, - data: { name: prefer[tag.title] }, - fix(fixer) { - return fixer.replaceTextRange( - [ - jsdocNode.range[0] + tag.range[0] + 3, - jsdocNode.range[0] + tag.range[0] + tag.title.length + 3 - ], - prefer[tag.title] - ); - } - }); - } - - // validate the types - if (checkPreferType && tag.type) { - validateType(jsdocNode, tag.type); - } - }); - - paramTags.forEach(param => { - if (requireParamType && !param.type) { - context.report({ - node: jsdocNode, - messageId: "missingParamType", - loc: getAbsoluteRange(jsdocNode, param), - data: { name: param.name } - }); - } - if (!param.description && requireParamDescription) { - context.report({ - node: jsdocNode, - messageId: "missingParamDesc", - loc: getAbsoluteRange(jsdocNode, param), - data: { name: param.name } - }); - } - if (paramTagsByName[param.name]) { - context.report({ - node: jsdocNode, - messageId: "duplicateParam", - loc: getAbsoluteRange(jsdocNode, param), - data: { name: param.name } - }); - } else if (!param.name.includes(".")) { - paramTagsByName[param.name] = param; - } - }); - - if (hasReturns) { - if (!requireReturn && !functionData.returnPresent && (returnsTag.type === null || !isValidReturnType(returnsTag)) && !isAbstract) { - context.report({ - node: jsdocNode, - messageId: "unexpectedTag", - loc: getAbsoluteRange(jsdocNode, returnsTag), - data: { - title: returnsTag.title - } - }); - } else { - if (requireReturnType && !returnsTag.type) { - context.report({ node: jsdocNode, messageId: "missingReturnType" }); - } - - if (!isValidReturnType(returnsTag) && !returnsTag.description && requireReturnDescription) { - context.report({ node: jsdocNode, messageId: "missingReturnDesc" }); - } - } - } - - // check for functions missing @returns - if (!isOverride && !hasReturns && !hasConstructor && !isInterface && - node.parent.kind !== "get" && node.parent.kind !== "constructor" && - node.parent.kind !== "set" && !isTypeClass(node)) { - if (requireReturn || (functionData.returnPresent && !node.async)) { - context.report({ - node: jsdocNode, - messageId: "missingReturn", - data: { - returns: prefer.returns || "returns" - } - }); - } - } - - // check the parameters - const jsdocParamNames = Object.keys(paramTagsByName); - - if (node.params) { - node.params.forEach((param, paramsIndex) => { - const bindingParam = param.type === "AssignmentPattern" - ? param.left - : param; - - // TODO(nzakas): Figure out logical things to do with destructured, default, rest params - if (bindingParam.type === "Identifier") { - const name = bindingParam.name; - - if (jsdocParamNames[paramsIndex] && (name !== jsdocParamNames[paramsIndex])) { - context.report({ - node: jsdocNode, - messageId: "expected", - loc: getAbsoluteRange(jsdocNode, paramTagsByName[jsdocParamNames[paramsIndex]]), - data: { - name, - jsdocName: jsdocParamNames[paramsIndex] - } - }); - } else if (!paramTagsByName[name] && !isOverride) { - context.report({ - node: jsdocNode, - messageId: "missingParam", - data: { - name - } - }); - } - } - }); - } - - if (options.matchDescription) { - const regex = new RegExp(options.matchDescription, "u"); - - if (!regex.test(jsdoc.description)) { - context.report({ node: jsdocNode, messageId: "unsatisfiedDesc" }); - } - } - - } - - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - ArrowFunctionExpression: startFunction, - FunctionExpression: startFunction, - FunctionDeclaration: startFunction, - ClassExpression: startFunction, - ClassDeclaration: startFunction, - "ArrowFunctionExpression:exit": checkJSDoc, - "FunctionExpression:exit": checkJSDoc, - "FunctionDeclaration:exit": checkJSDoc, - "ClassExpression:exit": checkJSDoc, - "ClassDeclaration:exit": checkJSDoc, - ReturnStatement: addReturn - }; - - } -}; diff --git a/tools/node_modules/eslint/lib/rules/yield-star-spacing.js b/tools/node_modules/eslint/lib/rules/yield-star-spacing.js index 9a67b78d25f034..5cf3804abb835e 100644 --- a/tools/node_modules/eslint/lib/rules/yield-star-spacing.js +++ b/tools/node_modules/eslint/lib/rules/yield-star-spacing.js @@ -79,7 +79,7 @@ module.exports = { const after = leftToken.value === "*"; const spaceRequired = mode[side]; const node = after ? leftToken : rightToken; - let messageId = ""; + let messageId; if (spaceRequired) { messageId = side === "before" ? "missingBefore" : "missingAfter"; diff --git a/tools/node_modules/eslint/lib/shared/config-validator.js b/tools/node_modules/eslint/lib/shared/config-validator.js deleted file mode 100644 index 47353ac4814b72..00000000000000 --- a/tools/node_modules/eslint/lib/shared/config-validator.js +++ /dev/null @@ -1,347 +0,0 @@ -/* - * STOP!!! DO NOT MODIFY. - * - * This file is part of the ongoing work to move the eslintrc-style config - * system into the @eslint/eslintrc package. This file needs to remain - * unchanged in order for this work to proceed. - * - * If you think you need to change this file, please contact @nzakas first. - * - * Thanks in advance for your cooperation. - */ - -/** - * @fileoverview Validates configs. - * @author Brandon Mills - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const - util = require("util"), - configSchema = require("../../conf/config-schema"), - BuiltInRules = require("../rules"), - { - Legacy: { - ConfigOps, - environments: BuiltInEnvironments - } - } = require("@eslint/eslintrc"), - { emitDeprecationWarning } = require("./deprecation-warnings"); - -const ajv = require("./ajv")(); -const ruleValidators = new WeakMap(); -const noop = Function.prototype; - -//------------------------------------------------------------------------------ -// Private -//------------------------------------------------------------------------------ -let validateSchema; -const severityMap = { - error: 2, - warn: 1, - off: 0 -}; - -/** - * Gets a complete options schema for a rule. - * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object - * @returns {Object} JSON Schema for the rule's options. - */ -function getRuleOptionsSchema(rule) { - if (!rule) { - return null; - } - - const schema = rule.schema || rule.meta && rule.meta.schema; - - // Given a tuple of schemas, insert warning level at the beginning - if (Array.isArray(schema)) { - if (schema.length) { - return { - type: "array", - items: schema, - minItems: 0, - maxItems: schema.length - }; - } - return { - type: "array", - minItems: 0, - maxItems: 0 - }; - - } - - // Given a full schema, leave it alone - return schema || null; -} - -/** - * Validates a rule's severity and returns the severity value. Throws an error if the severity is invalid. - * @param {options} options The given options for the rule. - * @throws {Error} Wrong severity value. - * @returns {number|string} The rule's severity value - */ -function validateRuleSeverity(options) { - const severity = Array.isArray(options) ? options[0] : options; - const normSeverity = typeof severity === "string" ? severityMap[severity.toLowerCase()] : severity; - - if (normSeverity === 0 || normSeverity === 1 || normSeverity === 2) { - return normSeverity; - } - - throw new Error(`\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '${util.inspect(severity).replace(/'/gu, "\"").replace(/\n/gu, "")}').\n`); - -} - -/** - * Validates the non-severity options passed to a rule, based on its schema. - * @param {{create: Function}} rule The rule to validate - * @param {Array} localOptions The options for the rule, excluding severity - * @throws {Error} Any rule validation errors. - * @returns {void} - */ -function validateRuleSchema(rule, localOptions) { - if (!ruleValidators.has(rule)) { - const schema = getRuleOptionsSchema(rule); - - if (schema) { - ruleValidators.set(rule, ajv.compile(schema)); - } - } - - const validateRule = ruleValidators.get(rule); - - if (validateRule) { - validateRule(localOptions); - if (validateRule.errors) { - throw new Error(validateRule.errors.map( - error => `\tValue ${JSON.stringify(error.data)} ${error.message}.\n` - ).join("")); - } - } -} - -/** - * Validates a rule's options against its schema. - * @param {{create: Function}|null} rule The rule that the config is being validated for - * @param {string} ruleId The rule's unique name. - * @param {Array|number} options The given options for the rule. - * @param {string|null} source The name of the configuration source to report in any errors. If null or undefined, - * no source is prepended to the message. - * @throws {Error} Upon any bad rule configuration. - * @returns {void} - */ -function validateRuleOptions(rule, ruleId, options, source = null) { - try { - const severity = validateRuleSeverity(options); - - if (severity !== 0) { - validateRuleSchema(rule, Array.isArray(options) ? options.slice(1) : []); - } - } catch (err) { - const enhancedMessage = `Configuration for rule "${ruleId}" is invalid:\n${err.message}`; - - if (typeof source === "string") { - throw new Error(`${source}:\n\t${enhancedMessage}`); - } else { - throw new Error(enhancedMessage); - } - } -} - -/** - * Validates an environment object - * @param {Object} environment The environment config object to validate. - * @param {string} source The name of the configuration source to report in any errors. - * @param {(envId:string) => Object} [getAdditionalEnv] A map from strings to loaded environments. - * @returns {void} - */ -function validateEnvironment( - environment, - source, - getAdditionalEnv = noop -) { - - // not having an environment is ok - if (!environment) { - return; - } - - Object.keys(environment).forEach(id => { - const env = getAdditionalEnv(id) || BuiltInEnvironments.get(id) || null; - - if (!env) { - const message = `${source}:\n\tEnvironment key "${id}" is unknown\n`; - - throw new Error(message); - } - }); -} - -/** - * Validates a rules config object - * @param {Object} rulesConfig The rules config object to validate. - * @param {string} source The name of the configuration source to report in any errors. - * @param {(ruleId:string) => Object} getAdditionalRule A map from strings to loaded rules - * @returns {void} - */ -function validateRules( - rulesConfig, - source, - getAdditionalRule = noop -) { - if (!rulesConfig) { - return; - } - - Object.keys(rulesConfig).forEach(id => { - const rule = getAdditionalRule(id) || BuiltInRules.get(id) || null; - - validateRuleOptions(rule, id, rulesConfig[id], source); - }); -} - -/** - * Validates a `globals` section of a config file - * @param {Object} globalsConfig The `globals` section - * @param {string|null} source The name of the configuration source to report in the event of an error. - * @returns {void} - */ -function validateGlobals(globalsConfig, source = null) { - if (!globalsConfig) { - return; - } - - Object.entries(globalsConfig) - .forEach(([configuredGlobal, configuredValue]) => { - try { - ConfigOps.normalizeConfigGlobal(configuredValue); - } catch (err) { - throw new Error(`ESLint configuration of global '${configuredGlobal}' in ${source} is invalid:\n${err.message}`); - } - }); -} - -/** - * Validate `processor` configuration. - * @param {string|undefined} processorName The processor name. - * @param {string} source The name of config file. - * @param {(id:string) => Processor} getProcessor The getter of defined processors. - * @throws {Error} For invalid processor configuration. - * @returns {void} - */ -function validateProcessor(processorName, source, getProcessor) { - if (processorName && !getProcessor(processorName)) { - throw new Error(`ESLint configuration of processor in '${source}' is invalid: '${processorName}' was not found.`); - } -} - -/** - * Formats an array of schema validation errors. - * @param {Array} errors An array of error messages to format. - * @returns {string} Formatted error message - */ -function formatErrors(errors) { - return errors.map(error => { - if (error.keyword === "additionalProperties") { - const formattedPropertyPath = error.dataPath.length ? `${error.dataPath.slice(1)}.${error.params.additionalProperty}` : error.params.additionalProperty; - - return `Unexpected top-level property "${formattedPropertyPath}"`; - } - if (error.keyword === "type") { - const formattedField = error.dataPath.slice(1); - const formattedExpectedType = Array.isArray(error.schema) ? error.schema.join("/") : error.schema; - const formattedValue = JSON.stringify(error.data); - - return `Property "${formattedField}" is the wrong type (expected ${formattedExpectedType} but got \`${formattedValue}\`)`; - } - - const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath; - - return `"${field}" ${error.message}. Value: ${JSON.stringify(error.data)}`; - }).map(message => `\t- ${message}.\n`).join(""); -} - -/** - * Validates the top level properties of the config object. - * @param {Object} config The config object to validate. - * @param {string} source The name of the configuration source to report in any errors. - * @throws {Error} For any config invalid per the schema. - * @returns {void} - */ -function validateConfigSchema(config, source = null) { - validateSchema = validateSchema || ajv.compile(configSchema); - - if (!validateSchema(config)) { - throw new Error(`ESLint configuration in ${source} is invalid:\n${formatErrors(validateSchema.errors)}`); - } - - if (Object.hasOwnProperty.call(config, "ecmaFeatures")) { - emitDeprecationWarning(source, "ESLINT_LEGACY_ECMAFEATURES"); - } -} - -/** - * Validates an entire config object. - * @param {Object} config The config object to validate. - * @param {string} source The name of the configuration source to report in any errors. - * @param {(ruleId:string) => Object} [getAdditionalRule] A map from strings to loaded rules. - * @param {(envId:string) => Object} [getAdditionalEnv] A map from strings to loaded envs. - * @returns {void} - */ -function validate(config, source, getAdditionalRule, getAdditionalEnv) { - validateConfigSchema(config, source); - validateRules(config.rules, source, getAdditionalRule); - validateEnvironment(config.env, source, getAdditionalEnv); - validateGlobals(config.globals, source); - - for (const override of config.overrides || []) { - validateRules(override.rules, source, getAdditionalRule); - validateEnvironment(override.env, source, getAdditionalEnv); - validateGlobals(config.globals, source); - } -} - -const validated = new WeakSet(); - -/** - * Validate config array object. - * @param {ConfigArray} configArray The config array to validate. - * @returns {void} - */ -function validateConfigArray(configArray) { - const getPluginEnv = Map.prototype.get.bind(configArray.pluginEnvironments); - const getPluginProcessor = Map.prototype.get.bind(configArray.pluginProcessors); - const getPluginRule = Map.prototype.get.bind(configArray.pluginRules); - - // Validate. - for (const element of configArray) { - if (validated.has(element)) { - continue; - } - validated.add(element); - - validateEnvironment(element.env, element.name, getPluginEnv); - validateGlobals(element.globals, element.name); - validateProcessor(element.processor, element.name, getPluginProcessor); - validateRules(element.rules, element.name, getPluginRule); - } -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = { - getRuleOptionsSchema, - validate, - validateConfigArray, - validateConfigSchema, - validateRuleOptions -}; diff --git a/tools/node_modules/eslint/lib/shared/deprecation-warnings.js b/tools/node_modules/eslint/lib/shared/deprecation-warnings.js deleted file mode 100644 index d09cafb07fe13f..00000000000000 --- a/tools/node_modules/eslint/lib/shared/deprecation-warnings.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @fileoverview Provide the function that emits deprecation warnings. - * @author Toru Nagashima - */ -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const path = require("path"); - -//------------------------------------------------------------------------------ -// Private -//------------------------------------------------------------------------------ - -// Definitions for deprecation warnings. -const deprecationWarningMessages = { - ESLINT_LEGACY_ECMAFEATURES: - "The 'ecmaFeatures' config file property is deprecated and has no effect." -}; - -const sourceFileErrorCache = new Set(); - -/** - * Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted - * for each unique file path, but repeated invocations with the same file path have no effect. - * No warnings are emitted if the `--no-deprecation` or `--no-warnings` Node runtime flags are active. - * @param {string} source The name of the configuration source to report the warning for. - * @param {string} errorCode The warning message to show. - * @returns {void} - */ -function emitDeprecationWarning(source, errorCode) { - const cacheKey = JSON.stringify({ source, errorCode }); - - if (sourceFileErrorCache.has(cacheKey)) { - return; - } - - sourceFileErrorCache.add(cacheKey); - - const rel = path.relative(process.cwd(), source); - const message = deprecationWarningMessages[errorCode]; - - process.emitWarning( - `${message} (found in "${rel}")`, - "DeprecationWarning", - errorCode - ); -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = { - emitDeprecationWarning -}; diff --git a/tools/node_modules/eslint/lib/shared/relative-module-resolver.js b/tools/node_modules/eslint/lib/shared/relative-module-resolver.js deleted file mode 100644 index 18a694983c19f5..00000000000000 --- a/tools/node_modules/eslint/lib/shared/relative-module-resolver.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * STOP!!! DO NOT MODIFY. - * - * This file is part of the ongoing work to move the eslintrc-style config - * system into the @eslint/eslintrc package. This file needs to remain - * unchanged in order for this work to proceed. - * - * If you think you need to change this file, please contact @nzakas first. - * - * Thanks in advance for your cooperation. - */ - -/** - * Utility for resolving a module relative to another module - * @author Teddy Katz - */ - -"use strict"; - -const { createRequire } = require("module"); - -module.exports = { - - /** - * Resolves a Node module relative to another module - * @param {string} moduleName The name of a Node module, or a path to a Node module. - * @param {string} relativeToPath An absolute path indicating the module that `moduleName` should be resolved relative to. This must be - * a file rather than a directory, but the file need not actually exist. - * @throws {Error} Any error from `module.createRequire` or its `resolve`. - * @returns {string} The absolute path that would result from calling `require.resolve(moduleName)` in a file located at `relativeToPath` - */ - resolve(moduleName, relativeToPath) { - try { - return createRequire(relativeToPath).resolve(moduleName); - } catch (error) { - - // This `if` block is for older Node.js than 12.0.0. We can remove this block in the future. - if ( - typeof error === "object" && - error !== null && - error.code === "MODULE_NOT_FOUND" && - !error.requireStack && - error.message.includes(moduleName) - ) { - error.message += `\nRequire stack:\n- ${relativeToPath}`; - } - throw error; - } - } -}; diff --git a/tools/node_modules/eslint/lib/shared/runtime-info.js b/tools/node_modules/eslint/lib/shared/runtime-info.js index b99ad1038f3931..e172e5b3572f2b 100644 --- a/tools/node_modules/eslint/lib/shared/runtime-info.js +++ b/tools/node_modules/eslint/lib/shared/runtime-info.js @@ -162,6 +162,7 @@ function version() { //------------------------------------------------------------------------------ module.exports = { + __esModule: true, // Indicate intent for imports, remove ambiguity for Knip (see: https://github.com/eslint/eslint/pull/18005#discussion_r1484422616) environment, version }; diff --git a/tools/node_modules/eslint/lib/shared/serialization.js b/tools/node_modules/eslint/lib/shared/serialization.js new file mode 100644 index 00000000000000..69f5710f2a306e --- /dev/null +++ b/tools/node_modules/eslint/lib/shared/serialization.js @@ -0,0 +1,55 @@ +/** + * @fileoverview Serialization utils. + * @author Bryan Mishkin + */ + +"use strict"; + +/** + * Check if a value is a primitive or plain object created by the Object constructor. + * @param {any} val the value to check + * @returns {boolean} true if so + * @private + */ +function isSerializablePrimitiveOrPlainObject(val) { + return ( + val === null || + typeof val === "string" || + typeof val === "boolean" || + typeof val === "number" || + (typeof val === "object" && val.constructor === Object) || + Array.isArray(val) + ); +} + +/** + * Check if a value is serializable. + * Functions or objects like RegExp cannot be serialized by JSON.stringify(). + * Inspired by: https://stackoverflow.com/questions/30579940/reliable-way-to-check-if-objects-is-serializable-in-javascript + * @param {any} val the value + * @returns {boolean} true if the value is serializable + */ +function isSerializable(val) { + if (!isSerializablePrimitiveOrPlainObject(val)) { + return false; + } + if (typeof val === "object") { + for (const property in val) { + if (Object.hasOwn(val, property)) { + if (!isSerializablePrimitiveOrPlainObject(val[property])) { + return false; + } + if (typeof val[property] === "object") { + if (!isSerializable(val[property])) { + return false; + } + } + } + } + } + return true; +} + +module.exports = { + isSerializable +}; diff --git a/tools/node_modules/eslint/lib/shared/stats.js b/tools/node_modules/eslint/lib/shared/stats.js new file mode 100644 index 00000000000000..c5d4d1885d415b --- /dev/null +++ b/tools/node_modules/eslint/lib/shared/stats.js @@ -0,0 +1,30 @@ +/** + * @fileoverview Provides helper functions to start/stop the time measurements + * that are provided by the ESLint 'stats' option. + * @author Mara Kiefer + */ +"use strict"; + +/** + * Start time measurement + * @returns {[number, number]} t variable for tracking time + */ +function startTime() { + return process.hrtime(); +} + +/** + * End time measurement + * @param {[number, number]} t Variable for tracking time + * @returns {number} The measured time in milliseconds + */ +function endTime(t) { + const time = process.hrtime(t); + + return time[0] * 1e3 + time[1] / 1e6; +} + +module.exports = { + startTime, + endTime +}; diff --git a/tools/node_modules/eslint/lib/shared/types.js b/tools/node_modules/eslint/lib/shared/types.js index e3a40bc986be5e..f4186dd96adab8 100644 --- a/tools/node_modules/eslint/lib/shared/types.js +++ b/tools/node_modules/eslint/lib/shared/types.js @@ -168,7 +168,7 @@ module.exports = {}; * @property {Record} [configs] The definition of plugin configs. * @property {Record} [environments] The definition of plugin environments. * @property {Record} [processors] The definition of plugin processors. - * @property {Record} [rules] The definition of plugin rules. + * @property {Record} [rules] The definition of plugin rules. */ /** @@ -189,11 +189,45 @@ module.exports = {}; * @property {number} warningCount Number of warnings for the result. * @property {number} fixableErrorCount Number of fixable errors for the result. * @property {number} fixableWarningCount Number of fixable warnings for the result. + * @property {Stats} [stats] The performance statistics collected with the `stats` flag. * @property {string} [source] The source code of the file that was linted. * @property {string} [output] The source code of the file that was linted, with as many fixes applied as possible. * @property {DeprecatedRuleInfo[]} usedDeprecatedRules The list of used deprecated rules. */ +/** + * Performance statistics + * @typedef {Object} Stats + * @property {number} fixPasses The number of times ESLint has applied at least one fix after linting. + * @property {Times} times The times spent on (parsing, fixing, linting) a file. + */ + +/** + * Performance Times for each ESLint pass + * @typedef {Object} Times + * @property {TimePass[]} passes Time passes + */ + +/** + * @typedef {Object} TimePass + * @property {ParseTime} parse The parse object containing all parse time information. + * @property {Record} [rules] The rules object containing all lint time information for each rule. + * @property {FixTime} fix The parse object containing all fix time information. + * @property {number} total The total time that is spent on (parsing, fixing, linting) a file. + */ +/** + * @typedef {Object} ParseTime + * @property {number} total The total time that is spent when parsing a file. + */ +/** + * @typedef {Object} RuleTime + * @property {number} total The total time that is spent on a rule. + */ +/** + * @typedef {Object} FixTime + * @property {number} total The total time that is spent on applying fixes to the code. + */ + /** * Information provided when the maximum warning threshold is exceeded. * @typedef {Object} MaxWarningsExceeded diff --git a/tools/node_modules/eslint/lib/source-code/index.js b/tools/node_modules/eslint/lib/source-code/index.js index 76e27869f321ac..1ecfbe470aa7cd 100644 --- a/tools/node_modules/eslint/lib/source-code/index.js +++ b/tools/node_modules/eslint/lib/source-code/index.js @@ -1,5 +1,7 @@ "use strict"; +const SourceCode = require("./source-code"); + module.exports = { - SourceCode: require("./source-code") + SourceCode }; diff --git a/tools/node_modules/eslint/lib/source-code/source-code.js b/tools/node_modules/eslint/lib/source-code/source-code.js index 236f6b5c6cc1d7..f3418e7e5b73e5 100644 --- a/tools/node_modules/eslint/lib/source-code/source-code.js +++ b/tools/node_modules/eslint/lib/source-code/source-code.js @@ -18,8 +18,12 @@ const directivesPattern } = require("../shared/directives"), - /* eslint-disable-next-line n/no-restricted-require -- Too messy to figure out right now. */ + /* eslint-disable n/no-restricted-require -- Should eventually be moved into SourceCode. */ + CodePathAnalyzer = require("../linter/code-path-analysis/code-path-analyzer"), + createEmitter = require("../linter/safe-emitter"), ConfigCommentParser = require("../linter/config-comment-parser"), + /* eslint-enable n/no-restricted-require -- Should eventually be moved into SourceCode. */ + eslintScope = require("eslint-scope"); //------------------------------------------------------------------------------ @@ -34,6 +38,16 @@ const const commentParser = new ConfigCommentParser(); +const CODE_PATH_EVENTS = [ + "onCodePathStart", + "onCodePathEnd", + "onCodePathSegmentStart", + "onCodePathSegmentEnd", + "onCodePathSegmentLoop", + "onUnreachableCodePathSegmentStart", + "onUnreachableCodePathSegmentEnd" +]; + /** * Validates that the given AST has the required information. * @param {ASTNode} ast The Program node of the AST to check. @@ -300,6 +314,65 @@ function markExportedVariables(globalScope, variables) { } +const STEP_KIND = { + visit: 1, + call: 2 +}; + +/** + * A class to represent a step in the traversal process. + */ +class TraversalStep { + + /** + * The type of the step. + * @type {string} + */ + type; + + /** + * The kind of the step. Represents the same data as the `type` property + * but it's a number for performance. + * @type {number} + */ + kind; + + /** + * The target of the step. + * @type {ASTNode|string} + */ + target; + + /** + * The phase of the step. + * @type {number|undefined} + */ + phase; + + /** + * The arguments of the step. + * @type {Array} + */ + args; + + /** + * Creates a new instance. + * @param {Object} options The options for the step. + * @param {string} options.type The type of the step. + * @param {ASTNode|string} options.target The target of the step. + * @param {number|undefined} [options.phase] The phase of the step. + * @param {Array} options.args The arguments of the step. + * @returns {void} + */ + constructor({ type, target, phase, args }) { + this.type = type; + this.kind = STEP_KIND[type]; + this.target = target; + this.phase = phase; + this.args = args; + } +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -311,6 +384,12 @@ const caches = Symbol("caches"); */ class SourceCode extends TokenStore { + /** + * The cache of steps that were taken while traversing the source code. + * @type {Array} + */ + #steps; + /** * @param {string|Object} textOrConfig The source code text or config object. * @param {string} textOrConfig.text The source code text. @@ -415,13 +494,10 @@ class SourceCode extends TokenStore { * and uses match.index to get the correct line start indices. */ while ((match = lineEndingPattern.exec(this.text))) { - this.lines.push(this.text.slice(this.lineStartIndices[this.lineStartIndices.length - 1], match.index)); + this.lines.push(this.text.slice(this.lineStartIndices.at(-1), match.index)); this.lineStartIndices.push(match.index + match[0].length); } - this.lines.push(this.text.slice(this.lineStartIndices[this.lineStartIndices.length - 1])); - - // Cache for comments found using getComments(). - this._commentCache = new WeakMap(); + this.lines.push(this.text.slice(this.lineStartIndices.at(-1))); // don't allow further modification of this object Object.freeze(this); @@ -472,81 +548,6 @@ class SourceCode extends TokenStore { return this.ast.comments; } - /** - * Gets all comments for the given node. - * @param {ASTNode} node The AST node to get the comments for. - * @returns {Object} An object containing a leading and trailing array - * of comments indexed by their position. - * @public - * @deprecated replaced by getCommentsBefore(), getCommentsAfter(), and getCommentsInside(). - */ - getComments(node) { - if (this._commentCache.has(node)) { - return this._commentCache.get(node); - } - - const comments = { - leading: [], - trailing: [] - }; - - /* - * Return all comments as leading comments of the Program node when - * there is no executable code. - */ - if (node.type === "Program") { - if (node.body.length === 0) { - comments.leading = node.comments; - } - } else { - - /* - * Return comments as trailing comments of nodes that only contain - * comments (to mimic the comment attachment behavior present in Espree). - */ - if ((node.type === "BlockStatement" || node.type === "ClassBody") && node.body.length === 0 || - node.type === "ObjectExpression" && node.properties.length === 0 || - node.type === "ArrayExpression" && node.elements.length === 0 || - node.type === "SwitchStatement" && node.cases.length === 0 - ) { - comments.trailing = this.getTokens(node, { - includeComments: true, - filter: isCommentToken - }); - } - - /* - * Iterate over tokens before and after node and collect comment tokens. - * Do not include comments that exist outside of the parent node - * to avoid duplication. - */ - let currentToken = this.getTokenBefore(node, { includeComments: true }); - - while (currentToken && isCommentToken(currentToken)) { - if (node.parent && node.parent.type !== "Program" && (currentToken.start < node.parent.start)) { - break; - } - comments.leading.push(currentToken); - currentToken = this.getTokenBefore(currentToken, { includeComments: true }); - } - - comments.leading.reverse(); - - currentToken = this.getTokenAfter(node, { includeComments: true }); - - while (currentToken && isCommentToken(currentToken)) { - if (node.parent && node.parent.type !== "Program" && (currentToken.end > node.parent.end)) { - break; - } - comments.trailing.push(currentToken); - currentToken = this.getTokenAfter(currentToken, { includeComments: true }); - } - } - - this._commentCache.set(node, comments); - return comments; - } - /** * Retrieves the JSDoc comment for a given node. * @param {ASTNode} node The AST node to get the comment for. @@ -701,14 +702,14 @@ class SourceCode extends TokenStore { * See getIndexFromLoc for the motivation for this special case. */ if (index === this.text.length) { - return { line: this.lines.length, column: this.lines[this.lines.length - 1].length }; + return { line: this.lines.length, column: this.lines.at(-1).length }; } /* * To figure out which line index is on, determine the last place at which index could * be inserted into lineStartIndices to keep the list sorted. */ - const lineNumber = index >= this.lineStartIndices[this.lineStartIndices.length - 1] + const lineNumber = index >= this.lineStartIndices.at(-1) ? this.lineStartIndices.length : this.lineStartIndices.findIndex(el => index < el); @@ -963,7 +964,7 @@ class SourceCode extends TokenStore { switch (directiveText) { case "exported": - Object.assign(exportedVariables, commentParser.parseStringConfig(directiveValue, comment)); + Object.assign(exportedVariables, commentParser.parseListConfig(directiveValue, comment)); break; case "globals": @@ -1050,6 +1051,91 @@ class SourceCode extends TokenStore { } + /** + * Traverse the source code and return the steps that were taken. + * @returns {Array} The steps that were taken while traversing the source code. + */ + traverse() { + + // Because the AST doesn't mutate, we can cache the steps + if (this.#steps) { + return this.#steps; + } + + const steps = this.#steps = []; + + /* + * This logic works for any AST, not just ESTree. Because ESLint has allowed + * custom parsers to return any AST, we need to ensure that the traversal + * logic works for any AST. + */ + const emitter = createEmitter(); + let analyzer = { + enterNode(node) { + steps.push(new TraversalStep({ + type: "visit", + target: node, + phase: 1, + args: [node, node.parent] + })); + }, + leaveNode(node) { + steps.push(new TraversalStep({ + type: "visit", + target: node, + phase: 2, + args: [node, node.parent] + })); + }, + emitter + }; + + /* + * We do code path analysis for ESTree only. Code path analysis is not + * necessary for other ASTs, and it's also not possible to do for other + * ASTs because the necessary information is not available. + * + * Generally speaking, we can tell that the AST is an ESTree if it has a + * Program node at the top level. This is not a perfect heuristic, but it + * is good enough for now. + */ + const isESTree = this.ast.type === "Program"; + + if (isESTree) { + analyzer = new CodePathAnalyzer(analyzer); + + CODE_PATH_EVENTS.forEach(eventName => { + emitter.on(eventName, (...args) => { + steps.push(new TraversalStep({ + type: "call", + target: eventName, + args + })); + }); + }); + } + + /* + * The actual AST traversal is done by the `Traverser` class. This class + * is responsible for walking the AST and calling the appropriate methods + * on the `analyzer` object, which is appropriate for the given AST. + */ + Traverser.traverse(this.ast, { + enter(node, parent) { + + // save the parent node on a property for backwards compatibility + node.parent = parent; + + analyzer.enterNode(node); + }, + leave(node) { + analyzer.leaveNode(node); + }, + visitorKeys: this.visitorKeys + }); + + return steps; + } } module.exports = SourceCode; diff --git a/tools/node_modules/eslint/lib/source-code/token-store/backward-token-cursor.js b/tools/node_modules/eslint/lib/source-code/token-store/backward-token-cursor.js index 454a2449701cf1..d3469c99b1459e 100644 --- a/tools/node_modules/eslint/lib/source-code/token-store/backward-token-cursor.js +++ b/tools/node_modules/eslint/lib/source-code/token-store/backward-token-cursor.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const Cursor = require("./cursor"); -const utils = require("./utils"); +const { getLastIndex, getFirstIndex } = require("./utils"); //------------------------------------------------------------------------------ // Exports @@ -31,8 +31,8 @@ module.exports = class BackwardTokenCursor extends Cursor { constructor(tokens, comments, indexMap, startLoc, endLoc) { super(); this.tokens = tokens; - this.index = utils.getLastIndex(tokens, indexMap, endLoc); - this.indexEnd = utils.getFirstIndex(tokens, indexMap, startLoc); + this.index = getLastIndex(tokens, indexMap, endLoc); + this.indexEnd = getFirstIndex(tokens, indexMap, startLoc); } /** @inheritdoc */ diff --git a/tools/node_modules/eslint/lib/source-code/token-store/cursors.js b/tools/node_modules/eslint/lib/source-code/token-store/cursors.js index 30c72b69b8f962..f2676f13da624c 100644 --- a/tools/node_modules/eslint/lib/source-code/token-store/cursors.js +++ b/tools/node_modules/eslint/lib/source-code/token-store/cursors.js @@ -86,5 +86,7 @@ class CursorFactory { // Exports //------------------------------------------------------------------------------ -exports.forward = new CursorFactory(ForwardTokenCursor, ForwardTokenCommentCursor); -exports.backward = new CursorFactory(BackwardTokenCursor, BackwardTokenCommentCursor); +module.exports = { + forward: new CursorFactory(ForwardTokenCursor, ForwardTokenCommentCursor), + backward: new CursorFactory(BackwardTokenCursor, BackwardTokenCommentCursor) +}; diff --git a/tools/node_modules/eslint/lib/source-code/token-store/forward-token-comment-cursor.js b/tools/node_modules/eslint/lib/source-code/token-store/forward-token-comment-cursor.js index 50c7a394f38476..8aa46c27b7435c 100644 --- a/tools/node_modules/eslint/lib/source-code/token-store/forward-token-comment-cursor.js +++ b/tools/node_modules/eslint/lib/source-code/token-store/forward-token-comment-cursor.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const Cursor = require("./cursor"); -const utils = require("./utils"); +const { getFirstIndex, search } = require("./utils"); //------------------------------------------------------------------------------ // Exports @@ -32,8 +32,8 @@ module.exports = class ForwardTokenCommentCursor extends Cursor { super(); this.tokens = tokens; this.comments = comments; - this.tokenIndex = utils.getFirstIndex(tokens, indexMap, startLoc); - this.commentIndex = utils.search(comments, startLoc); + this.tokenIndex = getFirstIndex(tokens, indexMap, startLoc); + this.commentIndex = search(comments, startLoc); this.border = endLoc; } diff --git a/tools/node_modules/eslint/lib/source-code/token-store/forward-token-cursor.js b/tools/node_modules/eslint/lib/source-code/token-store/forward-token-cursor.js index e8c18609621fba..9305cbef68380e 100644 --- a/tools/node_modules/eslint/lib/source-code/token-store/forward-token-cursor.js +++ b/tools/node_modules/eslint/lib/source-code/token-store/forward-token-cursor.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const Cursor = require("./cursor"); -const utils = require("./utils"); +const { getFirstIndex, getLastIndex } = require("./utils"); //------------------------------------------------------------------------------ // Exports @@ -31,8 +31,8 @@ module.exports = class ForwardTokenCursor extends Cursor { constructor(tokens, comments, indexMap, startLoc, endLoc) { super(); this.tokens = tokens; - this.index = utils.getFirstIndex(tokens, indexMap, startLoc); - this.indexEnd = utils.getLastIndex(tokens, indexMap, endLoc); + this.index = getFirstIndex(tokens, indexMap, startLoc); + this.indexEnd = getLastIndex(tokens, indexMap, endLoc); } /** @inheritdoc */ diff --git a/tools/node_modules/eslint/lib/source-code/token-store/index.js b/tools/node_modules/eslint/lib/source-code/token-store/index.js index 46a96b2f4b189d..d222c87bbf40bf 100644 --- a/tools/node_modules/eslint/lib/source-code/token-store/index.js +++ b/tools/node_modules/eslint/lib/source-code/token-store/index.js @@ -37,8 +37,8 @@ function createIndexMap(tokens, comments) { const map = Object.create(null); let tokenIndex = 0; let commentIndex = 0; - let nextStart = 0; - let range = null; + let nextStart; + let range; while (tokenIndex < tokens.length || commentIndex < comments.length) { nextStart = (commentIndex < comments.length) ? comments[commentIndex].range[0] : Number.MAX_SAFE_INTEGER; diff --git a/tools/node_modules/eslint/lib/unsupported-api.js b/tools/node_modules/eslint/lib/unsupported-api.js index 8a2e147aabeca8..50e7f5bd6d2216 100644 --- a/tools/node_modules/eslint/lib/unsupported-api.js +++ b/tools/node_modules/eslint/lib/unsupported-api.js @@ -12,9 +12,8 @@ //----------------------------------------------------------------------------- const { FileEnumerator } = require("./cli-engine/file-enumerator"); -const { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint"); -const FlatRuleTester = require("./rule-tester/flat-rule-tester"); -const { ESLint } = require("./eslint/eslint"); +const { ESLint: FlatESLint, shouldUseFlatConfig } = require("./eslint/eslint"); +const { LegacyESLint } = require("./eslint/legacy-eslint"); //----------------------------------------------------------------------------- // Exports @@ -24,7 +23,6 @@ module.exports = { builtinRules: require("./rules"), FlatESLint, shouldUseFlatConfig, - FlatRuleTester, FileEnumerator, - LegacyESLint: ESLint + LegacyESLint }; diff --git a/tools/node_modules/eslint/messages/plugin-conflict.js b/tools/node_modules/eslint/messages/plugin-conflict.js index c8c060e2f05c52..4113a538fc9f9e 100644 --- a/tools/node_modules/eslint/messages/plugin-conflict.js +++ b/tools/node_modules/eslint/messages/plugin-conflict.js @@ -15,7 +15,7 @@ module.exports = function(it) { Please remove the "plugins" setting from either config or remove either plugin installation. -If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team. +If you still can't figure out the problem, please see https://eslint.org/docs/latest/use/troubleshooting. `; return result; diff --git a/tools/node_modules/eslint/messages/plugin-invalid.js b/tools/node_modules/eslint/messages/plugin-invalid.js index 8b471d4a3366a6..4c60e41d319241 100644 --- a/tools/node_modules/eslint/messages/plugin-invalid.js +++ b/tools/node_modules/eslint/messages/plugin-invalid.js @@ -11,6 +11,6 @@ module.exports = function(it) { "${configName}" was referenced from the config file in "${importerName}". -If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team. +If you still can't figure out the problem, please see https://eslint.org/docs/latest/use/troubleshooting. `.trimStart(); }; diff --git a/tools/node_modules/eslint/messages/plugin-missing.js b/tools/node_modules/eslint/messages/plugin-missing.js index 0b7d34e3aa5f85..366ec4500e5f14 100644 --- a/tools/node_modules/eslint/messages/plugin-missing.js +++ b/tools/node_modules/eslint/messages/plugin-missing.js @@ -14,6 +14,6 @@ It's likely that the plugin isn't installed correctly. Try reinstalling by runni The plugin "${pluginName}" was referenced from the config file in "${importerName}". -If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team. +If you still can't figure out the problem, please see https://eslint.org/docs/latest/use/troubleshooting. `.trimStart(); }; diff --git a/tools/node_modules/eslint/node_modules/.bin/eslint b/tools/node_modules/eslint/node_modules/.bin/eslint deleted file mode 120000 index 810e4bcb32af34..00000000000000 --- a/tools/node_modules/eslint/node_modules/.bin/eslint +++ /dev/null @@ -1 +0,0 @@ -../eslint/bin/eslint.js \ No newline at end of file diff --git a/tools/node_modules/eslint/node_modules/.bin/rimraf b/tools/node_modules/eslint/node_modules/.bin/rimraf deleted file mode 120000 index 4cd49a49ddfc17..00000000000000 --- a/tools/node_modules/eslint/node_modules/.bin/rimraf +++ /dev/null @@ -1 +0,0 @@ -../rimraf/bin.js \ No newline at end of file diff --git a/tools/node_modules/eslint/node_modules/@ampproject/remapping/dist/remapping.mjs b/tools/node_modules/eslint/node_modules/@ampproject/remapping/dist/remapping.mjs index b5eddeda5a7802..f3875999c027f7 100644 --- a/tools/node_modules/eslint/node_modules/@ampproject/remapping/dist/remapping.mjs +++ b/tools/node_modules/eslint/node_modules/@ampproject/remapping/dist/remapping.mjs @@ -1,17 +1,18 @@ import { decodedMappings, traceSegment, TraceMap } from '@jridgewell/trace-mapping'; -import { GenMapping, maybeAddSegment, setSourceContent, toDecodedMap, toEncodedMap } from '@jridgewell/gen-mapping'; +import { GenMapping, maybeAddSegment, setSourceContent, setIgnore, toDecodedMap, toEncodedMap } from '@jridgewell/gen-mapping'; -const SOURCELESS_MAPPING = /* #__PURE__ */ SegmentObject('', -1, -1, '', null); +const SOURCELESS_MAPPING = /* #__PURE__ */ SegmentObject('', -1, -1, '', null, false); const EMPTY_SOURCES = []; -function SegmentObject(source, line, column, name, content) { - return { source, line, column, name, content }; +function SegmentObject(source, line, column, name, content, ignore) { + return { source, line, column, name, content, ignore }; } -function Source(map, sources, source, content) { +function Source(map, sources, source, content, ignore) { return { map, sources, source, content, + ignore, }; } /** @@ -19,14 +20,14 @@ function Source(map, sources, source, content) { * (which may themselves be SourceMapTrees). */ function MapSource(map, sources) { - return Source(map, sources, '', null); + return Source(map, sources, '', null, false); } /** * A "leaf" node in the sourcemap tree, representing an original, unmodified source file. Recursive * segment tracing ends at the `OriginalSource`. */ -function OriginalSource(source, content) { - return Source(null, EMPTY_SOURCES, source, content); +function OriginalSource(source, content, ignore) { + return Source(null, EMPTY_SOURCES, source, content, ignore); } /** * traceMappings is only called on the root level SourceMapTree, and begins the process of @@ -55,10 +56,12 @@ function traceMappings(tree) { if (traced == null) continue; } - const { column, line, name, content, source } = traced; + const { column, line, name, content, source, ignore } = traced; maybeAddSegment(gen, i, genCol, source, line, column, name); if (source && content != null) setSourceContent(gen, source, content); + if (ignore) + setIgnore(gen, source, true); } } return gen; @@ -69,7 +72,7 @@ function traceMappings(tree) { */ function originalPositionFor(source, line, column, name) { if (!source.map) { - return SegmentObject(source.source, line, column, name, source.content); + return SegmentObject(source.source, line, column, name, source.content, source.ignore); } const segment = traceSegment(source.map, line, column); // If we couldn't find a segment, then this doesn't exist in the sourcemap. @@ -114,7 +117,7 @@ function buildSourceMapTree(input, loader) { return tree; } function build(map, loader, importer, importerDepth) { - const { resolvedSources, sourcesContent } = map; + const { resolvedSources, sourcesContent, ignoreList } = map; const depth = importerDepth + 1; const children = resolvedSources.map((sourceFile, i) => { // The loading context gives the loader more information about why this file is being loaded @@ -126,20 +129,22 @@ function build(map, loader, importer, importerDepth) { depth, source: sourceFile || '', content: undefined, + ignore: undefined, }; // Use the provided loader callback to retrieve the file's sourcemap. // TODO: We should eventually support async loading of sourcemap files. const sourceMap = loader(ctx.source, ctx); - const { source, content } = ctx; + const { source, content, ignore } = ctx; // If there is a sourcemap, then we need to recurse into it to load its source files. if (sourceMap) return build(new TraceMap(sourceMap, source), loader, source, depth); - // Else, it's an an unmodified source file. + // Else, it's an unmodified source file. // The contents of this unmodified source file can be overridden via the loader context, // allowing it to be explicitly null or a string. If it remains undefined, we fall back to // the importing sourcemap's `sourcesContent` field. const sourceContent = content !== undefined ? content : sourcesContent ? sourcesContent[i] : null; - return OriginalSource(source, sourceContent); + const ignored = ignore !== undefined ? ignore : ignoreList ? ignoreList.includes(i) : false; + return OriginalSource(source, sourceContent, ignored); }); return MapSource(map, children); } @@ -155,6 +160,7 @@ class SourceMap { this.file = out.file; this.mappings = out.mappings; this.names = out.names; + this.ignoreList = out.ignoreList; this.sourceRoot = out.sourceRoot; this.sources = out.sources; if (!options.excludeContent) { diff --git a/tools/node_modules/eslint/node_modules/@ampproject/remapping/dist/remapping.umd.js b/tools/node_modules/eslint/node_modules/@ampproject/remapping/dist/remapping.umd.js index e292d4c37f8035..6b7b3bb520a45d 100644 --- a/tools/node_modules/eslint/node_modules/@ampproject/remapping/dist/remapping.umd.js +++ b/tools/node_modules/eslint/node_modules/@ampproject/remapping/dist/remapping.umd.js @@ -4,17 +4,18 @@ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.remapping = factory(global.traceMapping, global.genMapping)); })(this, (function (traceMapping, genMapping) { 'use strict'; - const SOURCELESS_MAPPING = /* #__PURE__ */ SegmentObject('', -1, -1, '', null); + const SOURCELESS_MAPPING = /* #__PURE__ */ SegmentObject('', -1, -1, '', null, false); const EMPTY_SOURCES = []; - function SegmentObject(source, line, column, name, content) { - return { source, line, column, name, content }; + function SegmentObject(source, line, column, name, content, ignore) { + return { source, line, column, name, content, ignore }; } - function Source(map, sources, source, content) { + function Source(map, sources, source, content, ignore) { return { map, sources, source, content, + ignore, }; } /** @@ -22,14 +23,14 @@ * (which may themselves be SourceMapTrees). */ function MapSource(map, sources) { - return Source(map, sources, '', null); + return Source(map, sources, '', null, false); } /** * A "leaf" node in the sourcemap tree, representing an original, unmodified source file. Recursive * segment tracing ends at the `OriginalSource`. */ - function OriginalSource(source, content) { - return Source(null, EMPTY_SOURCES, source, content); + function OriginalSource(source, content, ignore) { + return Source(null, EMPTY_SOURCES, source, content, ignore); } /** * traceMappings is only called on the root level SourceMapTree, and begins the process of @@ -58,10 +59,12 @@ if (traced == null) continue; } - const { column, line, name, content, source } = traced; + const { column, line, name, content, source, ignore } = traced; genMapping.maybeAddSegment(gen, i, genCol, source, line, column, name); if (source && content != null) genMapping.setSourceContent(gen, source, content); + if (ignore) + genMapping.setIgnore(gen, source, true); } } return gen; @@ -72,7 +75,7 @@ */ function originalPositionFor(source, line, column, name) { if (!source.map) { - return SegmentObject(source.source, line, column, name, source.content); + return SegmentObject(source.source, line, column, name, source.content, source.ignore); } const segment = traceMapping.traceSegment(source.map, line, column); // If we couldn't find a segment, then this doesn't exist in the sourcemap. @@ -117,7 +120,7 @@ return tree; } function build(map, loader, importer, importerDepth) { - const { resolvedSources, sourcesContent } = map; + const { resolvedSources, sourcesContent, ignoreList } = map; const depth = importerDepth + 1; const children = resolvedSources.map((sourceFile, i) => { // The loading context gives the loader more information about why this file is being loaded @@ -129,20 +132,22 @@ depth, source: sourceFile || '', content: undefined, + ignore: undefined, }; // Use the provided loader callback to retrieve the file's sourcemap. // TODO: We should eventually support async loading of sourcemap files. const sourceMap = loader(ctx.source, ctx); - const { source, content } = ctx; + const { source, content, ignore } = ctx; // If there is a sourcemap, then we need to recurse into it to load its source files. if (sourceMap) return build(new traceMapping.TraceMap(sourceMap, source), loader, source, depth); - // Else, it's an an unmodified source file. + // Else, it's an unmodified source file. // The contents of this unmodified source file can be overridden via the loader context, // allowing it to be explicitly null or a string. If it remains undefined, we fall back to // the importing sourcemap's `sourcesContent` field. const sourceContent = content !== undefined ? content : sourcesContent ? sourcesContent[i] : null; - return OriginalSource(source, sourceContent); + const ignored = ignore !== undefined ? ignore : ignoreList ? ignoreList.includes(i) : false; + return OriginalSource(source, sourceContent, ignored); }); return MapSource(map, children); } @@ -158,6 +163,7 @@ this.file = out.file; this.mappings = out.mappings; this.names = out.names; + this.ignoreList = out.ignoreList; this.sourceRoot = out.sourceRoot; this.sources = out.sources; if (!options.excludeContent) { diff --git a/tools/node_modules/eslint/node_modules/@ampproject/remapping/package.json b/tools/node_modules/eslint/node_modules/@ampproject/remapping/package.json index bf3dad29b5c235..091224c6730b07 100644 --- a/tools/node_modules/eslint/node_modules/@ampproject/remapping/package.json +++ b/tools/node_modules/eslint/node_modules/@ampproject/remapping/package.json @@ -1,6 +1,6 @@ { "name": "@ampproject/remapping", - "version": "2.2.1", + "version": "2.3.0", "description": "Remap sequential sourcemaps through transformations to point at the original source code", "keywords": [ "source", @@ -69,7 +69,7 @@ "typescript": "4.6.3" }, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } } diff --git a/tools/node_modules/eslint/node_modules/@babel/code-frame/lib/index.js b/tools/node_modules/eslint/node_modules/@babel/code-frame/lib/index.js index 2f900ebacdc6cd..85ef5d6cda3b78 100644 --- a/tools/node_modules/eslint/node_modules/@babel/code-frame/lib/index.js +++ b/tools/node_modules/eslint/node_modules/@babel/code-frame/lib/index.js @@ -6,27 +6,26 @@ Object.defineProperty(exports, "__esModule", { exports.codeFrameColumns = codeFrameColumns; exports.default = _default; var _highlight = require("@babel/highlight"); -var _chalk = _interopRequireWildcard(require("chalk"), true); +var _picocolors = _interopRequireWildcard(require("picocolors"), true); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } -let chalkWithForcedColor = undefined; -function getChalk(forceColor) { +const colors = typeof process === "object" && (process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false") ? (0, _picocolors.createColors)(false) : _picocolors.default; +const compose = (f, g) => v => f(g(v)); +let pcWithForcedColor = undefined; +function getColors(forceColor) { if (forceColor) { - var _chalkWithForcedColor; - (_chalkWithForcedColor = chalkWithForcedColor) != null ? _chalkWithForcedColor : chalkWithForcedColor = new _chalk.default.constructor({ - enabled: true, - level: 1 - }); - return chalkWithForcedColor; + var _pcWithForcedColor; + (_pcWithForcedColor = pcWithForcedColor) != null ? _pcWithForcedColor : pcWithForcedColor = (0, _picocolors.createColors)(true); + return pcWithForcedColor; } - return _chalk.default; + return colors; } let deprecationWarningShown = false; -function getDefs(chalk) { +function getDefs(colors) { return { - gutter: chalk.grey, - marker: chalk.red.bold, - message: chalk.red.bold + gutter: colors.gray, + marker: compose(colors.red, colors.bold), + message: compose(colors.red, colors.bold) }; } const NEWLINE = /\r\n|[\n\r\u2028\u2029]/; @@ -88,10 +87,10 @@ function getMarkerLines(loc, source, opts) { } function codeFrameColumns(rawLines, loc, opts = {}) { const highlighted = (opts.highlightCode || opts.forceColor) && (0, _highlight.shouldHighlight)(opts); - const chalk = getChalk(opts.forceColor); - const defs = getDefs(chalk); - const maybeHighlight = (chalkFn, string) => { - return highlighted ? chalkFn(string) : string; + const colors = getColors(opts.forceColor); + const defs = getDefs(colors); + const maybeHighlight = (fmt, string) => { + return highlighted ? fmt(string) : string; }; const lines = rawLines.split(NEWLINE); const { @@ -127,7 +126,7 @@ function codeFrameColumns(rawLines, loc, opts = {}) { frame = `${" ".repeat(numberMaxWidth + 1)}${opts.message}\n${frame}`; } if (highlighted) { - return chalk.reset(frame); + return colors.reset(frame); } else { return frame; } diff --git a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/index.js b/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/index.js deleted file mode 100644 index 90a871c4d78f6f..00000000000000 --- a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/index.js +++ /dev/null @@ -1,165 +0,0 @@ -'use strict'; -const colorConvert = require('color-convert'); - -const wrapAnsi16 = (fn, offset) => function () { - const code = fn.apply(colorConvert, arguments); - return `\u001B[${code + offset}m`; -}; - -const wrapAnsi256 = (fn, offset) => function () { - const code = fn.apply(colorConvert, arguments); - return `\u001B[${38 + offset};5;${code}m`; -}; - -const wrapAnsi16m = (fn, offset) => function () { - const rgb = fn.apply(colorConvert, arguments); - return `\u001B[${38 + offset};2;${rgb[0]};${rgb[1]};${rgb[2]}m`; -}; - -function assembleStyles() { - const codes = new Map(); - const styles = { - modifier: { - reset: [0, 0], - // 21 isn't widely supported and 22 does the same thing - bold: [1, 22], - dim: [2, 22], - italic: [3, 23], - underline: [4, 24], - inverse: [7, 27], - hidden: [8, 28], - strikethrough: [9, 29] - }, - color: { - black: [30, 39], - red: [31, 39], - green: [32, 39], - yellow: [33, 39], - blue: [34, 39], - magenta: [35, 39], - cyan: [36, 39], - white: [37, 39], - gray: [90, 39], - - // Bright color - redBright: [91, 39], - greenBright: [92, 39], - yellowBright: [93, 39], - blueBright: [94, 39], - magentaBright: [95, 39], - cyanBright: [96, 39], - whiteBright: [97, 39] - }, - bgColor: { - bgBlack: [40, 49], - bgRed: [41, 49], - bgGreen: [42, 49], - bgYellow: [43, 49], - bgBlue: [44, 49], - bgMagenta: [45, 49], - bgCyan: [46, 49], - bgWhite: [47, 49], - - // Bright color - bgBlackBright: [100, 49], - bgRedBright: [101, 49], - bgGreenBright: [102, 49], - bgYellowBright: [103, 49], - bgBlueBright: [104, 49], - bgMagentaBright: [105, 49], - bgCyanBright: [106, 49], - bgWhiteBright: [107, 49] - } - }; - - // Fix humans - styles.color.grey = styles.color.gray; - - for (const groupName of Object.keys(styles)) { - const group = styles[groupName]; - - for (const styleName of Object.keys(group)) { - const style = group[styleName]; - - styles[styleName] = { - open: `\u001B[${style[0]}m`, - close: `\u001B[${style[1]}m` - }; - - group[styleName] = styles[styleName]; - - codes.set(style[0], style[1]); - } - - Object.defineProperty(styles, groupName, { - value: group, - enumerable: false - }); - - Object.defineProperty(styles, 'codes', { - value: codes, - enumerable: false - }); - } - - const ansi2ansi = n => n; - const rgb2rgb = (r, g, b) => [r, g, b]; - - styles.color.close = '\u001B[39m'; - styles.bgColor.close = '\u001B[49m'; - - styles.color.ansi = { - ansi: wrapAnsi16(ansi2ansi, 0) - }; - styles.color.ansi256 = { - ansi256: wrapAnsi256(ansi2ansi, 0) - }; - styles.color.ansi16m = { - rgb: wrapAnsi16m(rgb2rgb, 0) - }; - - styles.bgColor.ansi = { - ansi: wrapAnsi16(ansi2ansi, 10) - }; - styles.bgColor.ansi256 = { - ansi256: wrapAnsi256(ansi2ansi, 10) - }; - styles.bgColor.ansi16m = { - rgb: wrapAnsi16m(rgb2rgb, 10) - }; - - for (let key of Object.keys(colorConvert)) { - if (typeof colorConvert[key] !== 'object') { - continue; - } - - const suite = colorConvert[key]; - - if (key === 'ansi16') { - key = 'ansi'; - } - - if ('ansi16' in suite) { - styles.color.ansi[key] = wrapAnsi16(suite.ansi16, 0); - styles.bgColor.ansi[key] = wrapAnsi16(suite.ansi16, 10); - } - - if ('ansi256' in suite) { - styles.color.ansi256[key] = wrapAnsi256(suite.ansi256, 0); - styles.bgColor.ansi256[key] = wrapAnsi256(suite.ansi256, 10); - } - - if ('rgb' in suite) { - styles.color.ansi16m[key] = wrapAnsi16m(suite.rgb, 0); - styles.bgColor.ansi16m[key] = wrapAnsi16m(suite.rgb, 10); - } - } - - return styles; -} - -// Make the export immutable -Object.defineProperty(module, 'exports', { - enumerable: true, - get: assembleStyles -}); diff --git a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/license b/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/license deleted file mode 100644 index e7af2f77107d73..00000000000000 --- a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/license +++ /dev/null @@ -1,9 +0,0 @@ -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/package.json b/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/package.json deleted file mode 100644 index 65edb48c399c5c..00000000000000 --- a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "ansi-styles", - "version": "3.2.1", - "description": "ANSI escape codes for styling strings in the terminal", - "license": "MIT", - "repository": "chalk/ansi-styles", - "author": { - "name": "Sindre Sorhus", - "email": "sindresorhus@gmail.com", - "url": "sindresorhus.com" - }, - "engines": { - "node": ">=4" - }, - "scripts": { - "test": "xo && ava", - "screenshot": "svg-term --command='node screenshot' --out=screenshot.svg --padding=3 --width=55 --height=3 --at=1000 --no-cursor" - }, - "files": [ - "index.js" - ], - "keywords": [ - "ansi", - "styles", - "color", - "colour", - "colors", - "terminal", - "console", - "cli", - "string", - "tty", - "escape", - "formatting", - "rgb", - "256", - "shell", - "xterm", - "log", - "logging", - "command-line", - "text" - ], - "dependencies": { - "color-convert": "^1.9.0" - }, - "devDependencies": { - "ava": "*", - "babel-polyfill": "^6.23.0", - "svg-term-cli": "^2.1.1", - "xo": "*" - }, - "ava": { - "require": "babel-polyfill" - } -} diff --git a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/readme.md b/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/readme.md deleted file mode 100644 index 3158e2df59ce66..00000000000000 --- a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/ansi-styles/readme.md +++ /dev/null @@ -1,147 +0,0 @@ -# ansi-styles [![Build Status](https://travis-ci.org/chalk/ansi-styles.svg?branch=master)](https://travis-ci.org/chalk/ansi-styles) - -> [ANSI escape codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors_and_Styles) for styling strings in the terminal - -You probably want the higher-level [chalk](https://github.com/chalk/chalk) module for styling your strings. - - - - -## Install - -``` -$ npm install ansi-styles -``` - - -## Usage - -```js -const style = require('ansi-styles'); - -console.log(`${style.green.open}Hello world!${style.green.close}`); - - -// Color conversion between 16/256/truecolor -// NOTE: If conversion goes to 16 colors or 256 colors, the original color -// may be degraded to fit that color palette. This means terminals -// that do not support 16 million colors will best-match the -// original color. -console.log(style.bgColor.ansi.hsl(120, 80, 72) + 'Hello world!' + style.bgColor.close); -console.log(style.color.ansi256.rgb(199, 20, 250) + 'Hello world!' + style.color.close); -console.log(style.color.ansi16m.hex('#ABCDEF') + 'Hello world!' + style.color.close); -``` - -## API - -Each style has an `open` and `close` property. - - -## Styles - -### Modifiers - -- `reset` -- `bold` -- `dim` -- `italic` *(Not widely supported)* -- `underline` -- `inverse` -- `hidden` -- `strikethrough` *(Not widely supported)* - -### Colors - -- `black` -- `red` -- `green` -- `yellow` -- `blue` -- `magenta` -- `cyan` -- `white` -- `gray` ("bright black") -- `redBright` -- `greenBright` -- `yellowBright` -- `blueBright` -- `magentaBright` -- `cyanBright` -- `whiteBright` - -### Background colors - -- `bgBlack` -- `bgRed` -- `bgGreen` -- `bgYellow` -- `bgBlue` -- `bgMagenta` -- `bgCyan` -- `bgWhite` -- `bgBlackBright` -- `bgRedBright` -- `bgGreenBright` -- `bgYellowBright` -- `bgBlueBright` -- `bgMagentaBright` -- `bgCyanBright` -- `bgWhiteBright` - - -## Advanced usage - -By default, you get a map of styles, but the styles are also available as groups. They are non-enumerable so they don't show up unless you access them explicitly. This makes it easier to expose only a subset in a higher-level module. - -- `style.modifier` -- `style.color` -- `style.bgColor` - -###### Example - -```js -console.log(style.color.green.open); -``` - -Raw escape codes (i.e. without the CSI escape prefix `\u001B[` and render mode postfix `m`) are available under `style.codes`, which returns a `Map` with the open codes as keys and close codes as values. - -###### Example - -```js -console.log(style.codes.get(36)); -//=> 39 -``` - - -## [256 / 16 million (TrueColor) support](https://gist.github.com/XVilka/8346728) - -`ansi-styles` uses the [`color-convert`](https://github.com/Qix-/color-convert) package to allow for converting between various colors and ANSI escapes, with support for 256 and 16 million colors. - -To use these, call the associated conversion function with the intended output, for example: - -```js -style.color.ansi.rgb(100, 200, 15); // RGB to 16 color ansi foreground code -style.bgColor.ansi.rgb(100, 200, 15); // RGB to 16 color ansi background code - -style.color.ansi256.hsl(120, 100, 60); // HSL to 256 color ansi foreground code -style.bgColor.ansi256.hsl(120, 100, 60); // HSL to 256 color ansi foreground code - -style.color.ansi16m.hex('#C0FFEE'); // Hex (RGB) to 16 million color foreground code -style.bgColor.ansi16m.hex('#C0FFEE'); // Hex (RGB) to 16 million color background code -``` - - -## Related - -- [ansi-escapes](https://github.com/sindresorhus/ansi-escapes) - ANSI escape codes for manipulating the terminal - - -## Maintainers - -- [Sindre Sorhus](https://github.com/sindresorhus) -- [Josh Junon](https://github.com/qix-) - - -## License - -MIT diff --git a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/index.js b/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/index.js deleted file mode 100644 index 1cc5fa89a95159..00000000000000 --- a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/index.js +++ /dev/null @@ -1,228 +0,0 @@ -'use strict'; -const escapeStringRegexp = require('escape-string-regexp'); -const ansiStyles = require('ansi-styles'); -const stdoutColor = require('supports-color').stdout; - -const template = require('./templates.js'); - -const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); - -// `supportsColor.level` → `ansiStyles.color[name]` mapping -const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; - -// `color-convert` models to exclude from the Chalk API due to conflicts and such -const skipModels = new Set(['gray']); - -const styles = Object.create(null); - -function applyOptions(obj, options) { - options = options || {}; - - // Detect level if not set manually - const scLevel = stdoutColor ? stdoutColor.level : 0; - obj.level = options.level === undefined ? scLevel : options.level; - obj.enabled = 'enabled' in options ? options.enabled : obj.level > 0; -} - -function Chalk(options) { - // We check for this.template here since calling `chalk.constructor()` - // by itself will have a `this` of a previously constructed chalk object - if (!this || !(this instanceof Chalk) || this.template) { - const chalk = {}; - applyOptions(chalk, options); - - chalk.template = function () { - const args = [].slice.call(arguments); - return chalkTag.apply(null, [chalk.template].concat(args)); - }; - - Object.setPrototypeOf(chalk, Chalk.prototype); - Object.setPrototypeOf(chalk.template, chalk); - - chalk.template.constructor = Chalk; - - return chalk.template; - } - - applyOptions(this, options); -} - -// Use bright blue on Windows as the normal blue color is illegible -if (isSimpleWindowsTerm) { - ansiStyles.blue.open = '\u001B[94m'; -} - -for (const key of Object.keys(ansiStyles)) { - ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g'); - - styles[key] = { - get() { - const codes = ansiStyles[key]; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, key); - } - }; -} - -styles.visible = { - get() { - return build.call(this, this._styles || [], true, 'visible'); - } -}; - -ansiStyles.color.closeRe = new RegExp(escapeStringRegexp(ansiStyles.color.close), 'g'); -for (const model of Object.keys(ansiStyles.color.ansi)) { - if (skipModels.has(model)) { - continue; - } - - styles[model] = { - get() { - const level = this.level; - return function () { - const open = ansiStyles.color[levelMapping[level]][model].apply(null, arguments); - const codes = { - open, - close: ansiStyles.color.close, - closeRe: ansiStyles.color.closeRe - }; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); - }; - } - }; -} - -ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); -for (const model of Object.keys(ansiStyles.bgColor.ansi)) { - if (skipModels.has(model)) { - continue; - } - - const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); - styles[bgModel] = { - get() { - const level = this.level; - return function () { - const open = ansiStyles.bgColor[levelMapping[level]][model].apply(null, arguments); - const codes = { - open, - close: ansiStyles.bgColor.close, - closeRe: ansiStyles.bgColor.closeRe - }; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); - }; - } - }; -} - -const proto = Object.defineProperties(() => {}, styles); - -function build(_styles, _empty, key) { - const builder = function () { - return applyStyle.apply(builder, arguments); - }; - - builder._styles = _styles; - builder._empty = _empty; - - const self = this; - - Object.defineProperty(builder, 'level', { - enumerable: true, - get() { - return self.level; - }, - set(level) { - self.level = level; - } - }); - - Object.defineProperty(builder, 'enabled', { - enumerable: true, - get() { - return self.enabled; - }, - set(enabled) { - self.enabled = enabled; - } - }); - - // See below for fix regarding invisible grey/dim combination on Windows - builder.hasGrey = this.hasGrey || key === 'gray' || key === 'grey'; - - // `__proto__` is used because we must return a function, but there is - // no way to create a function with a different prototype - builder.__proto__ = proto; // eslint-disable-line no-proto - - return builder; -} - -function applyStyle() { - // Support varags, but simply cast to string in case there's only one arg - const args = arguments; - const argsLen = args.length; - let str = String(arguments[0]); - - if (argsLen === 0) { - return ''; - } - - if (argsLen > 1) { - // Don't slice `arguments`, it prevents V8 optimizations - for (let a = 1; a < argsLen; a++) { - str += ' ' + args[a]; - } - } - - if (!this.enabled || this.level <= 0 || !str) { - return this._empty ? '' : str; - } - - // Turns out that on Windows dimmed gray text becomes invisible in cmd.exe, - // see https://github.com/chalk/chalk/issues/58 - // If we're on Windows and we're dealing with a gray color, temporarily make 'dim' a noop. - const originalDim = ansiStyles.dim.open; - if (isSimpleWindowsTerm && this.hasGrey) { - ansiStyles.dim.open = ''; - } - - for (const code of this._styles.slice().reverse()) { - // Replace any instances already present with a re-opening code - // otherwise only the part of the string until said closing code - // will be colored, and the rest will simply be 'plain'. - str = code.open + str.replace(code.closeRe, code.open) + code.close; - - // Close the styling before a linebreak and reopen - // after next line to fix a bleed issue on macOS - // https://github.com/chalk/chalk/pull/92 - str = str.replace(/\r?\n/g, `${code.close}$&${code.open}`); - } - - // Reset the original `dim` if we changed it to work around the Windows dimmed gray issue - ansiStyles.dim.open = originalDim; - - return str; -} - -function chalkTag(chalk, strings) { - if (!Array.isArray(strings)) { - // If chalk() was called by itself or with a string, - // return the string itself as a string. - return [].slice.call(arguments, 1).join(' '); - } - - const args = [].slice.call(arguments, 2); - const parts = [strings.raw[0]]; - - for (let i = 1; i < strings.length; i++) { - parts.push(String(args[i - 1]).replace(/[{}\\]/g, '\\$&')); - parts.push(String(strings.raw[i])); - } - - return template(chalk, parts.join('')); -} - -Object.defineProperties(Chalk.prototype, styles); - -module.exports = Chalk(); // eslint-disable-line new-cap -module.exports.supportsColor = stdoutColor; -module.exports.default = module.exports; // For TypeScript diff --git a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/index.js.flow b/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/index.js.flow deleted file mode 100644 index 622caaa2e803f3..00000000000000 --- a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/index.js.flow +++ /dev/null @@ -1,93 +0,0 @@ -// @flow strict - -type TemplateStringsArray = $ReadOnlyArray; - -export type Level = $Values<{ - None: 0, - Basic: 1, - Ansi256: 2, - TrueColor: 3 -}>; - -export type ChalkOptions = {| - enabled?: boolean, - level?: Level -|}; - -export type ColorSupport = {| - level: Level, - hasBasic: boolean, - has256: boolean, - has16m: boolean -|}; - -export interface Chalk { - (...text: string[]): string, - (text: TemplateStringsArray, ...placeholders: string[]): string, - constructor(options?: ChalkOptions): Chalk, - enabled: boolean, - level: Level, - rgb(r: number, g: number, b: number): Chalk, - hsl(h: number, s: number, l: number): Chalk, - hsv(h: number, s: number, v: number): Chalk, - hwb(h: number, w: number, b: number): Chalk, - bgHex(color: string): Chalk, - bgKeyword(color: string): Chalk, - bgRgb(r: number, g: number, b: number): Chalk, - bgHsl(h: number, s: number, l: number): Chalk, - bgHsv(h: number, s: number, v: number): Chalk, - bgHwb(h: number, w: number, b: number): Chalk, - hex(color: string): Chalk, - keyword(color: string): Chalk, - - +reset: Chalk, - +bold: Chalk, - +dim: Chalk, - +italic: Chalk, - +underline: Chalk, - +inverse: Chalk, - +hidden: Chalk, - +strikethrough: Chalk, - - +visible: Chalk, - - +black: Chalk, - +red: Chalk, - +green: Chalk, - +yellow: Chalk, - +blue: Chalk, - +magenta: Chalk, - +cyan: Chalk, - +white: Chalk, - +gray: Chalk, - +grey: Chalk, - +blackBright: Chalk, - +redBright: Chalk, - +greenBright: Chalk, - +yellowBright: Chalk, - +blueBright: Chalk, - +magentaBright: Chalk, - +cyanBright: Chalk, - +whiteBright: Chalk, - - +bgBlack: Chalk, - +bgRed: Chalk, - +bgGreen: Chalk, - +bgYellow: Chalk, - +bgBlue: Chalk, - +bgMagenta: Chalk, - +bgCyan: Chalk, - +bgWhite: Chalk, - +bgBlackBright: Chalk, - +bgRedBright: Chalk, - +bgGreenBright: Chalk, - +bgYellowBright: Chalk, - +bgBlueBright: Chalk, - +bgMagentaBright: Chalk, - +bgCyanBright: Chalk, - +bgWhiteBrigh: Chalk, - - supportsColor: ColorSupport -}; - -declare module.exports: Chalk; diff --git a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/license b/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/license deleted file mode 100644 index e7af2f77107d73..00000000000000 --- a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/license +++ /dev/null @@ -1,9 +0,0 @@ -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/package.json b/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/package.json deleted file mode 100644 index bc324685a7625f..00000000000000 --- a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/package.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "name": "chalk", - "version": "2.4.2", - "description": "Terminal string styling done right", - "license": "MIT", - "repository": "chalk/chalk", - "engines": { - "node": ">=4" - }, - "scripts": { - "test": "xo && tsc --project types && flow --max-warnings=0 && nyc ava", - "bench": "matcha benchmark.js", - "coveralls": "nyc report --reporter=text-lcov | coveralls" - }, - "files": [ - "index.js", - "templates.js", - "types/index.d.ts", - "index.js.flow" - ], - "keywords": [ - "color", - "colour", - "colors", - "terminal", - "console", - "cli", - "string", - "str", - "ansi", - "style", - "styles", - "tty", - "formatting", - "rgb", - "256", - "shell", - "xterm", - "log", - "logging", - "command-line", - "text" - ], - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "devDependencies": { - "ava": "*", - "coveralls": "^3.0.0", - "execa": "^0.9.0", - "flow-bin": "^0.68.0", - "import-fresh": "^2.0.0", - "matcha": "^0.7.0", - "nyc": "^11.0.2", - "resolve-from": "^4.0.0", - "typescript": "^2.5.3", - "xo": "*" - }, - "types": "types/index.d.ts", - "xo": { - "envs": [ - "node", - "mocha" - ], - "ignores": [ - "test/_flow.js" - ] - } -} diff --git a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/readme.md b/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/readme.md deleted file mode 100644 index d298e2c48d64a0..00000000000000 --- a/tools/node_modules/eslint/node_modules/@babel/code-frame/node_modules/chalk/readme.md +++ /dev/null @@ -1,314 +0,0 @@ -

-
-
- Chalk -
-
-
-

- -> Terminal string styling done right - -[![Build Status](https://travis-ci.org/chalk/chalk.svg?branch=master)](https://travis-ci.org/chalk/chalk) [![Coverage Status](https://coveralls.io/repos/github/chalk/chalk/badge.svg?branch=master)](https://coveralls.io/github/chalk/chalk?branch=master) [![](https://img.shields.io/badge/unicorn-approved-ff69b4.svg)](https://www.youtube.com/watch?v=9auOCbH5Ns4) [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/xojs/xo) [![Mentioned in Awesome Node.js](https://awesome.re/mentioned-badge.svg)](https://github.com/sindresorhus/awesome-nodejs) - -### [See what's new in Chalk 2](https://github.com/chalk/chalk/releases/tag/v2.0.0) - - - - -## Highlights - -- Expressive API -- Highly performant -- Ability to nest styles -- [256/Truecolor color support](#256-and-truecolor-color-support) -- Auto-detects color support -- Doesn't extend `String.prototype` -- Clean and focused -- Actively maintained -- [Used by ~23,000 packages](https://www.npmjs.com/browse/depended/chalk) as of December 31, 2017 - - -## Install - -```console -$ npm install chalk -``` - - - - - - -## Usage - -```js -const chalk = require('chalk'); - -console.log(chalk.blue('Hello world!')); -``` - -Chalk comes with an easy to use composable API where you just chain and nest the styles you want. - -```js -const chalk = require('chalk'); -const log = console.log; - -// Combine styled and normal strings -log(chalk.blue('Hello') + ' World' + chalk.red('!')); - -// Compose multiple styles using the chainable API -log(chalk.blue.bgRed.bold('Hello world!')); - -// Pass in multiple arguments -log(chalk.blue('Hello', 'World!', 'Foo', 'bar', 'biz', 'baz')); - -// Nest styles -log(chalk.red('Hello', chalk.underline.bgBlue('world') + '!')); - -// Nest styles of the same type even (color, underline, background) -log(chalk.green( - 'I am a green line ' + - chalk.blue.underline.bold('with a blue substring') + - ' that becomes green again!' -)); - -// ES2015 template literal -log(` -CPU: ${chalk.red('90%')} -RAM: ${chalk.green('40%')} -DISK: ${chalk.yellow('70%')} -`); - -// ES2015 tagged template literal -log(chalk` -CPU: {red ${cpu.totalPercent}%} -RAM: {green ${ram.used / ram.total * 100}%} -DISK: {rgb(255,131,0) ${disk.used / disk.total * 100}%} -`); - -// Use RGB colors in terminal emulators that support it. -log(chalk.keyword('orange')('Yay for orange colored text!')); -log(chalk.rgb(123, 45, 67).underline('Underlined reddish color')); -log(chalk.hex('#DEADED').bold('Bold gray!')); -``` - -Easily define your own themes: - -```js -const chalk = require('chalk'); - -const error = chalk.bold.red; -const warning = chalk.keyword('orange'); - -console.log(error('Error!')); -console.log(warning('Warning!')); -``` - -Take advantage of console.log [string substitution](https://nodejs.org/docs/latest/api/console.html#console_console_log_data_args): - -```js -const name = 'Sindre'; -console.log(chalk.green('Hello %s'), name); -//=> 'Hello Sindre' -``` - - -## API - -### chalk.`