From 769f997431c55bdcfb3fd58d240a66fe32614ff9 Mon Sep 17 00:00:00 2001 From: Tanuj Kanti Date: Sun, 25 Feb 2024 14:24:53 +0530 Subject: [PATCH 001/166] feat: limit the references of Object.assign --- docs/src/rules/prefer-object-spread.md | 3 +++ lib/rules/prefer-object-spread.js | 15 +++++++++++++++ tests/lib/rules/prefer-object-spread.js | 4 ++++ 3 files changed, 22 insertions(+) diff --git a/docs/src/rules/prefer-object-spread.md b/docs/src/rules/prefer-object-spread.md index b1927350ece..b427bee0480 100644 --- a/docs/src/rules/prefer-object-spread.md +++ b/docs/src/rules/prefer-object-spread.md @@ -55,6 +55,9 @@ Object.assign(foo, bar); Object.assign(foo, { bar, baz }); Object.assign(foo, { ...baz }); + +var foo = Object.assign; +var bar = foo({}, baz); ``` ::: diff --git a/lib/rules/prefer-object-spread.js b/lib/rules/prefer-object-spread.js index 60b0c3175c0..bbe63f60134 100644 --- a/lib/rules/prefer-object-spread.js +++ b/lib/rules/prefer-object-spread.js @@ -239,6 +239,20 @@ function defineFixer(node, sourceCode) { }; } +/** + * Limit the reference to the Object.assign. + * @param {ASTNode|null} node The node that the rule warns on, i.e. the Object.assign call + * @returns {boolean} true if the reference has Object.assign + */ +function isObjectAssignReference(node) { + return ( + ( + (node.callee.object && (node.callee.object.name === "Object" || node.callee.object.property.name === "Object")) && + (node.callee.property && node.callee.property.name === "assign") + ) + ); +} + /** @type {import('../shared/types').Rule} */ module.exports = { meta: { @@ -276,6 +290,7 @@ module.exports = { // Iterate all calls of `Object.assign` (only of the global variable `Object`). for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { if ( + isObjectAssignReference(refNode) && refNode.arguments.length >= 1 && refNode.arguments[0].type === "ObjectExpression" && !hasArraySpread(refNode) && diff --git a/tests/lib/rules/prefer-object-spread.js b/tests/lib/rules/prefer-object-spread.js index ad346062d62..8488d475571 100644 --- a/tests/lib/rules/prefer-object-spread.js +++ b/tests/lib/rules/prefer-object-spread.js @@ -86,6 +86,10 @@ ruleTester.run("prefer-object-spread", rule, { code: "class C { #assign; foo() { Object.#assign({}, foo); } }", languageOptions: { ecmaVersion: 2022 } }, + { + code: "var foo = Object.assign; \n var bar = foo({}, baz);", + languageOptions: { ecmaVersion: 2022 } + }, // ignore Object.assign() with > 1 arguments if any of the arguments is an object expression with a getter/setter "Object.assign({ get a() {} }, {})", From 11144a2671b2404b293f656be111221557f3390f Mon Sep 17 00:00:00 2001 From: M Pater Date: Tue, 27 Feb 2024 12:18:49 +0100 Subject: [PATCH 002/166] feat: `no-restricted-imports` option added `allowImportNames` (#16196) * feat: [no-restricted-imports] added option `excludeImportNames` * Fix: Removed question marks from checks in `no-restricted-imports` * Fix: [no-restricted-imports] `excludeImportNames` * docs: Update no-restricted-imports.md with new option Documentation added for option `excludeImportNames`. * docs: Update no-restricted-imports option exludeImportNames to allowImportNames * fix: Renamed `no-restricted-imports` option `excludeImportNames` to `allowImportNames` * fix: `no-restricted-imports` comments for options `allowImportNames` to pass test * fix: `no-restricted-imports` option `allowImportNames` for importing `*` * fix: `no-restricted-imports` rules added for option `allowImportNames` * fix: `no-restricted-imports` tests added for option `allowImportNames` * Lint * Lint Fix * Check Rules * fix: `no-restricted-imports` * fix: `no-restricted-imports`: Add tests for allowImportNamePattern * docs: `no-restricted-imports` updated * feat: refactor no-restricted-imports and add tests * Update docs/src/rules/no-restricted-imports.md Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * Update docs/src/rules/no-restricted-imports.md Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * fix: `no-restricted-imports`: requested changes * docs: `no-restricted-imports`: added `allowImportNames` to `patterns` * Update docs/src/rules/no-restricted-imports.md Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * Update docs/src/rules/no-restricted-imports.md Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * docs: `no-restricted-imports` message variation * feat: validate schema * docs: `no-restricted-imports`: disallow using both `importNames` and `allowImportNames` * Update docs/src/rules/no-restricted-imports.md Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * Update docs/src/rules/no-restricted-imports.md Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * Update docs/src/rules/no-restricted-imports.md Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * Update docs/src/rules/no-restricted-imports.md Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * Update docs/src/rules/no-restricted-imports.md Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * Update docs/src/rules/no-restricted-imports.md Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * fix: `no-restricted-imports`: Review suggestions * feat: add more validations to schema * docs: add validate options name * Update lib/rules/no-restricted-imports.js Co-authored-by: Milos Djermanovic * Update lib/rules/no-restricted-imports.js Co-authored-by: Milos Djermanovic * Update lib/rules/no-restricted-imports.js Co-authored-by: Milos Djermanovic * Update lib/rules/no-restricted-imports.js Co-authored-by: Milos Djermanovic * Update lib/rules/no-restricted-imports.js Co-authored-by: Milos Djermanovic * Update lib/rules/no-restricted-imports.js Co-authored-by: Milos Djermanovic * fix: `no-restricted-imports`: tests updated accordingly * feat: add return * Update docs/src/rules/no-restricted-imports.md Co-authored-by: Milos Djermanovic * Update lib/rules/no-restricted-imports.js Co-authored-by: Milos Djermanovic * Update lib/rules/no-restricted-imports.js Co-authored-by: Milos Djermanovic * feat: `no-restricted-imports`: added custom message to tests * fix: `no-restricted-imports`: remove test case * fix: `no-restricted-imports` * Update docs/src/rules/no-restricted-imports.md Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * Update docs/src/rules/no-restricted-imports.md Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> --------- Co-authored-by: Tanuj Kanti Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Co-authored-by: Milos Djermanovic --- docs/src/rules/no-restricted-imports.md | 145 +++++++++++++ lib/rules/no-restricted-imports.js | 230 ++++++++++++++++----- tests/lib/rules/no-restricted-imports.js | 253 +++++++++++++++++++++++ 3 files changed, 581 insertions(+), 47 deletions(-) diff --git a/docs/src/rules/no-restricted-imports.md b/docs/src/rules/no-restricted-imports.md index 2ce0d8f9d1a..2211fd21c2f 100644 --- a/docs/src/rules/no-restricted-imports.md +++ b/docs/src/rules/no-restricted-imports.md @@ -230,6 +230,58 @@ import { AllowedObject as DisallowedObject } from "foo"; ::: +#### allowImportNames + +This option is an array. Inverse of `importNames`, `allowImportNames` allows the imports that are specified inside this array. So it restricts all imports from a module, except specified allowed ones. + +Note: `allowImportNames` cannot be used in combination with `importNames`. + +```json +"no-restricted-imports": ["error", { + "paths": [{ + "name": "import-foo", + "allowImportNames": ["Bar"], + "message": "Please use only Bar from import-foo." + }] +}] +``` + +Examples of **incorrect** code for `allowImportNames` in `paths`: + +Disallowing all import names except 'AllowedObject'. + +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + allowImportNames: ["AllowedObject"], + message: "Please use only 'AllowedObject' from 'foo'." +}]}]*/ + +import { DisallowedObject } from "foo"; +``` + +::: + +Examples of **correct** code for `allowImportNames` in `paths`: + +Disallowing all import names except 'AllowedObject'. + +::: correct { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + allowImportNames: ["AllowedObject"], + message: "Only use 'AllowedObject' from 'foo'." +}]}]*/ + +import { AllowedObject } from "foo"; +``` + +::: + ### patterns This is also an object option whose value is an array. This option allows you to specify multiple modules to restrict using `gitignore`-style patterns. @@ -445,6 +497,54 @@ import { hasValues } from 'utils/collection-utils'; ::: +#### allowImportNames + +You can also specify `allowImportNames` on objects inside of `patterns`. In this case, the specified names are applied only to the specified `group`. + +Note: `allowImportNames` cannot be used in combination with `importNames`, `importNamePattern` or `allowImportNamePattern`. + +```json +"no-restricted-imports": ["error", { + "patterns": [{ + "group": ["utils/*"], + "allowImportNames": ["isEmpty"], + "message": "Please use only 'isEmpty' from utils." + }] +}] +``` + +Examples of **incorrect** code for `allowImportNames` in `patterns`: + +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["utils/*"], + allowImportNames: ['isEmpty'], + message: "Please use only 'isEmpty' from utils." +}]}]*/ + +import { hasValues } from 'utils/collection-utils'; +``` + +::: + +Examples of **correct** code for `allowImportNames` in `patterns`: + +::: correct { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["utils/*"], + allowImportNames: ['isEmpty'], + message: "Please use only 'isEmpty' from utils." +}]}]*/ + +import { isEmpty } from 'utils/collection-utils'; +``` + +::: + #### importNamePattern This option allows you to use regex patterns to restrict import names: @@ -518,6 +618,51 @@ import isEmpty, { hasValue } from 'utils/collection-utils'; ::: +#### allowImportNamePattern + +This is a string option. Inverse of `importNamePattern`, this option allows imports that matches the specified regex pattern. So it restricts all imports from a module, except specified allowed patterns. + +Note: `allowImportNamePattern` cannot be used in combination with `importNames`, `importNamePattern` or `allowImportNames`. + +```json +"no-restricted-imports": ["error", { + "patterns": [{ + "group": ["import-foo/*"], + "allowImportNamePattern": "^foo", + }] +}] +``` + +Examples of **incorrect** code for `allowImportNamePattern` option: + +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["utils/*"], + allowImportNamePattern: '^has' +}]}]*/ + +import { isEmpty } from 'utils/collection-utils'; +``` + +::: + +Examples of **correct** code for `allowImportNamePattern` option: + +::: correct { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["utils/*"], + allowImportNamePattern: '^is' +}]}]*/ + +import { isEmpty } from 'utils/collection-utils'; +``` + +::: + ## When Not To Use It Don't use this rule or don't include a module in the list for this rule if you want to be able to import a module in your project without an ESLint error or warning. diff --git a/lib/rules/no-restricted-imports.js b/lib/rules/no-restricted-imports.js index afd0bbb8ba2..062be909ef0 100644 --- a/lib/rules/no-restricted-imports.js +++ b/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: { @@ -175,7 +218,8 @@ module.exports = { } else { memo[path].push({ message: importSource.message, - importNames: importSource.importNames + importNames: importSource.importNames, + allowImportNames: importSource.allowImportNames }); } return memo; @@ -190,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) { @@ -218,42 +268,9 @@ module.exports = { groupedRestrictedPaths[importSource].forEach(restrictedPathEntry => { const customMessage = restrictedPathEntry.message; const restrictedImportNames = restrictedPathEntry.importNames; + const allowedImportNames = restrictedPathEntry.allowImportNames; - if (restrictedImportNames) { - if (importNames.has("*")) { - const specifierData = importNames.get("*")[0]; - - context.report({ - node, - messageId: customMessage ? "everythingWithCustomMessage" : "everything", - loc: specifierData.loc, - data: { - importSource, - importNames: restrictedImportNames, - customMessage - } - }); - } - - restrictedImportNames.forEach(importName => { - if (importNames.has(importName)) { - const specifiers = importNames.get(importName); - - specifiers.forEach(specifier => { - context.report({ - node, - messageId: customMessage ? "importNameWithCustomMessage" : "importName", - loc: specifier.loc, - data: { - importSource, - customMessage, - importName - } - }); - }); - } - }); - } else { + if (!restrictedImportNames && !allowedImportNames) { context.report({ node, messageId: customMessage ? "pathWithCustomMessage" : "path", @@ -262,7 +279,72 @@ module.exports = { customMessage } }); + + return; } + + 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, + messageId: customMessage ? "importNameWithCustomMessage" : "importName", + loc: specifier.loc, + data: { + importSource, + customMessage, + importName + } + }); + }); + } + + if (allowedImportNames && !allowedImportNames.includes(importName)) { + specifiers.forEach(specifier => { + context.report({ + node, + loc: specifier.loc, + messageId: customMessage ? "allowedImportNameWithCustomMessage" : "allowedImportName", + data: { + importSource, + customMessage, + importName, + allowedImportNames + } + }); + }); + } + }); }); } @@ -281,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", @@ -313,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, @@ -346,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/tests/lib/rules/no-restricted-imports.js b/tests/lib/rules/no-restricted-imports.js index af50d44e6e7..e0456247d4b 100644 --- a/tests/lib/rules/no-restricted-imports.js +++ b/tests/lib/rules/no-restricted-imports.js @@ -375,6 +375,61 @@ ruleTester.run("no-restricted-imports", rule, { importNamePattern: "^Foo" }] }] + }, + { + code: "import { AllowedObject } from \"foo\";", + options: [{ + paths: [{ + name: "foo", + allowImportNames: ["AllowedObject"], + message: "Please import anything except 'AllowedObject' from /bar/ instead." + }] + }] + }, + { + code: "import { foo } from 'foo';", + options: [{ + paths: [{ + name: "foo", + allowImportNames: ["foo"] + }] + }] + }, + { + code: "import { foo } from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + allowImportNames: ["foo"] + }] + }] + }, + { + code: "export { bar } from 'foo';", + options: [{ + paths: [{ + name: "foo", + allowImportNames: ["bar"] + }] + }] + }, + { + code: "export { bar } from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + allowImportNames: ["bar"] + }] + }] + }, + { + code: "import { Foo } from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + allowImportNamePattern: "^Foo" + }] + }] } ], invalid: [{ @@ -1953,6 +2008,204 @@ ruleTester.run("no-restricted-imports", rule, { endColumn: 9, message: "* import is invalid because import name matching '/^Foo/u' pattern from 'foo' is restricted from being used." }] + }, + { + code: "export { Bar } from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + allowImportNamePattern: "^Foo" + }] + }], + errors: [{ + type: "ExportNamedDeclaration", + line: 1, + column: 10, + endColumn: 13, + message: "'Bar' import from 'foo' is restricted because only imports that match the pattern '/^Foo/u' are allowed from 'foo'." + }] + }, + { + code: "export { Bar } from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + allowImportNamePattern: "^Foo", + message: "Only imports that match the pattern '/^Foo/u' are allowed to be imported from 'foo'." + }] + }], + errors: [{ + type: "ExportNamedDeclaration", + line: 1, + column: 10, + endColumn: 13, + message: "'Bar' import from 'foo' is restricted because only imports that match the pattern '/^Foo/u' are allowed from 'foo'. Only imports that match the pattern '/^Foo/u' are allowed to be imported from 'foo'." + }] + }, + { + code: "import { AllowedObject, DisallowedObject } from \"foo\";", + options: [{ + paths: [{ + name: "foo", + allowImportNames: ["AllowedObject"] + }] + }], + errors: [{ + message: "'DisallowedObject' import from 'foo' is restricted because only 'AllowedObject' import(s) is/are allowed.", + type: "ImportDeclaration", + line: 1, + column: 25, + endColumn: 41 + }] + }, + { + code: "import { AllowedObject, DisallowedObject } from \"foo\";", + options: [{ + paths: [{ + name: "foo", + allowImportNames: ["AllowedObject"], + message: "Only 'AllowedObject' is allowed to be imported from 'foo'." + }] + }], + errors: [{ + message: "'DisallowedObject' import from 'foo' is restricted because only 'AllowedObject' import(s) is/are allowed. Only 'AllowedObject' is allowed to be imported from 'foo'.", + type: "ImportDeclaration", + line: 1, + column: 25, + endColumn: 41 + }] + }, + { + code: "import { AllowedObject, DisallowedObject } from \"foo\";", + options: [{ + patterns: [{ + group: ["foo"], + allowImportNames: ["AllowedObject"] + }] + }], + errors: [{ + message: "'DisallowedObject' import from 'foo' is restricted because only 'AllowedObject' import(s) is/are allowed.", + type: "ImportDeclaration", + line: 1, + column: 25, + endColumn: 41 + }] + }, + { + code: "import { AllowedObject, DisallowedObject } from \"foo\";", + options: [{ + patterns: [{ + group: ["foo"], + allowImportNames: ["AllowedObject"], + message: "Only 'AllowedObject' is allowed to be imported from 'foo'." + }] + }], + errors: [{ + message: "'DisallowedObject' import from 'foo' is restricted because only 'AllowedObject' import(s) is/are allowed. Only 'AllowedObject' is allowed to be imported from 'foo'.", + type: "ImportDeclaration", + line: 1, + column: 25, + endColumn: 41 + }] + }, + { + code: "import * as AllowedObject from \"foo\";", + options: [{ + paths: [{ + name: "foo", + allowImportNames: ["AllowedObject"] + }] + }], + errors: [{ + message: "* import is invalid because only 'AllowedObject' from 'foo' is/are allowed.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 26 + }] + }, + { + code: "import * as AllowedObject from \"foo\";", + options: [{ + paths: [{ + name: "foo", + allowImportNames: ["AllowedObject"], + message: "Only 'AllowedObject' is allowed to be imported from 'foo'." + }] + }], + errors: [{ + message: "* import is invalid because only 'AllowedObject' from 'foo' is/are allowed. Only 'AllowedObject' is allowed to be imported from 'foo'.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 26 + }] + }, + { + code: "import * as AllowedObject from \"foo/bar\";", + options: [{ + patterns: [{ + group: ["foo/*"], + allowImportNames: ["AllowedObject"] + }] + }], + errors: [{ + message: "* import is invalid because only 'AllowedObject' from 'foo/bar' is/are allowed.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 26 + }] + }, + { + code: "import * as AllowedObject from \"foo/bar\";", + options: [{ + patterns: [{ + group: ["foo/*"], + allowImportNames: ["AllowedObject"], + message: "Only 'AllowedObject' is allowed to be imported from 'foo'." + }] + }], + errors: [{ + message: "* import is invalid because only 'AllowedObject' from 'foo/bar' is/are allowed. Only 'AllowedObject' is allowed to be imported from 'foo'.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 26 + }] + }, + { + code: "import * as AllowedObject from \"foo/bar\";", + options: [{ + patterns: [{ + group: ["foo/*"], + allowImportNamePattern: "^Allow" + }] + }], + errors: [{ + message: "* import is invalid because only imports that match the pattern '/^Allow/u' from 'foo/bar' are allowed.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 26 + }] + }, + { + code: "import * as AllowedObject from \"foo/bar\";", + options: [{ + patterns: [{ + group: ["foo/*"], + allowImportNamePattern: "^Allow", + message: "Only import names starting with 'Allow' are allowed to be imported from 'foo'." + }] + }], + errors: [{ + message: "* import is invalid because only imports that match the pattern '/^Allow/u' from 'foo/bar' are allowed. Only import names starting with 'Allow' are allowed to be imported from 'foo'.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 26 + }] } ] }); From 450d0f044023843b1790bd497dfca45dcbdb41e4 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Wed, 28 Feb 2024 08:23:33 +0100 Subject: [PATCH 003/166] docs: fix `ignore` option docs (#18154) --- docs/src/integrate/nodejs-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/integrate/nodejs-api.md b/docs/src/integrate/nodejs-api.md index 5d081df3d3b..cc01388f852 100644 --- a/docs/src/integrate/nodejs-api.md +++ b/docs/src/integrate/nodejs-api.md @@ -128,7 +128,7 @@ The `ESLint` constructor takes an `options` object. If you omit the `options` ob * `options.globInputPaths` (`boolean`)
Default is `true`. If `false` is present, the [`eslint.lintFiles()`][eslint-lintfiles] method doesn't interpret glob patterns. * `options.ignore` (`boolean`)
- Default is `true`. If `false` is present, the [`eslint.lintFiles()`][eslint-lintfiles] method doesn't respect `.eslintignore` files or `ignorePatterns` in your configuration. + Default is `true`. If `false` is present, the [`eslint.lintFiles()`][eslint-lintfiles] method doesn't respect `ignorePatterns` in your configuration. * `options.ignorePatterns` (`string[] | null`)
Default is `null`. Ignore file patterns to use in addition to config ignores. These patterns are relative to `cwd`. * `options.passOnNoPatterns` (`boolean`)
From af6e17081fa6c343474959712e7a4a20f8b304e2 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Wed, 28 Feb 2024 20:08:57 +0100 Subject: [PATCH 004/166] fix: stop linting files after an error (#18155) --- lib/eslint/eslint.js | 9 ++++++++- tests/lib/eslint/eslint.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 97102d3fe0e..42b8ddd2410 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -838,6 +838,7 @@ class ESLint { configs, errorOnUnmatchedPattern }); + const controller = new AbortController(); debug(`${filePaths.length} files found in: ${Date.now() - startTime}ms`); @@ -906,9 +907,12 @@ class ESLint { fixer = message => shouldMessageBeFixed(message, config, fixTypesSet) && originalFix(message); } - return fs.readFile(filePath, "utf8") + 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, @@ -932,6 +936,9 @@ class ESLint { } return result; + }).catch(error => { + controller.abort(error); + throw error; }); }) diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 9360d39449d..a00efc2afad 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -16,6 +16,7 @@ const fs = require("fs"); const fsp = fs.promises; const os = require("os"); const path = require("path"); +const timers = require("node:timers/promises"); const escapeStringRegExp = require("escape-string-regexp"); const fCache = require("file-entry-cache"); const sinon = require("sinon"); @@ -4445,6 +4446,40 @@ describe("ESLint", () => { }); }); + it("should stop linting files if a rule crashes", async () => { + + const cwd = getFixturePath("files"); + let createCallCount = 0; + + eslint = new ESLint({ + cwd, + plugins: { + boom: { + rules: { + boom: { + create() { + createCallCount++; + throw Error("Boom!"); + } + } + } + } + }, + baseConfig: { + rules: { + "boom/boom": "error" + } + } + }); + + await assert.rejects(eslint.lintFiles("*.js")); + + // Wait until all files have been closed. + while (process.getActiveResourcesInfo().includes("CloseReq")) { + await timers.setImmediate(); + } + assert.strictEqual(createCallCount, 1); + }); }); From e5ef3cd6953bb40108556e0465653898ffed8420 Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Fri, 1 Mar 2024 02:47:57 +0530 Subject: [PATCH 005/166] docs: add inline cases condition in `no-fallthrough` (#18158) docs: add inline cases condition --- docs/src/rules/no-fallthrough.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/rules/no-fallthrough.md b/docs/src/rules/no-fallthrough.md index c49293cad4a..1f092097330 100644 --- a/docs/src/rules/no-fallthrough.md +++ b/docs/src/rules/no-fallthrough.md @@ -140,6 +140,11 @@ switch(foo) { doSomething(); } +switch(foo) { + case 1: case 2: + doSomething(); +} + switch(foo) { case 1: doSomething(); From c49ed63265fc8e0cccea404810a4c5075d396a15 Mon Sep 17 00:00:00 2001 From: Mathias Schreck Date: Fri, 1 Mar 2024 11:25:15 +0100 Subject: [PATCH 006/166] feat: update complexity rule for optional chaining & default values (#18152) Both, optional chaining expressions and default values (in function parameters or descructuring expressions) increase the branching of the code and thus the complexity is increased. Fixes: #18060 --- docs/src/rules/complexity.md | 8 +++ lib/rules/complexity.js | 13 +++++ tests/lib/rules/complexity.js | 93 ++++++++++++++++++++++++++++++++++- 3 files changed, 112 insertions(+), 2 deletions(-) diff --git a/docs/src/rules/complexity.md b/docs/src/rules/complexity.md index 08dd839f5cd..dda6acb45ea 100644 --- a/docs/src/rules/complexity.md +++ b/docs/src/rules/complexity.md @@ -57,6 +57,14 @@ function b() { foo ||= 1; bar &&= 1; } + +function c(a = {}) { // default parameter -> 2nd path + const { b = 'default' } = a; // default value during destructuring -> 3rd path +} + +function d(a) { + return a?.b?.c; // optional chaining with two optional properties creates two additional branches +} ``` ::: diff --git a/lib/rules/complexity.js b/lib/rules/complexity.js index ac114187b2c..647de7b4d3b 100644 --- a/lib/rules/complexity.js +++ b/lib/rules/complexity.js @@ -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/tests/lib/rules/complexity.js b/tests/lib/rules/complexity.js index 0c8eb637482..27ac112fcc2 100644 --- a/tests/lib/rules/complexity.js +++ b/tests/lib/rules/complexity.js @@ -124,7 +124,25 @@ ruleTester.run("complexity", rule, { { code: "class C { static { if (a || b) c = d || e; } }", options: [4], languageOptions: { ecmaVersion: 2022 } }, // object property options - { code: "function b(x) {}", options: [{ max: 1 }] } + { code: "function b(x) {}", options: [{ max: 1 }] }, + + // optional chaining + { + code: "function a(b) { b?.c; }", options: [{ max: 2 }] + }, + + // default function parameter values + { + code: "function a(b = '') {}", options: [{ max: 2 }] + }, + + // default destructuring values + { + code: "function a(b) { const { c = '' } = b; }", options: [{ max: 2 }] + }, + { + code: "function a(b) { const [ c = '' ] = b; }", options: [{ max: 2 }] + } ], invalid: [ { code: "function a(x) {}", options: [0], errors: [makeError("Function 'a'", 1, 0)] }, @@ -522,6 +540,77 @@ ruleTester.run("complexity", rule, { }, // object property options - { code: "function a(x) {}", options: [{ max: 0 }], errors: [makeError("Function 'a'", 1, 0)] } + { code: "function a(x) {}", options: [{ max: 0 }], errors: [makeError("Function 'a'", 1, 0)] }, + + // optional chaining + { + code: "function a(b) { b?.c; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)] + }, + { + code: "function a(b) { b?.['c']; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)] + }, + { + code: "function a(b) { b?.c; d || e; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)] + }, + { + code: "function a(b) { b?.c?.d; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)] + }, + { + code: "function a(b) { b?.['c']?.['d']; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)] + }, + { + code: "function a(b) { b?.c?.['d']; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)] + }, + { + code: "function a(b) { b?.c.d?.e; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)] + }, + { + code: "function a(b) { b?.c?.(); }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)] + }, + { + code: "function a(b) { b?.c?.()?.(); }", + options: [{ max: 3 }], + errors: [makeError("Function 'a'", 4, 3)] + }, + + // default function parameter values + { + code: "function a(b = '') {}", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)] + }, + + // default destructuring values + { + code: "function a(b) { const { c = '' } = b; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)] + }, + { + code: "function a(b) { const [ c = '' ] = b; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)] + }, + { + code: "function a(b) { const [ { c: d = '' } = {} ] = b; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 3, 1)] + } ] }); From e37153f71f173e8667273d6298bef81e0d33f9ba Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Fri, 1 Mar 2024 16:05:56 +0530 Subject: [PATCH 007/166] fix: improve error message for invalid rule config (#18147) * fix: improve error message for invalid rule config * refactor: update indentation & use more strict checks --- lib/config/rule-validator.js | 17 +++- tests/lib/config/flat-config-array.js | 110 ++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/lib/config/rule-validator.js b/lib/config/rule-validator.js index a087718c9b4..3b4ea6122cb 100644 --- a/lib/config/rule-validator.js +++ b/lib/config/rule-validator.js @@ -167,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/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index cfaa3ebd906..a8bff24417a 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -44,6 +44,81 @@ const baseConfig = { baz: { }, + "prefer-const": { + meta: { + schema: [ + { + type: "object", + properties: { + destructuring: { enum: ["any", "all"], default: "any" }, + ignoreReadBeforeAssign: { type: "boolean", default: false } + }, + additionalProperties: false + } + ] + } + }, + "prefer-destructuring": { + meta: { + schema: [ + { + oneOf: [ + { + type: "object", + properties: { + VariableDeclarator: { + type: "object", + properties: { + array: { + type: "boolean" + }, + object: { + type: "boolean" + } + }, + additionalProperties: false + }, + AssignmentExpression: { + type: "object", + properties: { + array: { + type: "boolean" + }, + object: { + type: "boolean" + } + }, + additionalProperties: false + } + }, + additionalProperties: false + }, + { + type: "object", + properties: { + array: { + type: "boolean" + }, + object: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + { + type: "object", + properties: { + enforceForRenamedProperties: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + } + }, // old-style boom() {}, @@ -2157,6 +2232,41 @@ describe("FlatConfigArray", () => { } })); + it("should error show expected properties", async () => { + + await assertInvalidConfig([ + { + rules: { + "prefer-const": ["error", { destruct: true }] + } + } + ], "Unexpected property \"destruct\". Expected properties: \"destructuring\", \"ignoreReadBeforeAssign\""); + + await assertInvalidConfig([ + { + rules: { + "prefer-destructuring": ["error", { obj: true }] + } + } + ], "Unexpected property \"obj\". Expected properties: \"VariableDeclarator\", \"AssignmentExpression\""); + + await assertInvalidConfig([ + { + rules: { + "prefer-destructuring": ["error", { obj: true }] + } + } + ], "Unexpected property \"obj\". Expected properties: \"array\", \"object\""); + + await assertInvalidConfig([ + { + rules: { + "prefer-destructuring": ["error", { object: true }, { enforceRenamedProperties: true }] + } + } + ], "Unexpected property \"enforceRenamedProperties\". Expected properties: \"enforceForRenamedProperties\""); + }); + }); describe("Invalid Keys", () => { From 79a95eb7da7fe657b6448c225d4f8ac31117456a Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sun, 3 Mar 2024 08:15:04 +0100 Subject: [PATCH 008/166] feat!: disallow multiple configuration comments for same rule (#18157) * feat!: disallow multiple configuration comments for same rule Fixes #18132 * update lint error message Co-authored-by: Nicholas C. Zakas * update tests Co-authored-by: Nicholas C. Zakas --- docs/src/use/migrate-to-9.0.0.md | 25 +++ lib/linter/linter.js | 16 ++ tests/lib/linter/linter.js | 310 +++++++++++++++++++++++++++++++ 3 files changed, 351 insertions(+) diff --git a/docs/src/use/migrate-to-9.0.0.md b/docs/src/use/migrate-to-9.0.0.md index 9db6f4724ec..e6e0ed58e66 100644 --- a/docs/src/use/migrate-to-9.0.0.md +++ b/docs/src/use/migrate-to-9.0.0.md @@ -25,6 +25,7 @@ The lists below are ordered roughly by the number of users each change is expect * [`--output-file` now writes a file to disk even with an empty output](#output-file) * [Change in behavior when no patterns are passed to CLI](#cli-empty-patterns) * [`/* eslint */` comments with only severity now retain options from the config file](#eslint-comment-options) +* [Multiple `/* eslint */` comments for the same rule are now disallowed](#multiple-eslint-comments) * [Stricter `/* exported */` parsing](#exported-parsing) * [`no-constructor-return` and `no-sequences` rule schemas are stricter](#stricter-rule-schemas) * [New checks in `no-implicit-coercion` by default](#no-implicit-coercion) @@ -190,6 +191,30 @@ Note that this change only affects cases where the same rule is configured in th **Related issue(s):** [#17381](https://github.com/eslint/eslint/issues/17381) +## Multiple `/* eslint */` comments for the same rule are now disallowed + +Prior to ESLint v9.0.0, if the file being linted contained multiple `/* eslint */` configuration comments for the same rule, the last one would be applied, while the others would be silently ignored. For example: + +```js +/* eslint semi: ["error", "always"] */ +/* eslint semi: ["error", "never"] */ + +foo() // valid, because the configuration is "never" +``` + +In ESLint v9.0.0, the first one is applied, while the others are reported as lint errors: + +```js +/* eslint semi: ["error", "always"] */ +/* eslint semi: ["error", "never"] */ // error: Rule "semi" is already configured by another configuration comment in the preceding code. This configuration is ignored. + +foo() // error: Missing semicolon +``` + +**To address:** Remove duplicate `/* eslint */` comments. + +**Related issue(s):** [#18132](https://github.com/eslint/eslint/issues/18132) + ## Stricter `/* exported */` parsing Prior to ESLint v9.0.0, the `/* exported */` directive incorrectly allowed the following syntax: diff --git a/lib/linter/linter.js b/lib/linter/linter.js index 7cdcbec21c6..82417034b38 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -439,6 +439,14 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config) 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]; /* @@ -1706,6 +1714,14 @@ 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 { let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue]; diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index 9ead00af1a9..8e7c3c5070c 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -1877,6 +1877,156 @@ describe("Linter", () => { }); }); + describe("when evaluating code with multiple configuration comments for same rule", () => { + + beforeEach(() => { + linter.defineRule("no-foo", { + meta: { + schema: [{ + enum: ["bar", "baz", "qux"] + }] + }, + create(context) { + const replacement = context.options[0] ?? "default"; + + return { + "Identifier[name='foo']"(node) { + context.report(node, `Replace 'foo' with '${replacement}'.`); + } + }; + } + }); + }); + + it("should apply the first and report an error for the second when there are two", () => { + const code = "/*eslint no-foo: ['error', 'bar']*/ /*eslint no-foo: ['error', 'baz']*/ foo;"; + + const messages = linter.verify(code); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + severity: 2, + message: "Rule \"no-foo\" is already configured by another configuration comment in the preceding code. This configuration is ignored.", + line: 1, + column: 37, + endLine: 1, + endColumn: 72, + nodeType: null + }, + { + ruleId: "no-foo", + severity: 2, + message: "Replace 'foo' with 'bar'.", + line: 1, + column: 73, + endLine: 1, + endColumn: 76, + nodeType: "Identifier" + } + ]); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply the first and report an error for each other when there are more than two", () => { + const code = "/*eslint no-foo: ['error', 'bar']*/ /*eslint no-foo: ['error', 'baz']*/ /*eslint no-foo: ['error', 'qux']*/ foo;"; + + const messages = linter.verify(code); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + severity: 2, + message: "Rule \"no-foo\" is already configured by another configuration comment in the preceding code. This configuration is ignored.", + line: 1, + column: 37, + endLine: 1, + endColumn: 72, + nodeType: null + }, + { + ruleId: null, + severity: 2, + message: "Rule \"no-foo\" is already configured by another configuration comment in the preceding code. This configuration is ignored.", + line: 1, + column: 73, + endLine: 1, + endColumn: 108, + nodeType: null + }, + { + ruleId: "no-foo", + severity: 2, + message: "Replace 'foo' with 'bar'.", + line: 1, + column: 109, + endLine: 1, + endColumn: 112, + nodeType: "Identifier" + } + ]); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply the first and report an error for the second when both just override severity", () => { + const code = "/*eslint no-foo: 'warn'*/ /*eslint no-foo: 'error'*/ foo;"; + + const messages = linter.verify(code, { rules: { "no-foo": ["error", "bar"] } }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + severity: 2, + message: "Rule \"no-foo\" is already configured by another configuration comment in the preceding code. This configuration is ignored.", + line: 1, + column: 27, + endLine: 1, + endColumn: 53, + nodeType: null + }, + { + ruleId: "no-foo", + severity: 1, + message: "Replace 'foo' with 'bar'.", + line: 1, + column: 54, + endLine: 1, + endColumn: 57, + nodeType: "Identifier" + } + ]); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply the second if the first has an invalid configuration", () => { + const code = "/*eslint no-foo: ['error', 'quux']*/ /*eslint no-foo: ['error', 'bar']*/ foo;"; + + const messages = linter.verify(code); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.include(messages[0].message, "Configuration for rule \"no-foo\" is invalid"); + assert.strictEqual(messages[1].message, "Replace 'foo' with 'bar'."); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply configurations for other rules that are in the same comment as the duplicate", () => { + const code = "/*eslint no-foo: ['error', 'bar']*/ /*eslint no-foo: ['error', 'baz'], no-alert: ['error']*/ foo; alert();"; + + const messages = linter.verify(code); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 3); + assert.strictEqual(messages[0].message, "Rule \"no-foo\" is already configured by another configuration comment in the preceding code. This configuration is ignored."); + assert.strictEqual(messages[1].message, "Replace 'foo' with 'bar'."); + assert.strictEqual(messages[2].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + describe("when evaluating code with comments to enable and disable all reporting", () => { it("should report a violation", () => { @@ -11225,6 +11375,166 @@ describe("Linter with FlatConfigArray", () => { }); }); + describe("when evaluating code with multiple configuration comments for same rule", () => { + + let baseConfig; + + beforeEach(() => { + baseConfig = { + plugins: { + "test-plugin": { + rules: { + "no-foo": { + meta: { + schema: [{ + enum: ["bar", "baz", "qux"] + }] + }, + create(context) { + const replacement = context.options[0] ?? "default"; + + return { + "Identifier[name='foo']"(node) { + context.report(node, `Replace 'foo' with '${replacement}'.`); + } + }; + } + } + } + } + } + }; + }); + + it("should apply the first and report an error for the second when there are two", () => { + const code = "/*eslint test-plugin/no-foo: ['error', 'bar']*/ /*eslint test-plugin/no-foo: ['error', 'baz']*/ foo;"; + + const messages = linter.verify(code, baseConfig); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + severity: 2, + message: "Rule \"test-plugin/no-foo\" is already configured by another configuration comment in the preceding code. This configuration is ignored.", + line: 1, + column: 49, + endLine: 1, + endColumn: 96, + nodeType: null + }, + { + ruleId: "test-plugin/no-foo", + severity: 2, + message: "Replace 'foo' with 'bar'.", + line: 1, + column: 97, + endLine: 1, + endColumn: 100, + nodeType: "Identifier" + } + ]); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply the first and report an error for each other when there are more than two", () => { + const code = "/*eslint test-plugin/no-foo: ['error', 'bar']*/ /*eslint test-plugin/no-foo: ['error', 'baz']*/ /*eslint test-plugin/no-foo: ['error', 'qux']*/ foo;"; + + const messages = linter.verify(code, baseConfig); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + severity: 2, + message: "Rule \"test-plugin/no-foo\" is already configured by another configuration comment in the preceding code. This configuration is ignored.", + line: 1, + column: 49, + endLine: 1, + endColumn: 96, + nodeType: null + }, + { + ruleId: null, + severity: 2, + message: "Rule \"test-plugin/no-foo\" is already configured by another configuration comment in the preceding code. This configuration is ignored.", + line: 1, + column: 97, + endLine: 1, + endColumn: 144, + nodeType: null + }, + { + ruleId: "test-plugin/no-foo", + severity: 2, + message: "Replace 'foo' with 'bar'.", + line: 1, + column: 145, + endLine: 1, + endColumn: 148, + nodeType: "Identifier" + } + ]); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply the first and report an error for the second when both just override severity", () => { + const code = "/*eslint test-plugin/no-foo: 'warn'*/ /*eslint test-plugin/no-foo: 'error'*/ foo;"; + + const messages = linter.verify(code, { ...baseConfig, rules: { "test-plugin/no-foo": ["error", "bar"] } }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + severity: 2, + message: "Rule \"test-plugin/no-foo\" is already configured by another configuration comment in the preceding code. This configuration is ignored.", + line: 1, + column: 39, + endLine: 1, + endColumn: 77, + nodeType: null + }, + { + ruleId: "test-plugin/no-foo", + severity: 1, + message: "Replace 'foo' with 'bar'.", + line: 1, + column: 78, + endLine: 1, + endColumn: 81, + nodeType: "Identifier" + } + ]); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply the second if the first has an invalid configuration", () => { + const code = "/*eslint test-plugin/no-foo: ['error', 'quux']*/ /*eslint test-plugin/no-foo: ['error', 'bar']*/ foo;"; + + const messages = linter.verify(code, baseConfig); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.include(messages[0].message, "Inline configuration for rule \"test-plugin/no-foo\" is invalid"); + assert.strictEqual(messages[1].message, "Replace 'foo' with 'bar'."); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply configurations for other rules that are in the same comment as the duplicate", () => { + const code = "/*eslint test-plugin/no-foo: ['error', 'bar']*/ /*eslint test-plugin/no-foo: ['error', 'baz'], no-alert: ['error']*/ foo; alert();"; + + const messages = linter.verify(code, baseConfig); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 3); + assert.strictEqual(messages[0].message, "Rule \"test-plugin/no-foo\" is already configured by another configuration comment in the preceding code. This configuration is ignored."); + assert.strictEqual(messages[1].message, "Replace 'foo' with 'bar'."); + assert.strictEqual(messages[2].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + describe("when evaluating code with comments to enable and disable all reporting", () => { it("should report a violation", () => { From 1f1260e863f53e2a5891163485a67c55d41993aa Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Mon, 4 Mar 2024 09:54:49 +0100 Subject: [PATCH 009/166] docs: replace HackerOne link with GitHub advisory (#18165) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c3c3a2a2e02..331622770d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,7 @@ Before filing an issue, please be sure to read the guidelines for what you're re * [Propose a Rule Change](https://eslint.org/docs/latest/contribute/propose-rule-change) * [Request a Change](https://eslint.org/docs/latest/contribute/request-change) -To report a security vulnerability in ESLint, please use our [HackerOne program](https://hackerone.com/eslint). +To report a security vulnerability in ESLint, please use our [create an advisory form](https://github.com/eslint/eslint/security/advisories/new) on GitHub. ## Contributing Code From 972ef155a94ad2cc85db7d209ad869869222c14c Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Tue, 5 Mar 2024 00:26:10 +0530 Subject: [PATCH 010/166] chore: remove invalid type in @eslint/js (#18164) --- packages/js/src/configs/eslint-recommended.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/js/src/configs/eslint-recommended.js b/packages/js/src/configs/eslint-recommended.js index 2b8ca3a8593..b105595877b 100644 --- a/packages/js/src/configs/eslint-recommended.js +++ b/packages/js/src/configs/eslint-recommended.js @@ -8,7 +8,6 @@ /* eslint sort-keys: ["error", "asc"] -- Long, so make more readable */ -/** @type {import("../lib/shared/types").ConfigData} */ module.exports = Object.freeze({ rules: Object.freeze({ "constructor-super": "error", From a451b32b33535a57b4b7e24291f30760f65460ba Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Tue, 5 Mar 2024 13:05:04 +0100 Subject: [PATCH 011/166] feat: make `no-misleading-character-class` report more granular errors (#18082) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: report granular errors on arbitrary literals * use npm dependency * test with unescaped CRLF * inline `createReportLocationGenerator` * unit test for templates with expressions * restore old name `getNodeReportLocations` * update JSDoc * `charInfos` → `codeUnits` * extract char-source to a utility module * add `read` method to `SourceReader` * add `advance` method and JSDoc * fix logic * `SourceReader` → `TextReader` * handle `RegExp` calls with regex patterns * fix for browser test * fix for Node.js 18 * limit applicability of `getStaticValue` for Node.js 18 compatibility * fix for `RegExp()` without arguments * update JSDoc for `getStaticValueOrRegex` --- lib/rules/no-misleading-character-class.js | 173 ++++--- lib/rules/utils/char-source.js | 240 ++++++++++ .../rules/no-misleading-character-class.js | 423 ++++++++++++++---- tests/lib/rules/utils/char-source.js | 256 +++++++++++ 4 files changed, 945 insertions(+), 147 deletions(-) create mode 100644 lib/rules/utils/char-source.js create mode 100644 tests/lib/rules/utils/char-source.js diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index 8d818665790..fa50e226f97 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/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 @@ -193,6 +200,33 @@ const findCharacterSequences = { 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 //------------------------------------------------------------------------------ @@ -225,62 +259,7 @@ module.exports = { create(context) { const sourceCode = context.sourceCode; const parser = new RegExpParser(); - - /** - * Generates a granular loc for context.report, if directly calculable. - * @param {Character[]} chars Individual characters being reported on. - * @param {Node} node Parent string node to report within. - * @returns {Object | null} Granular loc for context.report, if directly calculable. - * @see https://github.com/eslint/eslint/pull/17515 - */ - function generateReportLocation(chars, node) { - - // Limit to to literals and expression-less templates with raw values === their value. - switch (node.type) { - case "TemplateLiteral": - if (node.expressions.length || sourceCode.getText(node).slice(1, -1) !== node.quasis[0].value.cooked) { - return null; - } - break; - - case "Literal": - if (typeof node.value === "string" && node.value !== node.raw.slice(1, -1)) { - return null; - } - break; - - default: - return null; - } - - return { - start: sourceCode.getLocFromIndex(node.range[0] + 1 + chars[0].start), - end: sourceCode.getLocFromIndex(node.range[0] + 1 + chars.at(-1).end) - }; - } - - /** - * Finds the report loc(s) for a range of matches. - * @param {Character[][]} matches Characters that should trigger a report. - * @param {Node} node The node to report. - * @returns {Object | null} Node loc(s) for context.report. - */ - function getNodeReportLocations(matches, node) { - const locs = []; - - for (const chars of matches) { - const loc = generateReportLocation(chars, node); - - // If a report can't match to a range, don't report any others - if (!loc) { - return [node.loc]; - } - - locs.push(loc); - } - - return locs; - } + const checkedPatternNodes = new Set(); /** * Verify a given regular expression. @@ -320,12 +299,58 @@ module.exports = { } else { foundKindMatches.set(kind, [...findCharacterSequences[kind](chars)]); } - } } } }); + 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; @@ -336,7 +361,7 @@ module.exports = { }]; } - const locs = getNodeReportLocations(matches, node); + const locs = getNodeReportLocations(matches); for (const loc of locs) { context.report({ @@ -351,6 +376,9 @@ module.exports = { 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; @@ -371,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(patternNode, pattern, flags || "", fixer => { + if (typeof flags === "string") { + verify(patternNode, pattern, flags, fixer => { if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)) { return null; diff --git a/lib/rules/utils/char-source.js b/lib/rules/utils/char-source.js new file mode 100644 index 00000000000..70738625b94 --- /dev/null +++ b/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/tests/lib/rules/no-misleading-character-class.js b/tests/lib/rules/no-misleading-character-class.js index 6ad54d42d4a..6a276ae12c2 100644 --- a/tests/lib/rules/no-misleading-character-class.js +++ b/tests/lib/rules/no-misleading-character-class.js @@ -40,6 +40,13 @@ ruleTester.run("no-misleading-character-class", rule, { "var r = /🇯🇵/", "var r = /[JP]/", "var r = /👨‍👩‍👦/", + "new RegExp()", + "var r = RegExp(/[👍]/u)", + "const regex = /[👍]/u; new RegExp(regex);", + { + code: "new RegExp('[👍]')", + languageOptions: { globals: { RegExp: "off" } } + }, // Ignore solo lead/tail surrogate. "var r = /[\\uD83D]/", @@ -72,6 +79,16 @@ ruleTester.run("no-misleading-character-class", rule, { { code: "var r = new globalThis.RegExp('[Á] [ ');", languageOptions: { ecmaVersion: 2020 } }, { code: "var r = globalThis.RegExp('{ [Á]', 'u');", languageOptions: { ecmaVersion: 2020 } }, + // don't report on templates with expressions + "var r = RegExp(`${x}[👍]`)", + + // don't report on unknown flags + "var r = new RegExp('[🇯🇵]', `${foo}`)", + String.raw`var r = new RegExp("[👍]", flags)`, + + // don't report on spread arguments + "const args = ['[👍]', 'i']; new RegExp(...args);", + // ES2024 { code: "var r = /[👍]/v", languageOptions: { ecmaVersion: 2024 } }, { code: String.raw`var r = /^[\q{👶🏻}]$/v`, languageOptions: { ecmaVersion: 2024 } }, @@ -625,23 +642,14 @@ ruleTester.run("no-misleading-character-class", rule, { { code: "var r = new RegExp(`\r\n[❇️]`)", errors: [{ - line: 1, - column: 20, + line: 2, + column: 2, endLine: 2, - endColumn: 6, + endColumn: 4, messageId: "combiningClass", suggestions: null }] }, - { - code: String.raw`var r = new RegExp("[👍]", flags)`, - errors: [{ - column: 22, - endColumn: 24, - messageId: "surrogatePairWithoutUFlag", - suggestions: null - }] - }, { code: String.raw`const flags = ""; var r = new RegExp("[👍]", flags)`, errors: [{ @@ -654,8 +662,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = RegExp("[\\uD83D\\uDC4D]", "")`, errors: [{ - column: 16, - endColumn: 34, + column: 18, + endColumn: 32, messageId: "surrogatePairWithoutUFlag", suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = RegExp("[\\uD83D\\uDC4D]", "u")` }] }] @@ -663,8 +671,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = RegExp("before[\\uD83D\\uDC4D]after", "")`, errors: [{ - column: 16, - endColumn: 45, + column: 24, + endColumn: 38, messageId: "surrogatePairWithoutUFlag", suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = RegExp("before[\\uD83D\\uDC4D]after", "u")` }] }] @@ -672,8 +680,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = RegExp("[before\\uD83D\\uDC4Dafter]", "")`, errors: [{ - column: 16, - endColumn: 45, + column: 24, + endColumn: 38, messageId: "surrogatePairWithoutUFlag", suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = RegExp("[before\\uD83D\\uDC4Dafter]", "u")` }] }] @@ -681,8 +689,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = RegExp("\t\t\t👍[👍]")`, errors: [{ - column: 16, - endColumn: 30, + column: 26, + endColumn: 28, messageId: "surrogatePairWithoutUFlag", suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = RegExp("\t\t\t👍[👍]", "u")` }] }] @@ -690,8 +698,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("\u1234[\\uD83D\\uDC4D]")`, errors: [{ - column: 20, - endColumn: 44, + column: 28, + endColumn: 42, messageId: "surrogatePairWithoutUFlag", suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("\u1234[\\uD83D\\uDC4D]", "u")` }] }] @@ -699,8 +707,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("\\u1234\\u5678👎[👍]")`, errors: [{ - column: 20, - endColumn: 42, + column: 38, + endColumn: 40, messageId: "surrogatePairWithoutUFlag", suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("\\u1234\\u5678👎[👍]", "u")` }] }] @@ -708,8 +716,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("\\u1234\\u5678👍[👍]")`, errors: [{ - column: 20, - endColumn: 42, + column: 38, + endColumn: 40, messageId: "surrogatePairWithoutUFlag", suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("\\u1234\\u5678👍[👍]", "u")` }] }] @@ -737,8 +745,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("[👍]\\a", "")`, errors: [{ - column: 20, - endColumn: 29, + column: 22, + endColumn: 24, messageId: "surrogatePairWithoutUFlag", suggestions: null // pattern would be invalid with the 'u' flag }] @@ -784,8 +792,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("[\\u0041\\u0301]", "")`, errors: [{ - column: 20, - endColumn: 38, + column: 22, + endColumn: 36, messageId: "combiningClass", suggestions: null }] @@ -793,8 +801,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("[\\u0041\\u0301]", "u")`, errors: [{ - column: 20, - endColumn: 38, + column: 22, + endColumn: 36, messageId: "combiningClass", suggestions: null }] @@ -802,8 +810,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("[\\u{41}\\u{301}]", "u")`, errors: [{ - column: 20, - endColumn: 39, + column: 22, + endColumn: 37, messageId: "combiningClass", suggestions: null }] @@ -829,8 +837,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`new RegExp("[ \\ufe0f]", "")`, errors: [{ - column: 12, - endColumn: 24, + column: 14, + endColumn: 22, messageId: "combiningClass", suggestions: null }] @@ -838,8 +846,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`new RegExp("[ \\ufe0f]", "u")`, errors: [{ - column: 12, - endColumn: 24, + column: 14, + endColumn: 22, messageId: "combiningClass", suggestions: null }] @@ -848,8 +856,14 @@ ruleTester.run("no-misleading-character-class", rule, { code: String.raw`new RegExp("[ \\ufe0f][ \\ufe0f]")`, errors: [ { - column: 12, - endColumn: 34, + column: 14, + endColumn: 22, + messageId: "combiningClass", + suggestions: null + }, + { + column: 24, + endColumn: 32, messageId: "combiningClass", suggestions: null } @@ -858,8 +872,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("[\\u2747\\uFE0F]", "")`, errors: [{ - column: 20, - endColumn: 38, + column: 22, + endColumn: 36, messageId: "combiningClass", suggestions: null }] @@ -867,8 +881,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("[\\u2747\\uFE0F]", "u")`, errors: [{ - column: 20, - endColumn: 38, + column: 22, + endColumn: 36, messageId: "combiningClass", suggestions: null }] @@ -876,8 +890,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("[\\u{2747}\\u{FE0F}]", "u")`, errors: [{ - column: 20, - endColumn: 42, + column: 22, + endColumn: 40, messageId: "combiningClass", suggestions: null }] @@ -911,8 +925,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("[\\uD83D\\uDC76\\uD83C\\uDFFB]", "u")`, errors: [{ - column: 20, - endColumn: 52, + column: 22, + endColumn: 50, messageId: "emojiModifier", suggestions: null }] @@ -920,8 +934,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("[\\u{1F476}\\u{1F3FB}]", "u")`, errors: [{ - column: 20, - endColumn: 44, + column: 22, + endColumn: 42, messageId: "emojiModifier", suggestions: null }] @@ -938,8 +952,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: "var r = RegExp(`\\t\\t\\t👍[👍]`)", errors: [{ - column: 16, - endColumn: 30, + column: 26, + endColumn: 28, messageId: "surrogatePairWithoutUFlag", suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = RegExp(`\\t\\t\\t👍[👍]`, \"u\")" }] }] @@ -995,23 +1009,6 @@ ruleTester.run("no-misleading-character-class", rule, { } ] }, - { - code: "var r = new RegExp('[🇯🇵]', `${foo}`)", - errors: [ - { - column: 22, - endColumn: 24, - messageId: "surrogatePairWithoutUFlag", - suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = new RegExp('[🇯🇵]', `${foo}u`)" }] - }, - { - column: 24, - endColumn: 26, - messageId: "surrogatePairWithoutUFlag", - suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = new RegExp('[🇯🇵]', `${foo}u`)" }] - } - ] - }, { code: String.raw`var r = new RegExp("[🇯🇵]")`, errors: [ @@ -1111,8 +1108,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("[\\uD83C\\uDDEF\\uD83C\\uDDF5]", "u")`, errors: [{ - column: 20, - endColumn: 52, + column: 22, + endColumn: 50, messageId: "regionalIndicatorSymbol", suggestions: null }] @@ -1120,8 +1117,8 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("[\\u{1F1EF}\\u{1F1F5}]", "u")`, errors: [{ - column: 20, - endColumn: 44, + column: 22, + endColumn: 42, messageId: "regionalIndicatorSymbol", suggestions: null }] @@ -1238,8 +1235,8 @@ ruleTester.run("no-misleading-character-class", rule, { code: String.raw`var r = new RegExp("[\\uD83D\\uDC68\\u200D\\uD83D\\uDC69\\u200D\\uD83D\\uDC66]", "u")`, errors: [ { - column: 20, - endColumn: 80, + column: 22, + endColumn: 78, messageId: "zwj", suggestions: null } @@ -1249,8 +1246,8 @@ ruleTester.run("no-misleading-character-class", rule, { code: String.raw`var r = new RegExp("[\\u{1F468}\\u{200D}\\u{1F469}\\u{200D}\\u{1F466}]", "u")`, errors: [ { - column: 20, - endColumn: 72, + column: 22, + endColumn: 70, messageId: "zwj", suggestions: null } @@ -1299,8 +1296,8 @@ ruleTester.run("no-misleading-character-class", rule, { languageOptions: { ecmaVersion: 2020 }, errors: [ { - column: 31, - endColumn: 83, + column: 33, + endColumn: 81, messageId: "zwj", suggestions: null } @@ -1335,8 +1332,242 @@ ruleTester.run("no-misleading-character-class", rule, { }] }, + // no granular reports on templates with expressions + { + code: 'new RegExp(`${"[👍🇯🇵]"}[😊]`);', + errors: [{ + column: 12, + endColumn: 31, + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ + messageId: "suggestUnicodeFlag", + output: 'new RegExp(`${"[👍🇯🇵]"}[😊]`, "u");' + }] + }] + }, + + // no granular reports on identifiers + { + code: 'const pattern = "[👍]"; new RegExp(pattern);', + errors: [{ + column: 36, + endColumn: 43, + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ + messageId: "suggestUnicodeFlag", + output: 'const pattern = "[👍]"; new RegExp(pattern, "u");' + }] + }] + }, + + // second argument in RegExp should override flags in regex literal + { + code: "RegExp(/[a👍z]/u, '');", + errors: [{ + column: 11, + endColumn: 13, + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ + messageId: "suggestUnicodeFlag", + output: "RegExp(/[a👍z]/u, 'u');" + }] + }] + }, + + /* + * These test cases have been disabled because of a limitation in Node.js 18, see https://github.com/eslint/eslint/pull/18082#discussion_r1506142421. + * + * { + * code: "const pattern = /[👍]/u; RegExp(pattern, '');", + * errors: [{ + * column: 33, + * endColumn: 40, + * messageId: "surrogatePairWithoutUFlag", + * suggestions: [{ + * messageId: "suggestUnicodeFlag", + * output: "const pattern = /[👍]/u; RegExp(pattern, 'u');" + * }] + * }] + * }, + * { + * code: "const pattern = /[👍]/g; RegExp(pattern, 'i');", + * errors: [{ + * column: 19, + * endColumn: 21, + * messageId: "surrogatePairWithoutUFlag", + * suggestions: [{ + * messageId: "suggestUnicodeFlag", + * output: "const pattern = /[👍]/gu; RegExp(pattern, 'i');" + * }] + * }, { + * column: 33, + * endColumn: 40, + * messageId: "surrogatePairWithoutUFlag", + * suggestions: [{ + * messageId: "suggestUnicodeFlag", + * output: "const pattern = /[👍]/g; RegExp(pattern, 'iu');" + * }] + * }] + * }, + */ + + // report only on regex literal if no flags are supplied + { + code: "RegExp(/[👍]/)", + errors: [{ + column: 10, + endColumn: 12, + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "RegExp(/[👍]/u)" }] + }] + }, + + // report only on RegExp call if a regex literal and flags are supplied + { + code: "RegExp(/[👍]/, 'i');", + errors: [{ + column: 10, + endColumn: 12, + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "RegExp(/[👍]/, 'iu');" }] + }] + }, + + // ignore RegExp if not built-in + { + code: "RegExp(/[👍]/, 'g');", + languageOptions: { globals: { RegExp: "off" } }, + errors: [{ + column: 10, + endColumn: 12, + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "RegExp(/[👍]/u, 'g');" }] + }] + }, + + { + code: String.raw` + + // "[" and "]" escaped as "\x5B" and "\u005D" + new RegExp("\x5B \\ufe0f\u005D") + + `, + errors: [{ + column: 29, + endColumn: 37, + messageId: "combiningClass", + suggestions: null + }] + }, + { + code: String.raw` + + // backslash escaped as "\u{5c}" + new RegExp("[ \u{5c}ufe0f]") + + `, + errors: [{ + column: 26, + endColumn: 38, + messageId: "combiningClass", + suggestions: null + }] + }, + { + code: String.raw` + + // "0" escaped as "\60" + new RegExp("[ \\ufe\60f]") + + `, + languageOptions: { sourceType: "script" }, + errors: [{ + column: 26, + endColumn: 36, + messageId: "combiningClass", + suggestions: null + }] + }, + { + code: String.raw` + + // "e" escaped as "\e" + new RegExp("[ \\uf\e0f]") + + `, + errors: [{ + column: 26, + endColumn: 35, + messageId: "combiningClass", + suggestions: null + }] + }, + { + code: String.raw` + + // line continuation: backslash + + + new RegExp('[ \\ufe0f]') + + `.replace("", "\\\r\n"), + errors: [{ + line: 4, + column: 26, + endLine: 5, + endColumn: 5, + messageId: "combiningClass", + suggestions: null + }] + }, + { + code: String.raw` + + // just a backslash escaped as "\\" + new RegExp([.\\u200D.]) + + `.replaceAll("", "`"), + errors: [{ + column: 26, + endColumn: 35, + messageId: "zwj", + suggestions: null + }] + }, + { + code: String.raw` + + // "u" escaped as "\x75" + new RegExp([.\\\x75200D.]) + + `.replaceAll("", "`"), + errors: [{ + column: 26, + endColumn: 38, + messageId: "zwj", + suggestions: null + }] + }, + + /* eslint-disable lines-around-comment, internal-rules/multiline-comment-style -- see https://github.com/eslint/eslint/issues/18081 */ + + { + code: String.raw` + + // unescaped counts as a single character + new RegExp([\\u200D.]) + + `.replaceAll("", "`").replace("", "\n"), + errors: [{ + line: 4, + column: 26, + endLine: 5, + endColumn: 9, + messageId: "zwj", + suggestions: null + }] + }, // ES2024 + { code: "var r = /[[👶🏻]]/v", languageOptions: { ecmaVersion: 2024 }, @@ -1348,17 +1579,41 @@ ruleTester.run("no-misleading-character-class", rule, { }] }, { - code: "var r = /[👍]/", + code: "new RegExp(/^[👍]$/v, '')", languageOptions: { - ecmaVersion: 2015 + ecmaVersion: 2024 }, errors: [{ - column: 11, - endColumn: 13, + column: 15, + endColumn: 17, messageId: "surrogatePairWithoutUFlag", - suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = /[👍]/u" }] + suggestions: [{ messageId: "suggestUnicodeFlag", output: "new RegExp(/^[👍]$/v, 'u')" }] }] } + /* + * This test case has been disabled because of a limitation in Node.js 18, see https://github.com/eslint/eslint/pull/18082#discussion_r1506142421. + * + * { + * code: "var r = /[👶🏻]/v; RegExp(r, 'v');", + * languageOptions: { + * ecmaVersion: 2024 + * }, + * errors: [{ + * column: 11, + * endColumn: 15, + * messageId: "emojiModifier", + * suggestions: null + * }, { + * column: 27, + * endColumn: 28, + * messageId: "emojiModifier", + * suggestions: null + * }] + * } + */ + + /* eslint-enable lines-around-comment, internal-rules/multiline-comment-style -- re-enable rule */ + ] }); diff --git a/tests/lib/rules/utils/char-source.js b/tests/lib/rules/utils/char-source.js new file mode 100644 index 00000000000..2f37d9f3c0f --- /dev/null +++ b/tests/lib/rules/utils/char-source.js @@ -0,0 +1,256 @@ +"use strict"; + +const assertStrict = require("node:assert/strict"); +const { parseStringLiteral, parseTemplateToken } = require("../../../../lib/rules/utils/char-source"); + +describe( + "parseStringLiteral", + () => { + const TESTS = [ + { + description: "works with an empty string", + source: '""', + expectedCodeUnits: [] + }, + { + description: "works with surrogate pairs", + source: '"a𝄞z"', + expectedCodeUnits: [ + { start: 1, source: "a" }, + { start: 2, source: "\ud834" }, + { start: 3, source: "\udd1e" }, + { start: 4, source: "z" } + ] + }, + { + description: "works with escape sequences for single characters", + source: '"a\\x40\\u231Bz"', + expectedCodeUnits: [ + { start: 1, source: "a" }, + { start: 2, source: "\\x40" }, + { start: 6, source: "\\u231B" }, + { start: 12, source: "z" } + ] + }, + { + description: "works with escape sequences for code points", + source: '"a\\u{ffff}\\u{10000}\\u{10ffff}z"', + expectedCodeUnits: [ + { start: 1, source: "a" }, + { start: 2, source: "\\u{ffff}" }, + { start: 10, source: "\\u{10000}" }, + { start: 10, source: "\\u{10000}" }, + { start: 19, source: "\\u{10ffff}" }, + { start: 19, source: "\\u{10ffff}" }, + { start: 29, source: "z" } + ] + }, + { + description: "works with line continuations", + source: '"a\\\n\\\r\n\\\u2028\\\u2029z"', + expectedCodeUnits: [ + { start: 1, source: "a" }, + { start: 11, source: "z" } + ] + }, + { + description: "works with simple escape sequences", + source: '"\\"\\0\\b\\f\\n\\r\\t\\v"', + expectedCodeUnits: ['\\"', "\\0", "\\b", "\\f", "\\n", "\\r", "\\t", "\\v"] + .map((source, index) => ({ source, start: 1 + index * 2 })) + }, + { + description: "works with a character outside of a line continuation", + source: '"a\u2028z"', + expectedCodeUnits: [ + { start: 1, source: "a" }, + { start: 2, source: "\u2028" }, + { start: 3, source: "z" } + ] + }, + { + description: "works with a character outside of a line continuation", + source: '"a\u2029z"', + expectedCodeUnits: [ + { start: 1, source: "a" }, + { start: 2, source: "\u2029" }, + { start: 3, source: "z" } + ] + }, + { + description: "works with octal escape sequences", + source: '"\\0123\\456"', + expectedCodeUnits: [ + { source: "\\012", start: 1 }, + { source: "3", start: 5 }, + { source: "\\45", start: 6 }, + { source: "6", start: 9 } + ] + }, + { + description: "works with an escaped 7", + source: '"\\7"', + expectedCodeUnits: [{ source: "\\7", start: 1 }] + }, + { + description: "works with an escaped 8", + source: '"\\8"', + expectedCodeUnits: [{ source: "\\8", start: 1 }] + }, + { + description: "works with an escaped 9", + source: '"\\9"', + expectedCodeUnits: [{ source: "\\9", start: 1 }] + }, + { + description: 'works with the escaped sequence "00"', + source: '"\\00"', + expectedCodeUnits: [{ source: "\\00", start: 1 }] + }, + { + description: "works with an escaped 0 followed by 8", + source: '"\\08"', + expectedCodeUnits: [ + { source: "\\0", start: 1 }, + { source: "8", start: 3 } + ] + }, + { + description: "works with an escaped 0 followed by 9", + source: '"\\09"', + expectedCodeUnits: [ + { source: "\\0", start: 1 }, + { source: "9", start: 3 } + ] + } + ]; + + for (const { description, source, expectedCodeUnits, only } of TESTS) { + (only ? it.only : it)( + description, + () => { + const codeUnits = parseStringLiteral(source); + const expectedCharCount = expectedCodeUnits.length; + + assertStrict.equal(codeUnits.length, expectedCharCount); + for (let index = 0; index < expectedCharCount; ++index) { + const codeUnit = codeUnits[index]; + const expectedUnit = expectedCodeUnits[index]; + const message = `Expected values to be strictly equal at index ${index}`; + + assertStrict.equal(codeUnit.start, expectedUnit.start, message); + assertStrict.equal(codeUnit.source, expectedUnit.source, message); + } + } + ); + } + } +); + +describe( + "parseTemplateToken", + () => { + const TESTS = + [ + { + description: "works with an empty template", + source: "``", + expectedCodeUnits: [] + }, + { + description: "works with surrogate pairs", + source: "`A𝄞Z`", + expectedCodeUnits: [ + { start: 1, source: "A" }, + { start: 2, source: "\ud834" }, + { start: 3, source: "\udd1e" }, + { start: 4, source: "Z" } + ] + }, + { + description: "works with escape sequences for single characters", + source: "`A\\x40\\u231BZ${", + expectedCodeUnits: [ + { start: 1, source: "A" }, + { start: 2, source: "\\x40" }, + { start: 6, source: "\\u231B" }, + { start: 12, source: "Z" } + ] + }, + { + description: "works with escape sequences for code points", + source: "}A\\u{FFFF}\\u{10000}\\u{10FFFF}Z${", + expectedCodeUnits: [ + { start: 1, source: "A" }, + { start: 2, source: "\\u{FFFF}" }, + { start: 10, source: "\\u{10000}" }, + { start: 10, source: "\\u{10000}" }, + { start: 19, source: "\\u{10FFFF}" }, + { start: 19, source: "\\u{10FFFF}" }, + { start: 29, source: "Z" } + ] + }, + { + description: "works with line continuations", + source: "}A\\\n\\\r\n\\\u2028\\\u2029Z`", + expectedCodeUnits: [ + { start: 1, source: "A" }, + { start: 11, source: "Z" } + ] + }, + { + description: "works with simple escape sequences", + source: "`\\0\\`\\b\\f\\n\\r\\t\\v`", + expectedCodeUnits: ["\\0", "\\`", "\\b", "\\f", "\\n", "\\r", "\\t", "\\v"] + .map((source, index) => ({ source, start: 1 + index * 2 })) + }, + { + description: "works with a character outside of a line continuation", + source: "`a\u2028z`", + expectedCodeUnits: [ + { start: 1, source: "a" }, + { start: 2, source: "\u2028" }, + { start: 3, source: "z" } + ] + }, + { + description: "works with a character outside of a line continuation", + source: "`a\u2029z`", + expectedCodeUnits: [ + { start: 1, source: "a" }, + { start: 2, source: "\u2029" }, + { start: 3, source: "z" } + ] + }, + { + description: "works with unescaped sequences", + source: "`A\r\nZ`", + expectedCodeUnits: [ + { start: 1, source: "A" }, + { start: 2, source: "\r\n" }, + { start: 4, source: "Z" } + ] + } + ]; + + for (const { description, source, expectedCodeUnits, only } of TESTS) { + (only ? it.only : it)( + description, + () => { + const codeUnits = parseTemplateToken(source); + const expectedCharCount = expectedCodeUnits.length; + + assertStrict.equal(codeUnits.length, expectedCharCount); + for (let index = 0; index < expectedCharCount; ++index) { + const codeUnit = codeUnits[index]; + const expectedUnit = expectedCodeUnits[index]; + const message = `Expected values to be strictly equal at index ${index}`; + + assertStrict.equal(codeUnit.start, expectedUnit.start, message); + assertStrict.equal(codeUnit.source, expectedUnit.source, message); + } + } + ); + } + } +); From 2908b9b96ab7a25fe8044a1755030b18186a75b0 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 6 Mar 2024 13:54:37 -0700 Subject: [PATCH 012/166] docs: Update release documentation (#18174) * docs: Update release documentation * Fix lint error --- docs/src/maintain/manage-releases.md | 32 +++++++++++----------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/docs/src/maintain/manage-releases.md b/docs/src/maintain/manage-releases.md index 7a5e763a009..b8ae546c602 100644 --- a/docs/src/maintain/manage-releases.md +++ b/docs/src/maintain/manage-releases.md @@ -30,28 +30,13 @@ The release manager needs to have access to ESLint's two-factor authentication f ## Release Communication -Each scheduled release should be associated with a release issue ([example](https://github.com/eslint/eslint/issues/8138)). The release issue is the source of information for the team about the status of a release. Be sure the release issue has the "release" label so that it's easy to find. +Each scheduled release is associated with an autogenerated release issue ([example](https://github.com/eslint/eslint/issues/18151)). The release issue is the source of information for the team about the status of a release and contains a checklist that the release manager should follow. ## Process -On the day of a scheduled release, the release manager should follow these steps: - -1. Review open pull requests to see if any should be merged. In general, you can merge pull requests that: - * Have been open for at least two days and approved (these are just waiting for merge). - * Important pull requests (as determined by the team). You should stop and have people review before merging if they haven't been already. - * Documentation changes. - * Small bugfixes written by a team member. -1. Log into Jenkins and schedule a build for the "ESLint Release" job. -1. Watch the console output of the build on Jenkins. At some point, the build will pause and a link will be produced with an input field for a six-digit 2FA code. -1. Enter the current six-digit 2FA code from your authenticator app. -1. Continue the build and wait for it to finish. -1. Update the release blog post with a "Highlights" section, including new rules and anything else that's important. -1. Make a release announcement in the public chatroom. -1. Make a release announcement on Twitter. -1. Make a release announcement on the release issue. Document any problems that occurred during the release, and remind the team not to merge anything other than documentation changes and bugfixes. Leave the release issue open. -1. Add the `patch release pending` label to the release issue. (When this label is present, `eslint-github-bot` will create a pending status check on non-semver-patch pull requests, to ensure that they aren't accidentally merged while a patch release is pending.) - -All release-related communications occur in the `#team` channel on Discord. +On the day of a scheduled release, the release manager should follow the steps in the release issue. + +All release-related communications occur in a thread in the `#team` channel on Discord. On the Monday following the scheduled release, the release manager needs to determine if a patch release is necessary. A patch release is considered necessary if any of the following occurred since the scheduled release: @@ -71,3 +56,12 @@ An emergency release is unplanned and isn't the regularly scheduled release or t In general, we try not to do emergency releases. Even if there is a regression, it's best to wait until Monday to see if any other problems arise so a patch release can fix as many issues as possible. The only real exception is if ESLint is completely unusable by most of the current users. For instance, we once pushed a release that errored for everyone because it was missing some core files. In that case, an emergency release is appropriate. + +## Troubleshooting + +### `npm publish` returns a 404 + +This typically happens due to a permission error related to the npm token. + +* `release-please` uses a granular access token that expires after a year. This token is tied to the `eslintbot` npm account and needs to be regenerated every year in March. If the access token is expired, `npm publish` returns a 404. +* Jenkins uses a classic access token without an expiration date, but it does require a 2FA code to publish. If the 2FA code is incorrect, then `npm publish` returns a 404. From 558274abbd25ef269f4994cf258b2e44afbad548 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 7 Mar 2024 08:05:57 +0000 Subject: [PATCH 013/166] docs: Update README --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0f90821dc67..f70e809afc2 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,11 @@ The people who manage releases, review feature requests, and meet regularly to e Nicholas C. Zakas + +Francesco Trotta's Avatar
+Francesco Trotta +
+ Milos Djermanovic's Avatar
Milos Djermanovic @@ -250,11 +255,6 @@ Bryan Mishkin Josh Goldberg ✨
- -Francesco Trotta's Avatar
-Francesco Trotta -
- Tanuj Kanti's Avatar
Tanuj Kanti @@ -293,7 +293,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

-

JetBrains Liftoff American Express Workleap

Bronze Sponsors

+

JetBrains Liftoff Workleap

Bronze Sponsors

notion ThemeIsle Anagram Solver Icons8 Discord Transloadit Ignition Nx HeroCoders Nextbase Starter Kit

From d961eeb855b6dd9118a78165e358e454eb1d090d Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Thu, 7 Mar 2024 18:11:26 +0900 Subject: [PATCH 014/166] docs: show red underlines in examples in rules docs (#18041) * docs: show red underlines in examples in rules docs * fix: add postinstall to install eslint dependencies * fix: after-tokenize hook * fix: after-tokenize script * fix: after-tokenize hook * fix: after-tokenize hook * fix: comment * fix: change to verify using the custom container parserOptions * fix: refactor it so it doesn't affect tests * fix bug for no-multiple-empty-lines * fix: bug for eol-last * feat: use wavy line * feat: refactor * fix: refactor * feat: change to add message to title attribute * chore: revert scripts and change docs-ci * fix: prism-eslint-hook.js * fix: for unicode-bom * fix: disabled mark process if no linter is available * Update docs/tools/prism-eslint-hook.js Co-authored-by: Nicholas C. Zakas * chore: add fileoverview * chore: add netlify.toml * fix: for unicode-bom with always * feat: improved styles for zero width markers. * chore: fix format * Update syntax-highlighter.scss * fix: issue with consecutive line breaks * fix: hides marker on newline following the marker --------- Co-authored-by: Nicholas C. Zakas --- .github/workflows/docs-ci.yml | 3 + docs/.eleventy.js | 6 +- docs/netlify.toml | 2 + docs/src/_plugins/md-syntax-highlighter.js | 1 + docs/src/assets/scss/syntax-highlighter.scss | 46 ++ docs/tools/code-block-utils.js | 26 ++ docs/tools/markdown-it-rule-example.js | 6 +- docs/tools/prism-eslint-hook.js | 434 +++++++++++++++++++ 8 files changed, 520 insertions(+), 4 deletions(-) create mode 100644 docs/netlify.toml create mode 100644 docs/tools/code-block-utils.js create mode 100644 docs/tools/prism-eslint-hook.js diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml index 0e780de7cc2..09fbf643c3f 100644 --- a/.github/workflows/docs-ci.yml +++ b/.github/workflows/docs-ci.yml @@ -27,6 +27,9 @@ jobs: working-directory: docs run: npm install + - name: Install Packages + run: npm install --force + - name: Stylelint Docs working-directory: docs run: npm run lint:scss diff --git a/docs/.eleventy.js b/docs/.eleventy.js index 60e62319786..5698df45240 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -16,6 +16,7 @@ const { } = require("luxon"); const markdownIt = require("markdown-it"); const markdownItRuleExample = require("./tools/markdown-it-rule-example"); +const { addContentMustBeMarked } = require("./tools/prism-eslint-hook"); module.exports = function(eleventyConfig) { @@ -194,7 +195,10 @@ module.exports = function(eleventyConfig) { // markdown-it plugin options for playground-linked code blocks in rule examples. const ruleExampleOptions = markdownItRuleExample({ - open({ type, code, parserOptions, env }) { + open({ type, code, parserOptions, env, codeBlockToken }) { + + addContentMustBeMarked(codeBlockToken.content, parserOptions); + const isRuleRemoved = !Object.hasOwn(env.rules_meta, env.title); if (isRuleRemoved) { diff --git a/docs/netlify.toml b/docs/netlify.toml new file mode 100644 index 00000000000..57a235140ec --- /dev/null +++ b/docs/netlify.toml @@ -0,0 +1,2 @@ +[build] + command = "cd .. && npm install -f && cd ./docs && npm run build" diff --git a/docs/src/_plugins/md-syntax-highlighter.js b/docs/src/_plugins/md-syntax-highlighter.js index 9ae0dcd26cc..d645da631fc 100644 --- a/docs/src/_plugins/md-syntax-highlighter.js +++ b/docs/src/_plugins/md-syntax-highlighter.js @@ -26,6 +26,7 @@ SOFTWARE. const Prism = require("prismjs"); const loadLanguages = require("prismjs/components/"); +require("../../tools/prism-eslint-hook").installPrismESLintMarkerHook(); /** * diff --git a/docs/src/assets/scss/syntax-highlighter.scss b/docs/src/assets/scss/syntax-highlighter.scss index cb744db0e38..bef380b97c4 100644 --- a/docs/src/assets/scss/syntax-highlighter.scss +++ b/docs/src/assets/scss/syntax-highlighter.scss @@ -99,6 +99,52 @@ pre[class*="language-"] { cursor: help; } +.token.eslint-marked { + /* Draw the wavy line. */ + background: + url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23f14c4c'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") + repeat-x + bottom + left; + + /* + * Since the character width of the token span is not constant, + * if we use it as is, we may see a shift in the border. + * To make the border shift less noticeable, draw it with a smaller width. + */ + background-size: 4.4px auto; +} + +.token.eslint-marked-on-line-feed { + /* Use `padding` to give it width so the marker on line feed code is visible. */ + padding-right: 8px; +} + +.token.eslint-marked:not(.eslint-marked-on-line-feed) + .token.eslint-marked-on-line-feed { + /* + * If there is a marker before the same line, + * there is no need to make visible the marker on the line feed code. + */ + padding-right: 0; +} + +.token.eslint-marked-on-zero-width { + position: relative; + + /* Delete the wavy line. */ + background: none; +} + +.token.eslint-marked-on-zero-width::before { + content: ""; + position: absolute; + bottom: 0; + left: -2px; + border-left: 3px solid transparent; + border-right: 3px solid transparent; + border-bottom: 4px solid #d11; +} + .line-numbers-wrapper { position: absolute; top: 0; diff --git a/docs/tools/code-block-utils.js b/docs/tools/code-block-utils.js new file mode 100644 index 00000000000..faaadba70e1 --- /dev/null +++ b/docs/tools/code-block-utils.js @@ -0,0 +1,26 @@ +/** + * @fileoverview A utility related to markdown code blocks. + * @author Yosuke Ota + */ +"use strict"; + +/** + * Replaces some marks used in the code in the document and converts it into parsable code. + * The replaced string will be used in the linter, + * so be careful not to replace characters that change the location of the line and column. + * @param {string} code The code in the document. + * @returns {string} The parsable code. + */ +function docsExampleCodeToParsableCode(code) { + return code + + // Remove trailing newline and presentational `⏎` characters + .replace(/⏎(?=\n)/gu, "") + + // Code blocks always contain extra line breaks, so remove them. + .replace(/\n$/u, ""); +} + +module.exports = { + docsExampleCodeToParsableCode +}; diff --git a/docs/tools/markdown-it-rule-example.js b/docs/tools/markdown-it-rule-example.js index efe8ab90529..a769b120db5 100644 --- a/docs/tools/markdown-it-rule-example.js +++ b/docs/tools/markdown-it-rule-example.js @@ -1,5 +1,7 @@ "use strict"; +const { docsExampleCodeToParsableCode } = require("./code-block-utils"); + /** @typedef {import("../../lib/shared/types").ParserOptions} ParserOptions */ /** @@ -75,9 +77,7 @@ function markdownItRuleExample({ open, close }) { const codeBlockToken = tokens[index + 1]; // Remove trailing newline and presentational `⏎` characters (https://github.com/eslint/eslint/issues/17627): - const code = codeBlockToken.content - .replace(/\n$/u, "") - .replace(/⏎(?=\n)/gu, ""); + const code = docsExampleCodeToParsableCode(codeBlockToken.content); const text = open({ type, code, parserOptions, codeBlockToken, env }); diff --git a/docs/tools/prism-eslint-hook.js b/docs/tools/prism-eslint-hook.js new file mode 100644 index 00000000000..4b348916aa9 --- /dev/null +++ b/docs/tools/prism-eslint-hook.js @@ -0,0 +1,434 @@ +/** + * @fileoverview Use Prism hooks to draw linting errors with red markers on markdown code blocks. + * @author Yosuke Ota + */ +"use strict"; + +const Prism = require("prismjs"); +const { docsExampleCodeToParsableCode } = require("./code-block-utils"); + +let isAvailable = false; +let Linter = null; +let astUtils = null; + +/* + * We can only do the syntax highlighting in the English-language + * site because we need the Linter and astUtils. This same + * code is run in translation sites where these utilities are + * not available, so check to see if they are before attempting + * to highlight the code. + */ +try { + Linter = require("../../lib/api").Linter; + astUtils = require("../../lib/shared/ast-utils"); + isAvailable = true; +} catch { + + // ignore +} + +/** @typedef {import("../../lib/shared/types").ParserOptions} ParserOptions */ + +/** + * Content that needs to be marked with ESLint + * @type {string|undefined} + */ +let contentMustBeMarked; + +/** + * Parser options received from the `::: incorrect` or `::: correct` container. + * @type {ParserOptions|undefined} + */ +let contentParserOptions; + +/** + * Set content that needs to be marked. + * @param {string} content Source code content that marks ESLint errors. + * @param {ParserOptions} options The options used for validation. + * @returns {void} + */ +function addContentMustBeMarked(content, options) { + contentMustBeMarked = content; + contentParserOptions = options; +} + +/** + * Register a hook for `Prism` to mark errors in ESLint. + * @returns {void} + */ +function installPrismESLintMarkerHook() { + + /** + * A token type for marking the range reported by a rule. + * This is also used for the `class` attribute of ``. + */ + const TOKEN_TYPE_ESLINT_MARKED = "eslint-marked"; + + /** + * Use in the class attribute of `` when an error is displayed in the BOM code or empty string. + */ + const CLASS_ESLINT_MARKED_ON_ZERO_WIDTH = "eslint-marked-on-zero-width"; + + /** + * Use in the class attribute of `` when an error is displayed in the line-feed. + */ + const CLASS_ESLINT_MARKED_ON_LINE_FEED = "eslint-marked-on-line-feed"; + + /** + * A Map that holds message IDs and messages. + * @type {Map} + */ + const messageMap = new Map(); + + /** + * Gets the message ID from the given message. + * @param {string} message Message + * @returns {string} Message ID + */ + function getMessageIdFromMessage(message) { + let messageId; + + for (const [key, value] of messageMap.entries()) { + if (value === message) { + messageId = key; + break; + } + } + if (!messageId) { + messageId = `eslint-message-id-${messageMap.size + 1}`; + messageMap.set(messageId, message); + } + return messageId; + } + + + const linter = new Linter({ configType: "flat" }); + + Prism.hooks.add("after-tokenize", env => { + messageMap.clear(); + + if (contentMustBeMarked !== env.code) { + + // Ignore unmarked content. + return; + } + contentMustBeMarked = void 0; + const parserOptions = contentParserOptions; + + const code = env.code; + + /** Copied from SourceCode constructor */ + const lineStartIndices = [0]; + const lineEndingPattern = astUtils.createGlobalLinebreakMatcher(); + let match; + + while ((match = lineEndingPattern.exec(code))) { + lineStartIndices.push(match.index + match[0].length); + } + + /** + * Converts a (line, column) pair into a range index. + * @param {Object} loc A line/column location + * @param {number} loc.line The line number of the location (1-indexed) + * @param {number} loc.column The column number of the location (1-indexed) + * @returns {number} The range index of the location in the file. + * Copied from SourceCode#getIndexFromLoc + */ + function getIndexFromLoc(loc) { + const lineStartIndex = lineStartIndices[loc.line - 1]; + const positionIndex = lineStartIndex + loc.column - 1; + + return positionIndex; + } + + /* + * Run lint to extract the error range. + */ + const lintMessages = linter.verify( + + // Remove trailing newline and presentational `⏎` characters + docsExampleCodeToParsableCode(code), + { languageOptions: { sourceType: parserOptions.sourceType, parserOptions } }, + { filename: "code.js" } + ); + + if (lintMessages.some(m => m.fatal)) { + + // ESLint fatal error. + return; + } + const messages = lintMessages.map(message => { + const start = getIndexFromLoc({ + line: message.line, + column: message.column + }); + + return { + message: message.message, + range: [ + start, + typeof message.endLine === "undefined" + ? start + : getIndexFromLoc({ + line: message.endLine, + column: message.endColumn + }) + ] + }; + }); + + /** + * Get the content of the token. + * @param {string | Prism.Token} token The token + * @returns {string} The content of the token + */ + function getTokenContent(token) { + if (typeof token === "string") { + return token; + } + if (typeof token.content === "string") { + return token.content; + } + return [token.content].flat().map(getTokenContent).join(""); + } + + /** + * @typedef {Object} SplitTokenResult + * @property {string | Prism.Token | null} before The token before the marked range + * @property {Object} marked The marked token information + * @property {Prism.Token} marked.token The token with the marked range + * @property {boolean} marked.canBeMerged If true, it can be merged with previous and subsequent marked tokens. + * @property {string | Prism.Token | null} after The token after the marked range + */ + + /** + * Splits the given token into the `eslint-marked` token and the token before and after it with the specified range. + * @param {Object} params Parameters + * @param {string | Prism.Token} params.token Token to be split + * @param {[number, number]} params.range Range to be marked + * @param {string} params.message Report message + * @param {number} params.tokenStart Starting position of the token + * @returns {SplitTokenResult} Splitted tokens + */ + function splitToken({ token, range, message, tokenStart }) { + + const content = getTokenContent(token); + const tokenEnd = tokenStart + content.length; + + if (range[0] <= tokenStart && tokenEnd <= range[1]) { + + // The token is in range. + const marked = new Prism.Token( + TOKEN_TYPE_ESLINT_MARKED, + [token], + [getMessageIdFromMessage(message)] + ); + + return { before: null, marked: { token: marked, canBeMerged: true }, after: null }; + } + + let buildToken; + + if (typeof token === "string") { + buildToken = newContent => newContent; + } else { + if (typeof token.content !== "string") { + if (token.content.every(childContent => typeof childContent === "string")) { + + // It can be flatten. + buildToken = newContent => new Prism.Token(token.type, newContent, token.alias); + } else { + // eslint-disable-next-line no-use-before-define -- Safe + token.content = [...convertMarked({ tokens: token.content, range, message, tokenStart })]; + return { before: null, marked: { token, canBeMerged: false }, after: null }; + } + } else { + buildToken = newContent => new Prism.Token(token.type, newContent, token.alias); + } + } + + const before = tokenStart < range[0] ? buildToken(content.slice(0, range[0] - tokenStart)) : null; + const mark = content.slice(before ? range[0] - tokenStart : 0, range[1] - tokenStart); + const marked = new Prism.Token( + TOKEN_TYPE_ESLINT_MARKED, + mark ? [buildToken(mark)] : mark, + [getMessageIdFromMessage(message)] + ); + const after = range[1] - tokenStart < content.length ? buildToken(content.slice(range[1] - tokenStart)) : null; + + return { before, marked: { token: marked, canBeMerged: true }, after }; + } + + /** + * Splits the given ESLint marked tokens with line feed code. + * The line feed code is not displayed because there is no character width, + * so by making it a single character token, the "wrap hook" applies CLASS_ESLINT_MARKED_ON_LINE_FEED + * to each character and makes it visible. + * @param {Prism.Token} token Token to be split + * @returns {IterableIterator} Splitted tokens + */ + function *splitMarkedTokenByLineFeed(token) { + for (const contentToken of [token.content].flat()) { + if (typeof contentToken !== "string") { + const content = getTokenContent(contentToken); + + if (/[\r\n]/u.test(content)) { + yield new Prism.Token(token.type, [...splitMarkedTokenByLineFeed(contentToken)], token.alias); + } else { + yield new Prism.Token(token.type, [contentToken], token.alias); + } + continue; + } + if (!/[\r\n]/u.test(contentToken)) { + yield new Prism.Token(token.type, [contentToken], token.alias); + continue; + } + const contents = []; + const reLineFeed = /\r\n?|\n/ug; + let startIndex = 0; + let matchLineFeed; + + while ((matchLineFeed = reLineFeed.exec(contentToken))) { + contents.push(contentToken.slice(startIndex, matchLineFeed.index)); + contents.push(matchLineFeed[0]); + startIndex = reLineFeed.lastIndex; + } + contents.push(contentToken.slice(startIndex)); + yield* contents.filter(Boolean).map(str => new Prism.Token(token.type, [str], token.alias)); + } + } + + /** + * Splits the given tokens by line feed code. + * @param {Prism.Token[]} tokens Token to be Separate + * @returns {IterableIterator} Splitted tokens + */ + function *splitTokensByLineFeed(tokens) { + for (const token of tokens) { + + const content = getTokenContent(token); + + if (!/[\r\n]/u.test(content)) { + yield token; + continue; + } + if (token.type === TOKEN_TYPE_ESLINT_MARKED) { + yield* splitMarkedTokenByLineFeed(token); + continue; + } + + if (Array.isArray(token.content) || typeof token.content !== "string") { + token.content = [...splitTokensByLineFeed([token.content].flat())]; + } + yield token; + } + } + + /** + * Generates a token stream with the `eslint-marked` class assigned to the error range. + * @param {Object} params Parameters + * @param {string | Prism.Token | (string | Prism.Token[])} params.tokens Tokens to be converted + * @param {[number, number]} params.range Range to be marked + * @param {string} params.message Report message + * @param {number} params.tokenStart Starting position of the tokens + * @returns {IterableIterator} converted tokens + */ + function *convertMarked({ tokens, range, message, tokenStart = 0 }) { + let start = tokenStart; + + const buffer = [tokens].flat(); + + let token; + + // Find the first token to mark + while ((token = buffer.shift())) { + const content = getTokenContent(token); + const end = start + content.length; + + if (!content || end <= range[0]) { + yield token; + start = end; + continue; + } + + break; + } + if (!token) { + return; + } + + // Mark the token. + const { before, marked, after } = splitToken({ token, range, message, tokenStart: start }); + + if (before) { + yield before; + } + if (after) { + yield* splitTokensByLineFeed([marked.token]); + yield after; + } else { + + // Subsequent tokens may still be present in the range. + let nextTokenStartIndex = start + getTokenContent(token).length; + let prevMarked = marked; + let nextAfter; + + while (nextTokenStartIndex < range[1] && buffer.length) { + const nextToken = buffer.shift(); + const next = splitToken({ token: nextToken, range, message, tokenStart: nextTokenStartIndex }); + + if (prevMarked.canBeMerged && next.marked.canBeMerged) { + prevMarked.token.content.push(...next.marked.token.content); + } else { + yield* splitTokensByLineFeed([prevMarked.token]); + prevMarked = next.marked; + } + if (next.after) { + nextAfter = next.after; + } + nextTokenStartIndex += getTokenContent(nextToken).length; + } + + yield* splitTokensByLineFeed([prevMarked.token]); + if (nextAfter) { + yield nextAfter; + } + } + + yield* buffer; + } + + for (const { range, message } of messages) { + env.tokens = [...convertMarked({ tokens: env.tokens, range, message })]; + } + }); + + Prism.hooks.add("wrap", env => { + if (env.type === TOKEN_TYPE_ESLINT_MARKED) { + const messageId = env.classes.find(c => messageMap.has(c)); + + if (messageId) { + env.attributes.title = messageMap.get(messageId); + } + + if ( + env.content === "" || + env.content === "\ufeff" + ) { + env.classes.push(CLASS_ESLINT_MARKED_ON_ZERO_WIDTH); + } else if ( + env.content === "\n" || + env.content === "\r" || + env.content === "\r\n" + ) { + env.classes.push(CLASS_ESLINT_MARKED_ON_LINE_FEED); + } + } + }); +} + + +module.exports = { + installPrismESLintMarkerHook: isAvailable ? installPrismESLintMarkerHook : () => { /* noop */ }, + addContentMustBeMarked: isAvailable ? addContentMustBeMarked : () => { /* noop */ } +}; From 1c173dc1f3d36a28cb2543e93675c2fbdb6fa9f1 Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:05:22 +0530 Subject: [PATCH 015/166] feat: add `ignoreClassWithStaticInitBlock` option to `no-unused-vars` (#18170) * feat: add option to ignore SIB-classes * add docs and tests * add test * update docs --- docs/src/rules/no-unused-vars.md | 45 ++++++++++++++++++++++++++ lib/rules/no-unused-vars.js | 15 ++++++++- tests/lib/rules/no-unused-vars.js | 54 +++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/docs/src/rules/no-unused-vars.md b/docs/src/rules/no-unused-vars.md index d924f02d657..cc361e955e4 100644 --- a/docs/src/rules/no-unused-vars.md +++ b/docs/src/rules/no-unused-vars.md @@ -410,6 +410,51 @@ var bar; ::: +### ignoreClassWithStaticInitBlock + +The `ignoreClassWithStaticInitBlock` option is a boolean (default: `false`). Static initialization blocks allow you to initialize static variables and execute code during the evaluation of a class definition, meaning the static block code is executed without creating a new instance of the class. When set to `true`, this option ignores classes containing static initialization blocks. + +Examples of **incorrect** code for the `{ "ignoreClassWithStaticInitBlock": true }` option + +::: incorrect + +```js +/*eslint no-unused-vars: ["error", { "ignoreClassWithStaticInitBlock": true }]*/ + +class Foo { + static myProperty = "some string"; + static mymethod() { + return "some string"; + } +} + +class Bar { + static { + let baz; // unused variable + } +} +``` + +::: + +Examples of **correct** code for the `{ "ignoreClassWithStaticInitBlock": true }` option + +::: correct + +```js +/*eslint no-unused-vars: ["error", { "ignoreClassWithStaticInitBlock": true }]*/ + +class Foo { + static { + let bar = "some string"; + + console.log(bar); + } +} +``` + +::: + ## When Not To Use It If you don't want to be notified about unused variables or function arguments, you can safely turn this rule off. diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js index 74a664089ed..90b76e6f2d1 100644 --- a/lib/rules/no-unused-vars.js +++ b/lib/rules/no-unused-vars.js @@ -70,6 +70,9 @@ module.exports = { }, destructuredArrayIgnorePattern: { type: "string" + }, + ignoreClassWithStaticInitBlock: { + type: "boolean" } }, additionalProperties: false @@ -92,7 +95,8 @@ module.exports = { vars: "all", args: "after-used", ignoreRestSiblings: false, - caughtErrors: "all" + caughtErrors: "all", + ignoreClassWithStaticInitBlock: false }; const firstOption = context.options[0]; @@ -105,6 +109,7 @@ 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; if (firstOption.varsIgnorePattern) { config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern, "u"); @@ -613,6 +618,14 @@ module.exports = { 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") { diff --git a/tests/lib/rules/no-unused-vars.js b/tests/lib/rules/no-unused-vars.js index 0d8e7c33b89..de85050248f 100644 --- a/tests/lib/rules/no-unused-vars.js +++ b/tests/lib/rules/no-unused-vars.js @@ -445,6 +445,23 @@ ruleTester.run("no-unused-vars", rule, { { code: "var a; a ??= 1;", languageOptions: { ecmaVersion: 2021 } + }, + + // ignore class with static initialization block https://github.com/eslint/eslint/issues/17772 + { + code: "class Foo { static {} }", + options: [{ ignoreClassWithStaticInitBlock: true }], + languageOptions: { ecmaVersion: 2022 } + }, + { + code: "class Foo { static {} }", + options: [{ ignoreClassWithStaticInitBlock: true, varsIgnorePattern: "^_" }], + languageOptions: { ecmaVersion: 2022 } + }, + { + code: "class Foo { static {} }", + options: [{ ignoreClassWithStaticInitBlock: false, varsIgnorePattern: "^Foo" }], + languageOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -1557,6 +1574,43 @@ function foo1() { c = foo1`, languageOptions: { ecmaVersion: 2020 }, errors: [{ ...assignedError("c"), line: 10, column: 1 }] + }, + + // ignore class with static initialization block https://github.com/eslint/eslint/issues/17772 + { + code: "class Foo { static {} }", + options: [{ ignoreClassWithStaticInitBlock: false }], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ ...definedError("Foo"), line: 1, column: 7 }] + }, + { + code: "class Foo { static {} }", + languageOptions: { ecmaVersion: 2022 }, + errors: [{ ...definedError("Foo"), line: 1, column: 7 }] + }, + { + code: "class Foo { static { var bar; } }", + options: [{ ignoreClassWithStaticInitBlock: true }], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ ...definedError("bar"), line: 1, column: 26 }] + }, + { + code: "class Foo {}", + options: [{ ignoreClassWithStaticInitBlock: true }], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ ...definedError("Foo"), line: 1, column: 7 }] + }, + { + code: "class Foo { static bar; }", + options: [{ ignoreClassWithStaticInitBlock: true }], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ ...definedError("Foo"), line: 1, column: 7 }] + }, + { + code: "class Foo { static bar() {} }", + options: [{ ignoreClassWithStaticInitBlock: true }], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ ...definedError("Foo"), line: 1, column: 7 }] } ] }); From 925afa2b0c882f77f6b4411bdca3cb8ad6934b56 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Thu, 7 Mar 2024 18:23:27 +0100 Subject: [PATCH 016/166] chore: Remove some uses of `lodash.merge` (#18179) --- eslint.config.js | 53 +++++++++++++++--------------- tests/lib/rules/no-invalid-this.js | 8 ++--- 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 676fc87f226..5d332e6edfc 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -30,7 +30,6 @@ const internalPlugin = require("eslint-plugin-internal-rules"); const eslintPluginRulesRecommendedConfig = require("eslint-plugin-eslint-plugin/configs/rules-recommended"); const eslintPluginTestsRecommendedConfig = require("eslint-plugin-eslint-plugin/configs/tests-recommended"); const globals = require("globals"); -const merge = require("lodash.merge"); const eslintConfigESLintCJS = require("eslint-config-eslint/cjs"); //----------------------------------------------------------------------------- @@ -114,15 +113,15 @@ module.exports = [ { files: ["lib/rules/*", "tools/internal-rules/*"], ignores: ["**/index.js"], - ...merge({}, eslintPluginRulesRecommendedConfig, { - rules: { - "eslint-plugin/prefer-placeholders": "error", - "eslint-plugin/prefer-replace-text": "error", - "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], - "eslint-plugin/require-meta-docs-description": ["error", { pattern: "^(Enforce|Require|Disallow) .+[^. ]$" }], - "internal-rules/no-invalid-meta": "error" - } - }) + ...eslintPluginRulesRecommendedConfig, + rules: { + ...eslintPluginRulesRecommendedConfig.rules, + "eslint-plugin/prefer-placeholders": "error", + "eslint-plugin/prefer-replace-text": "error", + "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], + "eslint-plugin/require-meta-docs-description": ["error", { pattern: "^(Enforce|Require|Disallow) .+[^. ]$" }], + "internal-rules/no-invalid-meta": "error" + } }, { files: ["lib/rules/*"], @@ -133,23 +132,23 @@ module.exports = [ }, { files: ["tests/lib/rules/*", "tests/tools/internal-rules/*"], - ...merge({}, eslintPluginTestsRecommendedConfig, { - rules: { - "eslint-plugin/test-case-property-ordering": [ - "error", - [ - "name", - "filename", - "code", - "output", - "options", - "languageOptions", - "errors" - ] - ], - "eslint-plugin/test-case-shorthand-strings": "error" - } - }) + ...eslintPluginTestsRecommendedConfig, + rules: { + ...eslintPluginTestsRecommendedConfig.rules, + "eslint-plugin/test-case-property-ordering": [ + "error", + [ + "name", + "filename", + "code", + "output", + "options", + "languageOptions", + "errors" + ] + ], + "eslint-plugin/test-case-shorthand-strings": "error" + } }, { files: ["tests/**/*.js"], diff --git a/tests/lib/rules/no-invalid-this.js b/tests/lib/rules/no-invalid-this.js index 11e88f12437..8dd347030e2 100644 --- a/tests/lib/rules/no-invalid-this.js +++ b/tests/lib/rules/no-invalid-this.js @@ -9,8 +9,6 @@ // Requirements //------------------------------------------------------------------------------ -const merge = require("lodash.merge"); - const rule = require("../../../lib/rules/no-invalid-this"); const RuleTester = require("../../../lib/rule-tester/rule-tester"); @@ -72,7 +70,8 @@ function extractPatterns(patterns, type) { // Clone and apply the pattern environment. const patternsList = patterns.map(pattern => pattern[type].map(applyCondition => { - const thisPattern = merge({}, pattern); + const { valid, invalid, ...rest } = pattern; // eslint-disable-line no-unused-vars -- `valid` and `invalid` are used just to exclude properties + const thisPattern = structuredClone(rest); applyCondition(thisPattern); @@ -82,9 +81,6 @@ function extractPatterns(patterns, type) { thisPattern.code += " /* should error */"; } - delete thisPattern.valid; - delete thisPattern.invalid; - return thisPattern; })); From c7abd8936193a87be274174c47d6775e6220e354 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 7 Mar 2024 13:25:40 -0700 Subject: [PATCH 017/166] docs: Explain Node.js version support (#18176) * docs: Explain Node.js version support fixes #11022 * Update README.md Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index f70e809afc2..5c25bbbe592 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,18 @@ In other cases (including if rules need to warn on more or fewer cases due to ne Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the [TC39 process](https://tc39.github.io/process-document/)), we will accept issues and pull requests related to the new feature, subject to our [contributing guidelines](https://eslint.org/docs/latest/contribute). Until then, please use the appropriate parser and plugin(s) for your experimental feature. +### Which Node.js versions does ESLint support? + +ESLint updates the supported Node.js versions with each major release of ESLint. At that time, ESLint's supported Node.js versions are updated to be: + +1. The most recent maintenance release of Node.js +1. The lowest minor version of the Node.js LTS release that includes the features the ESLint team wants to use. +1. The Node.js Current release + +ESLint is also expected to work with Node.js versions released after the Node.js Current release. + +Refer to the [Quick Start Guide](https://eslint.org/docs/latest/use/getting-started#prerequisites) for the officially supported Node.js versions for a given ESLint release. + ### Where to ask for help? Open a [discussion](https://github.com/eslint/eslint/discussions) or stop by our [Discord server](https://eslint.org/chat). From 337cdf9f7ad939df7bc55c23d953e12d847b6ecc Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 7 Mar 2024 13:41:18 -0700 Subject: [PATCH 018/166] docs: Explain limitations of RuleTester fix testing (#18175) * docs: Explain limitations of RuleTester fix testing fixes #18007 * Update docs/src/integrate/nodejs-api.md Co-authored-by: Milos Djermanovic * Update docs/src/integrate/nodejs-api.md Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- docs/src/integrate/nodejs-api.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/src/integrate/nodejs-api.md b/docs/src/integrate/nodejs-api.md index cc01388f852..fdc06daf12f 100644 --- a/docs/src/integrate/nodejs-api.md +++ b/docs/src/integrate/nodejs-api.md @@ -803,7 +803,7 @@ Any additional properties of a test case will be passed directly to the linter a If a valid test case only uses the `code` property, it can optionally be provided as a string containing the code, rather than an object with a `code` key. -### Testing errors with `messageId` +### Testing Errors with `messageId` If the rule under test uses `messageId`s, you can use `messageId` property in a test case to assert reported error's `messageId` instead of its `message`. @@ -825,6 +825,31 @@ For messages with placeholders, a test case can also use `data` property to addi Please note that `data` in a test case does not assert `data` passed to `context.report`. Instead, it is used to form the expected message text which is then compared with the received `message`. +### Testing Fixes + +The result of applying fixes can be tested by using the `output` property of an invalid test case. The `output` property should be used only when you expect a fix to be applied to the specified `code`; you can safely omit `output` if no changes are expected to the code. Here's an example: + +```js +ruleTester.run("my-rule-for-no-foo", rule, { + valid: [], + invalid: [{ + code: "var foo;", + output: "var bar;", + errors: [{ + messageId: "shouldBeBar", + line: 1, + column: 5 + }] + }] +}) +``` + +A the end of this invalid test case, `RuleTester` expects a fix to be applied that results in the code changing from `var foo;` to `var bar;`. If the output after applying the fix doesn't match, then the test fails. + +::: important +ESLint makes its best attempt at applying all fixes, but there is no guarantee that all fixes will be applied. As such, you should aim for testing each type of fix in a separate `RuleTester` test case rather than one test case to test multiple fixes. When there is a conflict between two fixes (because they apply to the same section of code) `RuleTester` applies only the first fix. +::: + ### Testing Suggestions Suggestions can be tested by defining a `suggestions` key on an errors object. If this is a number, it asserts the number of suggestions provided for the error. Otherwise, this should be an array of objects, each containing information about a single provided suggestion. The following properties can be used: From ba1c1bbc6ba9d57a83d04f450566337d3c3b0448 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Fri, 8 Mar 2024 08:06:28 +0000 Subject: [PATCH 019/166] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c25bbbe592..695369711f7 100644 --- a/README.md +++ b/README.md @@ -305,7 +305,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

-

JetBrains Liftoff Workleap

Bronze Sponsors

+

JetBrains Liftoff American Express Workleap

Bronze Sponsors

notion ThemeIsle Anagram Solver Icons8 Discord Transloadit Ignition Nx HeroCoders Nextbase Starter Kit

From 96087b33dc10311bba83e22cc968919c358a0188 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 8 Mar 2024 20:45:31 +0000 Subject: [PATCH 020/166] chore: package.json update for @eslint/js release --- packages/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/package.json b/packages/js/package.json index 330a3e6b49f..12ef8235578 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "9.0.0-beta.1", + "version": "9.0.0-beta.2", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 75092764db117252067558bd3fbbf0c66ac081b7 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 8 Mar 2024 22:00:04 +0100 Subject: [PATCH 021/166] chore: upgrade @eslint/js@9.0.0-beta.2 (#18180) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e1c9f3ebb3a..2d4aa25bb01 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^3.0.2", - "@eslint/js": "9.0.0-beta.1", + "@eslint/js": "9.0.0-beta.2", "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", From d7ec0d1fbdbafa139d090ffd8b42d33bd4aa46f8 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 8 Mar 2024 21:18:50 +0000 Subject: [PATCH 022/166] Build: changelog update for 9.0.0-beta.2 --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3851ec24140..caaddda49de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +v9.0.0-beta.2 - March 8, 2024 + +* [`7509276`](https://github.com/eslint/eslint/commit/75092764db117252067558bd3fbbf0c66ac081b7) chore: upgrade @eslint/js@9.0.0-beta.2 (#18180) (Milos Djermanovic) +* [`96087b3`](https://github.com/eslint/eslint/commit/96087b33dc10311bba83e22cc968919c358a0188) chore: package.json update for @eslint/js release (Jenkins) +* [`ba1c1bb`](https://github.com/eslint/eslint/commit/ba1c1bbc6ba9d57a83d04f450566337d3c3b0448) docs: Update README (GitHub Actions Bot) +* [`337cdf9`](https://github.com/eslint/eslint/commit/337cdf9f7ad939df7bc55c23d953e12d847b6ecc) docs: Explain limitations of RuleTester fix testing (#18175) (Nicholas C. Zakas) +* [`c7abd89`](https://github.com/eslint/eslint/commit/c7abd8936193a87be274174c47d6775e6220e354) docs: Explain Node.js version support (#18176) (Nicholas C. Zakas) +* [`925afa2`](https://github.com/eslint/eslint/commit/925afa2b0c882f77f6b4411bdca3cb8ad6934b56) chore: Remove some uses of `lodash.merge` (#18179) (Milos Djermanovic) +* [`1c173dc`](https://github.com/eslint/eslint/commit/1c173dc1f3d36a28cb2543e93675c2fbdb6fa9f1) feat: add `ignoreClassWithStaticInitBlock` option to `no-unused-vars` (#18170) (Tanuj Kanti) +* [`d961eeb`](https://github.com/eslint/eslint/commit/d961eeb855b6dd9118a78165e358e454eb1d090d) docs: show red underlines in examples in rules docs (#18041) (Yosuke Ota) +* [`558274a`](https://github.com/eslint/eslint/commit/558274abbd25ef269f4994cf258b2e44afbad548) docs: Update README (GitHub Actions Bot) +* [`2908b9b`](https://github.com/eslint/eslint/commit/2908b9b96ab7a25fe8044a1755030b18186a75b0) docs: Update release documentation (#18174) (Nicholas C. Zakas) +* [`a451b32`](https://github.com/eslint/eslint/commit/a451b32b33535a57b4b7e24291f30760f65460ba) feat: make `no-misleading-character-class` report more granular errors (#18082) (Francesco Trotta) +* [`972ef15`](https://github.com/eslint/eslint/commit/972ef155a94ad2cc85db7d209ad869869222c14c) chore: remove invalid type in @eslint/js (#18164) (Nitin Kumar) +* [`1f1260e`](https://github.com/eslint/eslint/commit/1f1260e863f53e2a5891163485a67c55d41993aa) docs: replace HackerOne link with GitHub advisory (#18165) (Francesco Trotta) +* [`79a95eb`](https://github.com/eslint/eslint/commit/79a95eb7da7fe657b6448c225d4f8ac31117456a) feat!: disallow multiple configuration comments for same rule (#18157) (Milos Djermanovic) +* [`e37153f`](https://github.com/eslint/eslint/commit/e37153f71f173e8667273d6298bef81e0d33f9ba) fix: improve error message for invalid rule config (#18147) (Nitin Kumar) +* [`c49ed63`](https://github.com/eslint/eslint/commit/c49ed63265fc8e0cccea404810a4c5075d396a15) feat: update complexity rule for optional chaining & default values (#18152) (Mathias Schreck) +* [`e5ef3cd`](https://github.com/eslint/eslint/commit/e5ef3cd6953bb40108556e0465653898ffed8420) docs: add inline cases condition in `no-fallthrough` (#18158) (Tanuj Kanti) +* [`af6e170`](https://github.com/eslint/eslint/commit/af6e17081fa6c343474959712e7a4a20f8b304e2) fix: stop linting files after an error (#18155) (Francesco Trotta) +* [`450d0f0`](https://github.com/eslint/eslint/commit/450d0f044023843b1790bd497dfca45dcbdb41e4) docs: fix `ignore` option docs (#18154) (Francesco Trotta) +* [`11144a2`](https://github.com/eslint/eslint/commit/11144a2671b2404b293f656be111221557f3390f) feat: `no-restricted-imports` option added `allowImportNames` (#16196) (M Pater) + v9.0.0-beta.1 - February 23, 2024 * [`32ffdd1`](https://github.com/eslint/eslint/commit/32ffdd181aa673ccc596f714d10a2f879ec622a7) chore: upgrade @eslint/js@9.0.0-beta.1 (#18146) (Milos Djermanovic) From ba89c73261f7fd1b6cdd50cfaeb8f4ce36101757 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 8 Mar 2024 21:18:51 +0000 Subject: [PATCH 023/166] 9.0.0-beta.2 --- docs/package.json | 2 +- docs/src/use/formatters/html-formatter-example.html | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/package.json b/docs/package.json index 5aa7188e18c..bc44a1bc19b 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "9.0.0-beta.1", + "version": "9.0.0-beta.2", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index b15b574d70a..520b2bfc78c 100644 --- a/docs/src/use/formatters/html-formatter-example.html +++ b/docs/src/use/formatters/html-formatter-example.html @@ -118,7 +118,7 @@

ESLint Report

- 8 problems (4 errors, 4 warnings) - Generated on Fri Feb 23 2024 22:02:44 GMT+0000 (Coordinated Universal Time) + 8 problems (4 errors, 4 warnings) - Generated on Fri Mar 08 2024 21:18:52 GMT+0000 (Coordinated Universal Time)
diff --git a/package.json b/package.json index 2d4aa25bb01..112c8e38c13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "9.0.0-beta.1", + "version": "9.0.0-beta.2", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From acc2e06edd55eaab58530d891c0a572c1f0ec453 Mon Sep 17 00:00:00 2001 From: Lars Kappert Date: Mon, 11 Mar 2024 17:25:24 +0100 Subject: [PATCH 024/166] chore: Introduce Knip (#18005) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: add Knip + config * chore: use named exports consistently (only shorthands) * chore: update Knip & reduce config (by improved 11ty plugin) * chore: rename `lint:knip` → `lint:imports` Co-authored-by: Nicholas C. Zakas * chore: upgrade Knip to v4.3.0 * chore: fix createCoreRuleConfigs import in test * chore: annotate default export for runtime-info (to satisfy both proxyquire and knip) * chore: update knip.jsonc Co-authored-by: Josh Goldberg ✨ * chore: add comment to export/import hint * chore: upgrade Knip to v5.0.1 (no breaking changes) * chore: remove unused files, dependencies & exports * chore: fix whitespace in ci.yml * chore: add Knip to CI job * Remove tools/update-rule-types.js from knip.jsonc Co-authored-by: Milos Djermanovic * Add corrected shared/types Rule import to lazy-loading-rule-map.js * Added back eslint-plugin-* devDependencies * Rename to lint:unused * Fix introduced prism-eslint-hook complaints --------- Co-authored-by: Nicholas C. Zakas Co-authored-by: Josh Goldberg ✨ Co-authored-by: Milos Djermanovic --- .github/workflows/ci.yml | 100 +++++++++++---------- Makefile.js | 4 +- bin/eslint.js | 3 +- docs/.eleventy.js | 4 +- docs/package.json | 5 -- docs/src/_plugins/md-syntax-highlighter.js | 4 +- knip.jsonc | 52 +++++++++++ lib/cli-engine/xml-escape.js | 34 ------- lib/config/flat-config-schema.js | 3 +- lib/eslint/eslint-helpers.js | 1 - lib/linter/index.js | 4 +- lib/linter/linter.js | 2 - lib/rule-tester/index.js | 4 +- lib/rules/utils/lazy-loading-rule-map.js | 2 +- lib/rules/utils/unicode/index.js | 13 ++- lib/shared/deprecation-warnings.js | 58 ------------ lib/shared/runtime-info.js | 1 + lib/source-code/index.js | 4 +- package.json | 9 +- tests/_utils/in-memory-fs.js | 69 -------------- tests/_utils/index.js | 5 -- tests/tools/config-rule.js | 32 +++---- tools/internal-testers/test-parser.js | 48 ---------- 23 files changed, 153 insertions(+), 308 deletions(-) create mode 100644 knip.jsonc delete mode 100644 lib/cli-engine/xml-escape.js delete mode 100644 lib/shared/deprecation-warnings.js delete mode 100644 tests/_utils/in-memory-fs.js delete mode 100644 tools/internal-testers/test-parser.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b40e8d72aa5..555b73a3b44 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,32 +13,34 @@ jobs: name: Verify Files runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - - name: Install Packages - run: npm install --force + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "lts/*" + - name: Install Packages + run: npm install --force - - name: Install Docs Packages - working-directory: docs - run: npm install + - name: Install Docs Packages + working-directory: docs + run: npm install - - name: Lint Files - run: node Makefile lint + - name: Lint Files + run: node Makefile lint - - name: Check Rule Files - run: node Makefile checkRuleFiles + - name: Check Rule Files + run: node Makefile checkRuleFiles - - name: Check Licenses - run: node Makefile checkLicenses + - name: Check Licenses + run: node Makefile checkLicenses - - name: Lint Docs JS Files - run: node Makefile lintDocsJS + - name: Lint Docs JS Files + run: node Makefile lintDocsJS - - name: Check Rule Examples - run: node Makefile checkRuleExamples + - name: Check Rule Examples + run: node Makefile checkRuleExamples + - name: Lint Files, Dependencies, & Exports + run: npm run lint:unused test_on_node: name: Test @@ -47,40 +49,40 @@ jobs: os: [ubuntu-latest] node: [21.x, 20.x, 18.x, "18.18.0"] include: - - os: windows-latest - node: "lts/*" - - os: macOS-latest - node: "lts/*" + - os: windows-latest + node: "lts/*" + - os: macOS-latest + node: "lts/*" runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node }} - - name: Install Packages - run: npm install --force - - name: Test - run: node Makefile mocha - - name: Fuzz Test - run: node Makefile fuzz + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + - name: Install Packages + run: npm install --force + - name: Test + run: node Makefile mocha + - name: Fuzz Test + run: node Makefile fuzz test_on_browser: name: Browser Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: '20' # Should be the same as the version used on Netlify to build the ESLint Playground - - name: Install Packages - run: npm install --force - - name: Test - run: node Makefile wdio - - name: Fuzz Test - run: node Makefile fuzz - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: logs - path: | - wdio-logs/*.log + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" # Should be the same as the version used on Netlify to build the ESLint Playground + - name: Install Packages + run: npm install --force + - name: Test + run: node Makefile wdio + - name: Fuzz Test + run: node Makefile fuzz + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: logs + path: | + wdio-logs/*.log diff --git a/Makefile.js b/Makefile.js index ab4d15c1b94..36cd99348e4 100644 --- a/Makefile.js +++ b/Makefile.js @@ -532,8 +532,8 @@ target.lintDocsJS = function([fix = false] = []) { }; target.fuzz = function({ amount = 1000, fuzzBrokenAutofixes = false } = {}) { - const fuzzerRunner = require("./tools/fuzzer-runner"); - const fuzzResults = fuzzerRunner.run({ amount, fuzzBrokenAutofixes }); + const { run } = require("./tools/fuzzer-runner"); + const fuzzResults = run({ amount, fuzzBrokenAutofixes }); if (fuzzResults.length) { diff --git a/bin/eslint.js b/bin/eslint.js index eeb4647e70b..46ca98738b2 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -149,7 +149,8 @@ ${getErrorMessage(error)}`; } // 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/docs/.eleventy.js b/docs/.eleventy.js index 5698df45240..29d94d78dfb 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -16,7 +16,7 @@ const { } = require("luxon"); const markdownIt = require("markdown-it"); const markdownItRuleExample = require("./tools/markdown-it-rule-example"); -const { addContentMustBeMarked } = require("./tools/prism-eslint-hook"); +const prismESLintHook = require("./tools/prism-eslint-hook"); module.exports = function(eleventyConfig) { @@ -197,7 +197,7 @@ module.exports = function(eleventyConfig) { const ruleExampleOptions = markdownItRuleExample({ open({ type, code, parserOptions, env, codeBlockToken }) { - addContentMustBeMarked(codeBlockToken.content, parserOptions); + prismESLintHook.addContentMustBeMarked(codeBlockToken.content, parserOptions); const isRuleRemoved = !Object.hasOwn(env.rules_meta, env.title); diff --git a/docs/package.json b/docs/package.json index bc44a1bc19b..d976a151996 100644 --- a/docs/package.json +++ b/docs/package.json @@ -35,20 +35,15 @@ "autoprefixer": "^10.4.13", "cross-env": "^7.0.3", "cssnano": "^5.1.14", - "dom-parser": "^0.1.6", "eleventy-plugin-nesting-toc": "^1.3.0", - "eleventy-plugin-page-assets": "^0.3.0", - "eleventy-plugin-reading-time": "^0.0.1", "github-slugger": "^1.5.0", "hyperlink": "^5.0.4", - "imagemin": "^8.0.1", "imagemin-cli": "^7.0.0", "js-yaml": "^3.14.1", "luxon": "^2.4.0", "markdown-it": "^12.2.0", "markdown-it-anchor": "^8.1.2", "markdown-it-container": "^3.0.0", - "netlify-cli": "^10.3.1", "npm-run-all2": "^5.0.0", "postcss-cli": "^10.0.0", "postcss-html": "^1.5.0", diff --git a/docs/src/_plugins/md-syntax-highlighter.js b/docs/src/_plugins/md-syntax-highlighter.js index d645da631fc..c9e2322cbcb 100644 --- a/docs/src/_plugins/md-syntax-highlighter.js +++ b/docs/src/_plugins/md-syntax-highlighter.js @@ -26,7 +26,9 @@ SOFTWARE. const Prism = require("prismjs"); const loadLanguages = require("prismjs/components/"); -require("../../tools/prism-eslint-hook").installPrismESLintMarkerHook(); +const prismESLintHook = require("../../tools/prism-eslint-hook"); + +prismESLintHook.installPrismESLintMarkerHook(); /** * diff --git a/knip.jsonc b/knip.jsonc new file mode 100644 index 00000000000..06a7c3944eb --- /dev/null +++ b/knip.jsonc @@ -0,0 +1,52 @@ +{ + "workspaces": { + ".": { + // These entries are complementary to the ones found in package.json + "entry": [ + "lib/rules/index.js", + "tools/internal-rules/index.js", + // https://github.com/webpro/knip/issues/464 + // Remove when Knip has a wdio plugin + "wdio.conf.js" + ], + "project": ["{conf,lib,tools}/**/*.js"], + "mocha": { + "entry": [ + "tests/{bin,conf,lib,tools}/**/*.js", // see Makefile.js + "tests/_utils/test-lazy-loading-rules.js" + ], + "project": ["tests/**/*.js"] + }, + "ignore": [ + // If Knip would consider exports as named, their usage is too dynamic: globals[`es${ecmaVersion}`] + // An alternative is to add `__esModule: true` to the export and we can remove it here from the ignores: + "conf/globals.js", + // These contain unresolved imports and other oddities: + "tests/bench/large.js", + "tests/lib/rule-tester/rule-tester.js", + "tests/performance/jshint.js", + // Many are required using dynamic paths such as `fs.readFileSync(path.join())`: + "tests/fixtures/**" + ], + "ignoreDependencies": [ + "c8", + + // These will be removed in https://github.com/eslint/eslint/pull/18011 + "eslint-plugin-eslint-comments", + "eslint-plugin-jsdoc", + "eslint-plugin-n", + "eslint-plugin-unicorn", + + // Ignore until Knip has a wdio plugin: + "@wdio/*", + "rollup-plugin-node-polyfills" + ] + }, + "docs": { + "ignore": ["src/assets/js/search.js", "_examples/**"] + }, + // Workspaces with default configs: + "packages/*": {}, + "tools/internal-rules": {} + } +} diff --git a/lib/cli-engine/xml-escape.js b/lib/cli-engine/xml-escape.js deleted file mode 100644 index 2e52dbaac02..00000000000 --- a/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/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js index 2af99777df4..ce86229c429 100644 --- a/lib/config/flat-config-schema.js +++ b/lib/config/flat-config-schema.js @@ -588,6 +588,5 @@ const flatConfigSchema = { module.exports = { flatConfigSchema, - assertIsRuleSeverity, - assertIsRuleOptions + assertIsRuleSeverity }; diff --git a/lib/eslint/eslint-helpers.js b/lib/eslint/eslint-helpers.js index 065a9e98c5c..0206177831c 100644 --- a/lib/eslint/eslint-helpers.js +++ b/lib/eslint/eslint-helpers.js @@ -907,7 +907,6 @@ function getCacheFile(cacheFile, cwd) { //----------------------------------------------------------------------------- module.exports = { - isGlobPattern, findFiles, isNonEmptyString, diff --git a/lib/linter/index.js b/lib/linter/index.js index 795a414abf4..9e5397768bb 100644 --- a/lib/linter/index.js +++ b/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/lib/linter/linter.js b/lib/linter/linter.js index 82417034b38..605c8604388 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -58,8 +58,6 @@ const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version"); // 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 */ diff --git a/lib/rule-tester/index.js b/lib/rule-tester/index.js index f52d14027c5..58f67ee4940 100644 --- a/lib/rule-tester/index.js +++ b/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/lib/rules/utils/lazy-loading-rule-map.js b/lib/rules/utils/lazy-loading-rule-map.js index 7f116a2684f..3fa5d83b0e8 100644 --- a/lib/rules/utils/lazy-loading-rule-map.js +++ b/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/lib/rules/utils/unicode/index.js b/lib/rules/utils/unicode/index.js index 02eea502dfe..d35d812e1e9 100644 --- a/lib/rules/utils/unicode/index.js +++ b/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/lib/shared/deprecation-warnings.js b/lib/shared/deprecation-warnings.js deleted file mode 100644 index d09cafb07fe..00000000000 --- a/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/lib/shared/runtime-info.js b/lib/shared/runtime-info.js index b99ad1038f3..e172e5b3572 100644 --- a/lib/shared/runtime-info.js +++ b/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/lib/source-code/index.js b/lib/source-code/index.js index 76e27869f32..1ecfbe470aa 100644 --- a/lib/source-code/index.js +++ b/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/package.json b/package.json index 112c8e38c13..9be1a67532d 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "lint:docs:rule-examples": "node Makefile.js checkRuleExamples", "lint:fix": "node Makefile.js lint -- fix", "lint:fix:docs:js": "node Makefile.js lintDocsJS -- fix", + "lint:unused": "knip", "release:generate:alpha": "node Makefile.js generatePrerelease -- alpha", "release:generate:beta": "node Makefile.js generatePrerelease -- beta", "release:generate:latest": "node Makefile.js generateRelease", @@ -48,7 +49,7 @@ "node tools/fetch-docs-links.js", "git add docs/src/_data/further_reading_links.json" ], - "docs/**/*.svg": "npx svgo -r --multipass" + "docs/**/*.svg": "npx -y svgo -r --multipass" }, "files": [ "LICENSE", @@ -101,6 +102,8 @@ "devDependencies": { "@babel/core": "^7.4.3", "@babel/preset-env": "^7.4.3", + "@types/estree": "^1.0.5", + "@types/node": "^20.11.5", "@wdio/browser-runner": "^8.14.6", "@wdio/cli": "^8.14.6", "@wdio/concise-reporter": "^8.14.0", @@ -131,6 +134,7 @@ "got": "^11.8.3", "gray-matter": "^4.0.3", "js-yaml": "^4.1.0", + "knip": "^5.0.1", "lint-staged": "^11.0.0", "load-perf": "^0.2.0", "markdown-it": "^12.2.0", @@ -138,7 +142,6 @@ "markdownlint": "^0.33.0", "markdownlint-cli": "^0.39.0", "marked": "^4.0.8", - "memfs": "^3.0.1", "metascraper": "^5.25.7", "metascraper-description": "^5.25.7", "metascraper-image": "^5.29.3", @@ -157,8 +160,8 @@ "semver": "^7.5.3", "shelljs": "^0.8.5", "sinon": "^11.0.0", + "typescript": "^5.3.3", "vite-plugin-commonjs": "^0.10.0", - "webdriverio": "^8.14.6", "webpack": "^5.23.0", "webpack-cli": "^4.5.0", "yorkie": "^2.0.0" diff --git a/tests/_utils/in-memory-fs.js b/tests/_utils/in-memory-fs.js deleted file mode 100644 index 8242096dc90..00000000000 --- a/tests/_utils/in-memory-fs.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * @fileoverview in-memory file system. - * @author Toru Nagashima - */ -"use strict"; - -//----------------------------------------------------------------------------- -// Requirements -//----------------------------------------------------------------------------- - -const path = require("path"); -const { Volume, createFsFromVolume } = require("memfs"); - -//----------------------------------------------------------------------------- -// Helpers -//----------------------------------------------------------------------------- - -/** - * Define in-memory file system. - * @param {Object} options The options. - * @param {() => string} [options.cwd] The current working directory. - * @param {Object} [options.files] The initial files definition in the in-memory file system. - * @returns {import("fs")} The stubbed `ConfigArrayFactory` class. - */ -function defineInMemoryFs({ - cwd = process.cwd, - files = {} -} = {}) { - - /** - * The in-memory file system for this mock. - * @type {import("fs")} - */ - const fs = createFsFromVolume(new Volume()); - - fs.mkdirSync(cwd(), { recursive: true }); - - /* - * Write all files to the in-memory file system and compile all JavaScript - * files then set to `stubs`. - */ - (function initFiles(directoryPath, definition) { - for (const [filename, content] of Object.entries(definition)) { - const filePath = path.resolve(directoryPath, filename); - const parentPath = path.dirname(filePath); - - if (typeof content === "object") { - initFiles(filePath, content); - } else if (typeof content === "string") { - if (!fs.existsSync(parentPath)) { - fs.mkdirSync(parentPath, { recursive: true }); - } - fs.writeFileSync(filePath, content); - } else { - throw new Error(`Invalid content: ${typeof content}`); - } - } - }(cwd(), files)); - - return fs; -} - -//----------------------------------------------------------------------------- -// Exports -//----------------------------------------------------------------------------- - -module.exports = { - defineInMemoryFs -}; diff --git a/tests/_utils/index.js b/tests/_utils/index.js index 08ef08ae690..0708a24198d 100644 --- a/tests/_utils/index.js +++ b/tests/_utils/index.js @@ -8,10 +8,6 @@ // Requirements //----------------------------------------------------------------------------- -const { - defineInMemoryFs -} = require("./in-memory-fs"); - const { createTeardown, addFile } = require("fs-teardown"); //----------------------------------------------------------------------------- @@ -58,6 +54,5 @@ function createCustomTeardown({ cwd, files }) { module.exports = { unIndent, - defineInMemoryFs, createCustomTeardown }; diff --git a/tests/tools/config-rule.js b/tests/tools/config-rule.js index 2404a2147ab..8594b3643c9 100644 --- a/tests/tools/config-rule.js +++ b/tests/tools/config-rule.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - ConfigRule = require("../../tools/config-rule"), + { createCoreRuleConfigs, generateConfigsFromSchema } = require("../../tools/config-rule"), builtInRules = require("../../lib/rules"), schema = require("../fixtures/config-rule/schemas"); @@ -26,19 +26,19 @@ describe("ConfigRule", () => { let actualConfigs; it("should create a config with only severity for an empty schema", () => { - actualConfigs = ConfigRule.generateConfigsFromSchema([]); + actualConfigs = generateConfigsFromSchema([]); assert.deepStrictEqual(actualConfigs, [SEVERITY]); }); it("should create a config with only severity with no arguments", () => { - actualConfigs = ConfigRule.generateConfigsFromSchema(); + actualConfigs = generateConfigsFromSchema(); assert.deepStrictEqual(actualConfigs, [SEVERITY]); }); describe("for a single enum schema", () => { before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.enum); + actualConfigs = generateConfigsFromSchema(schema.enum); }); it("should create an array of configs", () => { @@ -66,7 +66,7 @@ describe("ConfigRule", () => { describe("for a object schema with a single enum property", () => { before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.objectWithEnum); + actualConfigs = generateConfigsFromSchema(schema.objectWithEnum); }); it("should return configs with option objects", () => { @@ -106,7 +106,7 @@ describe("ConfigRule", () => { describe("for a object schema with a multiple enum properties", () => { before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.objectWithMultipleEnums); + actualConfigs = generateConfigsFromSchema(schema.objectWithMultipleEnums); }); it("should create configs for all properties in each config", () => { @@ -140,7 +140,7 @@ describe("ConfigRule", () => { describe("for a object schema with a single boolean property", () => { before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.objectWithBool); + actualConfigs = generateConfigsFromSchema(schema.objectWithBool); }); it("should return configs with option objects", () => { @@ -179,7 +179,7 @@ describe("ConfigRule", () => { describe("for a object schema with a multiple bool properties", () => { before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.objectWithMultipleBools); + actualConfigs = generateConfigsFromSchema(schema.objectWithMultipleBools); }); it("should create configs for all properties in each config", () => { @@ -210,7 +210,7 @@ describe("ConfigRule", () => { describe("for a schema with an enum and an object", () => { before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.mixedEnumObject); + actualConfigs = generateConfigsFromSchema(schema.mixedEnumObject); }); it("should create configs with only the enum values", () => { @@ -232,7 +232,7 @@ describe("ConfigRule", () => { describe("for a schema with an enum followed by an object with no usable properties", () => { before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.mixedEnumObjectWithNothing); + actualConfigs = generateConfigsFromSchema(schema.mixedEnumObjectWithNothing); }); it("should create config only for the enum", () => { @@ -244,7 +244,7 @@ describe("ConfigRule", () => { describe("for a schema with an enum preceded by an object with no usable properties", () => { before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.mixedObjectWithNothingEnum); + actualConfigs = generateConfigsFromSchema(schema.mixedObjectWithNothingEnum); }); it("should not create a config for the enum", () => { @@ -256,7 +256,7 @@ describe("ConfigRule", () => { describe("for a schema with an enum preceded by a string", () => { before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.mixedStringEnum); + actualConfigs = generateConfigsFromSchema(schema.mixedStringEnum); }); it("should not create a config for the enum", () => { @@ -269,7 +269,7 @@ describe("ConfigRule", () => { describe("for a schema with oneOf", () => { before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.oneOf); + actualConfigs = generateConfigsFromSchema(schema.oneOf); }); it("should create a set of configs", () => { @@ -280,7 +280,7 @@ describe("ConfigRule", () => { describe("for a schema with nested objects", () => { before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.nestedObjects); + actualConfigs = generateConfigsFromSchema(schema.nestedObjects); }); it("should create a set of configs", () => { @@ -291,7 +291,7 @@ describe("ConfigRule", () => { describe("createCoreRuleConfigs()", () => { - const rulesConfig = ConfigRule.createCoreRuleConfigs(); + const rulesConfig = createCoreRuleConfigs(); it("should create a rulesConfig containing all core rules", () => { const @@ -309,7 +309,7 @@ describe("ConfigRule", () => { return !isDeprecated; }) .map(([id]) => id), - actualRules = Object.keys(ConfigRule.createCoreRuleConfigs(true)); + actualRules = Object.keys(createCoreRuleConfigs(true)); assert.sameMembers(actualRules, expectedRules); diff --git a/tools/internal-testers/test-parser.js b/tools/internal-testers/test-parser.js deleted file mode 100644 index 939705e94e4..00000000000 --- a/tools/internal-testers/test-parser.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @author Toru Nagashima - */ -"use strict"; - -const espree = require("espree"); -const Traverser = require("../../lib/shared/traverser"); - -/** - * Define `start`/`end` properties as throwing error. - * @param {ASTNode} node The node to define. - * @returns {void} - */ -function defineStartEndAsError(node) { - Object.defineProperty(node, "start", { - get() { - throw new Error("Use node.range[0] instead of node.start"); - }, - configurable: true, - enumerable: false - }); - Object.defineProperty(node, "end", { - get() { - throw new Error("Use node.range[1] instead of node.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. - * @returns {void} - */ -function defineStartEndAsErrorInTree(ast) { - Traverser.traverse(ast, { enter: defineStartEndAsError }); - ast.tokens.forEach(defineStartEndAsError); - ast.comments.forEach(defineStartEndAsError); -} - -module.exports.parse = (code, options) => { - const ret = espree.parse(code, options); - - defineStartEndAsErrorInTree(ret.ast || ret); - - return ret; -}; From 1dc861897e8b47280e878d609c13c9e41892f427 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Tue, 12 Mar 2024 08:06:40 +0000 Subject: [PATCH 025/166] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 695369711f7..ee61d0b312a 100644 --- a/README.md +++ b/README.md @@ -303,7 +303,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

-

Chrome Frameworks Fund Automattic

Gold Sponsors

+

Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

JetBrains Liftoff American Express Workleap

Bronze Sponsors

notion ThemeIsle Anagram Solver Icons8 Discord Transloadit Ignition Nx HeroCoders Nextbase Starter Kit

From 29c359599c2ddd168084a2c8cbca626c51d0dc13 Mon Sep 17 00:00:00 2001 From: cuithon <65674308+cuithon@users.noreply.github.com> Date: Tue, 12 Mar 2024 20:24:37 +0800 Subject: [PATCH 026/166] chore: remove repetitive words (#18193) Signed-off-by: cuithon --- tests/lib/cli-engine/cli-engine.js | 2 +- tests/lib/eslint/eslint.js | 2 +- tests/lib/eslint/legacy-eslint.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/lib/cli-engine/cli-engine.js b/tests/lib/cli-engine/cli-engine.js index c5f88e627a6..f7b30083dca 100644 --- a/tests/lib/cli-engine/cli-engine.js +++ b/tests/lib/cli-engine/cli-engine.js @@ -4397,7 +4397,7 @@ describe("CLIEngine", () => { assert(engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); }); - it("should still apply defaultPatterns if ignore option is is false", () => { + it("should still apply defaultPatterns if ignore option is false", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ ignore: false, cwd }); diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index a00efc2afad..39eea5787b6 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -4597,7 +4597,7 @@ describe("ESLint", () => { assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); }); - it("should still apply default ignore patterns if ignore option is is false", async () => { + it("should still apply default ignore patterns if ignore option is false", async () => { const cwd = getFixturePath("ignored-paths"); const engine = new ESLint({ ignore: false, cwd }); diff --git a/tests/lib/eslint/legacy-eslint.js b/tests/lib/eslint/legacy-eslint.js index 60b40cb5cd6..81a08ff8c5f 100644 --- a/tests/lib/eslint/legacy-eslint.js +++ b/tests/lib/eslint/legacy-eslint.js @@ -4998,7 +4998,7 @@ describe("LegacyESLint", () => { assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); }); - it("should still apply defaultPatterns if ignore option is is false", async () => { + it("should still apply defaultPatterns if ignore option is false", async () => { const cwd = getFixturePath("ignored-paths"); const engine = new LegacyESLint({ ignore: false, cwd }); From 5251327711a2d7083e3c629cb8e48d9d1e809add Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Wed, 13 Mar 2024 08:06:29 +0000 Subject: [PATCH 027/166] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee61d0b312a..b24a55daded 100644 --- a/README.md +++ b/README.md @@ -304,7 +304,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

Automattic

Gold Sponsors

-

Salesforce Airbnb

Silver Sponsors

+

Bitwarden Salesforce Airbnb

Silver Sponsors

JetBrains Liftoff American Express Workleap

Bronze Sponsors

notion ThemeIsle Anagram Solver Icons8 Discord Transloadit Ignition Nx HeroCoders Nextbase Starter Kit

From ae8103de69c12c6e71644a1de9589644e6767d15 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Wed, 13 Mar 2024 12:30:00 +0100 Subject: [PATCH 028/166] fix: load plugins in the CLI in flat config mode (#18185) * fix: load plugins in the CLI in flat config mode * apply review suggestions * fix nesting of unit tests * improve error message --- docs/src/extend/plugins.md | 2 + lib/cli.js | 39 +++++--- .../@scope/eslint-plugin-example.js | 11 +++ .../eslint-plugin-hello-cjs/index.js | 15 +++ .../eslint-plugin-hello-cjs/package.json | 3 + .../eslint-plugin-hello-esm/index.js | 15 +++ .../eslint-plugin-hello-esm/package.json | 3 + .../eslint-plugin-no-default-export/index.js | 12 +++ .../package.json | 3 + tests/fixtures/plugins/subdir/file.js | 0 tests/lib/cli.js | 99 +++++++++++++++++++ 11 files changed, 191 insertions(+), 11 deletions(-) create mode 100644 tests/fixtures/plugins/node_modules/eslint-plugin-hello-cjs/index.js create mode 100644 tests/fixtures/plugins/node_modules/eslint-plugin-hello-cjs/package.json create mode 100644 tests/fixtures/plugins/node_modules/eslint-plugin-hello-esm/index.js create mode 100644 tests/fixtures/plugins/node_modules/eslint-plugin-hello-esm/package.json create mode 100644 tests/fixtures/plugins/node_modules/eslint-plugin-no-default-export/index.js create mode 100644 tests/fixtures/plugins/node_modules/eslint-plugin-no-default-export/package.json create mode 100644 tests/fixtures/plugins/subdir/file.js diff --git a/docs/src/extend/plugins.md b/docs/src/extend/plugins.md index 37a0299dd14..94b090d871e 100644 --- a/docs/src/extend/plugins.md +++ b/docs/src/extend/plugins.md @@ -36,6 +36,8 @@ export default plugin; module.exports = plugin; ``` +If you plan to distribute your plugin as an npm package, make sure that the module that exports the plugin object is the default export of your package. This will enable ESLint to import the plugin when it is specified in the command line in the [`--plugin` option](../use/command-line-interface#--plugin). + ### Meta Data in Plugins For easier debugging and more effective caching of plugins, it's recommended to provide a name and version in a `meta` object at the root of your plugin, like this: diff --git a/lib/cli.js b/lib/cli.js index dce15238347..20a007cbc7b 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -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. @@ -152,17 +179,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 { diff --git a/tests/fixtures/plugins/node_modules/@scope/eslint-plugin-example.js b/tests/fixtures/plugins/node_modules/@scope/eslint-plugin-example.js index e69de29bb2d..046c2a31cf5 100644 --- a/tests/fixtures/plugins/node_modules/@scope/eslint-plugin-example.js +++ b/tests/fixtures/plugins/node_modules/@scope/eslint-plugin-example.js @@ -0,0 +1,11 @@ +const plugin = { + rules: { + test: { + create() { + return { }; + } + } + }, +}; + +module.exports = plugin; diff --git a/tests/fixtures/plugins/node_modules/eslint-plugin-hello-cjs/index.js b/tests/fixtures/plugins/node_modules/eslint-plugin-hello-cjs/index.js new file mode 100644 index 00000000000..5f2bd0b8feb --- /dev/null +++ b/tests/fixtures/plugins/node_modules/eslint-plugin-hello-cjs/index.js @@ -0,0 +1,15 @@ +const plugin = { + rules: { + hello: { + create(context) { + return { + Program(node) { + context.report({ node, message: 'Hello CommonJS!' }); + } + }; + } + } + }, +}; + +module.exports = plugin; diff --git a/tests/fixtures/plugins/node_modules/eslint-plugin-hello-cjs/package.json b/tests/fixtures/plugins/node_modules/eslint-plugin-hello-cjs/package.json new file mode 100644 index 00000000000..5bbefffbabe --- /dev/null +++ b/tests/fixtures/plugins/node_modules/eslint-plugin-hello-cjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/tests/fixtures/plugins/node_modules/eslint-plugin-hello-esm/index.js b/tests/fixtures/plugins/node_modules/eslint-plugin-hello-esm/index.js new file mode 100644 index 00000000000..c3bb6c5adc6 --- /dev/null +++ b/tests/fixtures/plugins/node_modules/eslint-plugin-hello-esm/index.js @@ -0,0 +1,15 @@ +const plugin = { + rules: { + hello: { + create(context) { + return { + Program(node) { + context.report({ node, message: 'Hello ESM!' }); + } + }; + } + } + }, +}; + +export default plugin; diff --git a/tests/fixtures/plugins/node_modules/eslint-plugin-hello-esm/package.json b/tests/fixtures/plugins/node_modules/eslint-plugin-hello-esm/package.json new file mode 100644 index 00000000000..3dbc1ca591c --- /dev/null +++ b/tests/fixtures/plugins/node_modules/eslint-plugin-hello-esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/tests/fixtures/plugins/node_modules/eslint-plugin-no-default-export/index.js b/tests/fixtures/plugins/node_modules/eslint-plugin-no-default-export/index.js new file mode 100644 index 00000000000..dbdb7dee865 --- /dev/null +++ b/tests/fixtures/plugins/node_modules/eslint-plugin-no-default-export/index.js @@ -0,0 +1,12 @@ +export const meta = { + name: "eslint-plugin-no-default-export", + version: "1.2.3" +}; + +export const rules = { + rule: { + create() { + return {}; + } + } +}; diff --git a/tests/fixtures/plugins/node_modules/eslint-plugin-no-default-export/package.json b/tests/fixtures/plugins/node_modules/eslint-plugin-no-default-export/package.json new file mode 100644 index 00000000000..3dbc1ca591c --- /dev/null +++ b/tests/fixtures/plugins/node_modules/eslint-plugin-no-default-export/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/tests/fixtures/plugins/subdir/file.js b/tests/fixtures/plugins/subdir/file.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/lib/cli.js b/tests/lib/cli.js index ef5b4e30cf0..ce127d542e6 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -1757,4 +1757,103 @@ describe("cli", () => { }); + describe("flat Only", () => { + + describe("`--plugin` option", () => { + + let originalCwd; + + beforeEach(() => { + originalCwd = process.cwd(); + process.chdir(getFixturePath("plugins")); + }); + + afterEach(() => { + process.chdir(originalCwd); + originalCwd = void 0; + }); + + it("should load a plugin from a CommonJS package", async () => { + const code = "--plugin hello-cjs --rule 'hello-cjs/hello: error' ../files/*.js"; + + const exitCode = await cli.execute(code, null, true); + + assert.strictEqual(exitCode, 1); + assert.ok(log.info.calledOnce); + assert.include(log.info.firstCall.firstArg, "Hello CommonJS!"); + }); + + it("should load a plugin from an ESM package", async () => { + const code = "--plugin hello-esm --rule 'hello-esm/hello: error' ../files/*.js"; + + const exitCode = await cli.execute(code, null, true); + + assert.strictEqual(exitCode, 1); + assert.ok(log.info.calledOnce); + assert.include(log.info.firstCall.firstArg, "Hello ESM!"); + }); + + it("should load multiple plugins", async () => { + const code = "--plugin 'hello-cjs, hello-esm' --rule 'hello-cjs/hello: warn, hello-esm/hello: error' ../files/*.js"; + + const exitCode = await cli.execute(code, null, true); + + assert.strictEqual(exitCode, 1); + assert.ok(log.info.calledOnce); + assert.include(log.info.firstCall.firstArg, "Hello CommonJS!"); + assert.include(log.info.firstCall.firstArg, "Hello ESM!"); + }); + + it("should resolve plugins specified with 'eslint-plugin-'", async () => { + const code = "--plugin 'eslint-plugin-schema-array, @scope/eslint-plugin-example' --rule 'schema-array/rule1: warn, @scope/example/test: warn' ../passing.js"; + + const exitCode = await cli.execute(code, null, true); + + assert.strictEqual(exitCode, 0); + }); + + it("should resolve plugins in the parent directory's node_module subdirectory", async () => { + process.chdir("subdir"); + const code = "--plugin 'example, @scope/example' file.js"; + + const exitCode = await cli.execute(code, null, true); + + assert.strictEqual(exitCode, 0); + }); + + it("should fail if a plugin is not found", async () => { + const code = "--plugin 'example, no-such-plugin' ../passing.js"; + + await stdAssert.rejects( + cli.execute(code, null, true), + ({ message }) => { + assert( + message.startsWith("Cannot find module 'eslint-plugin-no-such-plugin'\n"), + `Unexpected error message:\n${message}` + ); + return true; + } + ); + }); + + it("should fail if a plugin throws an error while loading", async () => { + const code = "--plugin 'example, throws-on-load' ../passing.js"; + + await stdAssert.rejects( + cli.execute(code, null, true), + { message: "error thrown while loading this module" } + ); + }); + + it("should fail to load a plugin from a package without a default export", async () => { + const code = "--plugin 'example, no-default-export' ../passing.js"; + + await stdAssert.rejects( + cli.execute(code, null, true), + { message: '"eslint-plugin-no-default-export" cannot be used with the `--plugin` option because its default module does not provide a `default` export' } + ); + }); + }); + }); + }); From b8fb57256103b908712302ccd508f464eff1c9dc Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger <53019676+kirkwaiblinger@users.noreply.github.com> Date: Thu, 14 Mar 2024 12:44:49 -0600 Subject: [PATCH 029/166] feat: add `reportUnusedFallthroughComment` option to no-fallthrough rule (#18188) * feat: (no-fallthrough) Report unused fallthrough comments fixes #18182 * add space * add a few test cases to ensure state doesn't leak across multiple switches * add correct case in docs * fix leaked state * Fix docs typo Co-authored-by: Milos Djermanovic * add some test coverage --------- Co-authored-by: Milos Djermanovic --- docs/src/rules/no-fallthrough.md | 56 +++++++ lib/rules/no-fallthrough.js | 57 +++++-- tests/lib/rules/no-fallthrough.js | 237 ++++++++++++++++++++++++++++++ 3 files changed, 334 insertions(+), 16 deletions(-) diff --git a/docs/src/rules/no-fallthrough.md b/docs/src/rules/no-fallthrough.md index 1f092097330..3076516aa65 100644 --- a/docs/src/rules/no-fallthrough.md +++ b/docs/src/rules/no-fallthrough.md @@ -178,6 +178,8 @@ This rule has an object option: * Set the `allowEmptyCase` option to `true` to allow empty cases regardless of the layout. By default, this rule does not require a fallthrough comment after an empty `case` only if the empty `case` and the next `case` are on the same line or on consecutive lines. +* Set the `reportUnusedFallthroughComment` option to `true` to prohibit a fallthrough comment from being present if the case cannot fallthrough due to being unreachable. This is mostly intended to help avoid misleading comments occurring as a result of refactoring. + ### commentPattern Examples of **correct** code for the `{ "commentPattern": "break[\\s\\w]*omitted" }` option: @@ -235,6 +237,60 @@ switch(foo){ ::: +### reportUnusedFallthroughComment + +Examples of **incorrect** code for the `{ "reportUnusedFallthroughComment": true }` option: + +::: incorrect + +```js +/* eslint no-fallthrough: ["error", { "reportUnusedFallthroughComment": true }] */ + +switch(foo){ + case 1: + doSomething(); + break; + // falls through + case 2: doSomething(); +} + +function f() { + switch(foo){ + case 1: + if (a) { + throw new Error(); + } else if (b) { + break; + } else { + return; + } + // falls through + case 2: + break; + } +} +``` + +::: + +Examples of **correct** code for the `{ "reportUnusedFallthroughComment": true }` option: + +::: correct + +```js +/* eslint no-fallthrough: ["error", { "reportUnusedFallthroughComment": true }] */ + +switch(foo){ + case 1: + doSomething(); + break; + // just a comment + case 2: doSomething(); +} +``` + +::: + ## When Not To Use It If you don't want to enforce that each `case` statement should end with a `throw`, `return`, `break`, or comment, then you can safely turn this rule off. diff --git a/lib/rules/no-fallthrough.js b/lib/rules/no-fallthrough.js index 03b5f6f7ce6..4c98ddde525 100644 --- a/lib/rules/no-fallthrough.js +++ b/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.at(-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/tests/lib/rules/no-fallthrough.js b/tests/lib/rules/no-fallthrough.js index f922a76fcac..ac3806964a6 100644 --- a/tests/lib/rules/no-fallthrough.js +++ b/tests/lib/rules/no-fallthrough.js @@ -64,6 +64,27 @@ ruleTester.run("no-fallthrough", rule, { "switch (foo) { case 0: try { throw 0; } catch (err) { break; } default: b(); }", "switch (foo) { case 0: do { throw 0; } while(a); default: b(); }", "switch (foo) { case 0: a(); \n// eslint-disable-next-line rule-to-test/no-fallthrough\n case 1: }", + ` + switch (foo) { + case 0: + a(); + break; + /* falls through */ + case 1: + b(); + } + `, + ` + switch (foo) { + case 0: + a(); + break; + // eslint-disable-next-line rule-to-test/no-fallthrough + /* falls through */ + case 1: + b(); + } + `, { code: "switch(foo) { case 0: a(); /* no break */ case 1: b(); }", options: [{ @@ -109,8 +130,128 @@ ruleTester.run("no-fallthrough", rule, { { code: "switch (a) {\n case 1: ; break; \n case 3: }", options: [{ allowEmptyCase: false }] + }, + ` +switch (foo) { + case 0: + a(); +} +switch (bar) { + case 1: + b(); +} + `, + { + code: + ` +switch (foo) { + case 0: + a(); + break; + // falls through +} +switch (bar) { + case 1: + b(); +} + `, + options: [{ reportUnusedFallthroughComment: true }] + }, + { + code: + ` +switch (foo) { + case 0: + a(); + break; + /* falls through */ +} +switch (bar) { + case 1: + b(); +} + `, + options: [{ reportUnusedFallthroughComment: true }] + }, + { + code: ` +switch(foo){ + case 1: + doSomething(); + break; + // just a comment + case 2: doSomething(); +} + `, + options: [{ reportUnusedFallthroughComment: true }] + + }, + { + code: ` +switch(foo){ + case 1: + doSomething(); + break; +} + +function f() { + switch(foo){ + // falls through comment should not false positive + case 1: + if (a) { + throw new Error(); + } else if (b) { + break; + } else { + return; + } + case 2: + break; + } +} + `, + options: [{ reportUnusedFallthroughComment: true }] + }, + { + code: ` +switch(foo){ + case 1: + doSomething(); + break; +} + +function f() { + switch(foo){ + /* falls through comment should not false positive */ + case 1: + if (a) { + throw new Error(); + } else if (b) { + break; + } else { + return; + } + case 2: + break; + } +} + `, + options: [{ reportUnusedFallthroughComment: true }] + }, + { + code: ` +switch(foo){ + case 1: + doSomething(); + // falls through + case 2: doSomething(); +} + `, + options: [{ reportUnusedFallthroughComment: true }] + } ], + invalid: [ { code: "switch(foo) { case 0: a();\ncase 1: b() }", @@ -310,6 +451,102 @@ ruleTester.run("no-fallthrough", rule, { column: 2 } ] + }, + { + code: ` +switch (foo) { + case 0: + a(); + break; + /* falls through */ + case 1: + b(); +}`, + options: [{ reportUnusedFallthroughComment: true }], + errors: [ + { + line: 6, + messageId: "unusedFallthroughComment" + } + ] + }, + { + code: ` +switch (foo) { + default: + a(); + break; + /* falls through */ + case 1: + b(); +}`, + options: [{ reportUnusedFallthroughComment: true }], + errors: [ + { + line: 6, + messageId: "unusedFallthroughComment" + } + ] + }, + { + code: ` +switch(foo){ + case 1: + doSomething(); + break; + // falls through + case 2: doSomething(); +}`, + options: [{ reportUnusedFallthroughComment: true }], + errors: [ + { + line: 6, + messageId: "unusedFallthroughComment" + } + ] + }, { + code: ` +function f() { + switch(foo){ + case 1: + if (a) { + throw new Error(); + } else if (b) { + break; + } else { + return; + } + // falls through + case 2: + break; + } +}`, + options: [{ reportUnusedFallthroughComment: true }], + errors: [ + { + line: 12, + messageId: "unusedFallthroughComment" + } + ] + }, + { + code: ` +switch (foo) { + case 0: { + a(); + break; + // falls through + } + case 1: + b(); +}`, + options: [{ reportUnusedFallthroughComment: true }], + errors: [ + { + line: 6, + messageId: "unusedFallthroughComment" + } + ] } ] }); From 1b841bb04ac642c5ee84d1e44be3e53317579526 Mon Sep 17 00:00:00 2001 From: avoidaway <163620038+avoidaway@users.noreply.github.com> Date: Sun, 17 Mar 2024 02:45:47 +0800 Subject: [PATCH 030/166] chore: fix some comments (#18213) --- CHANGELOG.md | 2 +- tests/bin/eslint.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caaddda49de..2169b2af1bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2865,7 +2865,7 @@ v6.0.0-rc.0 - June 9, 2019 * [`87451f4`](https://github.com/eslint/eslint/commit/87451f4779bc4c0ec874042b6854920f947ee258) Fix: no-octal should report NonOctalDecimalIntegerLiteral (fixes #11794) (#11805) (Milos Djermanovic) * [`e4ab053`](https://github.com/eslint/eslint/commit/e4ab0531c4e44c23494c6a802aa2329d15ac90e5) Update: support "bigint" in valid-typeof rule (#11802) (Colin Ihrig) * [`e0fafc8`](https://github.com/eslint/eslint/commit/e0fafc8ef59a80a6137f4170bbe46582d6fbcafc) Chore: removes unnecessary assignment in loop (#11780) (Dimitri Mitropoulos) -* [`20908a3`](https://github.com/eslint/eslint/commit/20908a38f489c285abf8fceef4d9d13bf8b87f22) Docs: removed '>' prefix from from docs/working-with-rules (#11818) (Alok Takshak) +* [`20908a3`](https://github.com/eslint/eslint/commit/20908a38f489c285abf8fceef4d9d13bf8b87f22) Docs: removed '>' prefix from docs/working-with-rules (#11818) (Alok Takshak) * [`1c43eef`](https://github.com/eslint/eslint/commit/1c43eef605a9cf02a157881424ea62dcae747f69) Sponsors: Sync README with website (ESLint Jenkins) * [`21f3131`](https://github.com/eslint/eslint/commit/21f3131aa1636afa8e5c01053e0e870f968425b1) Fix: `overrides` handle relative paths as expected (fixes #11577) (#11799) (Toru Nagashima) * [`5509cdf`](https://github.com/eslint/eslint/commit/5509cdfa1b3d575248eef89a935f79c82e3f3071) Fix: fails the test case if autofix made syntax error (fixes #11615) (#11798) (Toru Nagashima) diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js index 505e27a1398..44e5bfb22ed 100644 --- a/tests/bin/eslint.js +++ b/tests/bin/eslint.js @@ -102,7 +102,7 @@ describe("bin/eslint.js", () => { ], { - // Use the tests directory as the CWD to supress the ESLintIgnoreWarning + // Use the tests directory as the CWD to suppress the ESLintIgnoreWarning cwd: path.resolve(__dirname, "../") } ); From 4769c86cc16e0b54294c0a394a1ec7ed88fc334f Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:56:30 +0530 Subject: [PATCH 031/166] docs: fix incorrect example in `no-lone-blocks` (#18215) --- docs/src/rules/no-lone-blocks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/rules/no-lone-blocks.md b/docs/src/rules/no-lone-blocks.md index efce8248f1c..01eee9f0600 100644 --- a/docs/src/rules/no-lone-blocks.md +++ b/docs/src/rules/no-lone-blocks.md @@ -20,7 +20,7 @@ This rule aims to eliminate unnecessary and potentially confusing blocks at the Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-lone-blocks: "error"*/ From b91f9dc072f17f5ea79803deb86cf002d031b4cf Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Mon, 18 Mar 2024 09:45:01 +0100 Subject: [PATCH 032/166] build: fix TypeError in prism-eslint-hooks.js (#18209) * fix: TypeError in prism-eslint-hooks.js * check if `token` is a string --- docs/tools/prism-eslint-hook.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/tools/prism-eslint-hook.js b/docs/tools/prism-eslint-hook.js index 4b348916aa9..74dd5197cea 100644 --- a/docs/tools/prism-eslint-hook.js +++ b/docs/tools/prism-eslint-hook.js @@ -305,6 +305,10 @@ function installPrismESLintMarkerHook() { */ function *splitTokensByLineFeed(tokens) { for (const token of tokens) { + if (typeof token === "string") { + yield token; + continue; + } const content = getTokenContent(token); @@ -317,7 +321,7 @@ function installPrismESLintMarkerHook() { continue; } - if (Array.isArray(token.content) || typeof token.content !== "string") { + if (typeof token.content !== "string") { token.content = [...splitTokensByLineFeed([token.content].flat())]; } yield token; From 09bd7fe09ad255a263286e90accafbe2bf04ccfc Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 21 Mar 2024 14:34:21 -0700 Subject: [PATCH 033/166] feat!: move AST traversal into SourceCode (#18167) * feat: Implement SourceCode#traverse() Refs #16999 * Add more debugging * Clean up sourcecode * Try to fix code paths * Fix rules and update migration guide * Cache traversal steps * Fix lint error * Fix fuzz test * Simplify TraversalStep constructor * Remove unused parent arg * Fix constructor-super --- docs/src/use/migrate-to-9.0.0.md | 14 ++ .../code-path-analysis/code-path-analyzer.js | 1 - lib/linter/linter.js | 59 ++++--- lib/rules/constructor-super.js | 76 +++++--- lib/rules/no-this-before-super.js | 37 +++- lib/rules/no-useless-return.js | 9 +- lib/source-code/source-code.js | 166 +++++++++++++++++- tests/lib/linter/linter.js | 24 +++ tests/lib/rules/constructor-super.js | 13 ++ 9 files changed, 334 insertions(+), 65 deletions(-) diff --git a/docs/src/use/migrate-to-9.0.0.md b/docs/src/use/migrate-to-9.0.0.md index e6e0ed58e66..08523b19644 100644 --- a/docs/src/use/migrate-to-9.0.0.md +++ b/docs/src/use/migrate-to-9.0.0.md @@ -43,6 +43,7 @@ The lists below are ordered roughly by the number of users each change is expect * [Removed multiple `context` methods](#removed-context-methods) * [Removed `sourceCode.getComments()`](#removed-sourcecode-getcomments) * [Removed `CodePath#currentSegments`](#removed-codepath-currentsegments) +* [Code paths are now precalculated](#codepath-precalc) * [Function-style rules are no longer supported](#drop-function-style-rules) * [`meta.schema` is required for rules with options](#meta-schema-required) * [`FlatRuleTester` is now `RuleTester`](#flat-rule-tester) @@ -490,6 +491,19 @@ ESLint v9.0.0 removes the deprecated `CodePath#currentSegments` property. **Related Issues(s):** [#17457](https://github.com/eslint/eslint/issues/17457) +## Code paths are now precalculated + +Prior to ESLint v9.0.0, code paths were calculated during the same AST traversal used by rules, meaning that the information passed to methods like `onCodePathStart` and `onCodePathSegmentStart` was incomplete. Specifically, array properties like `CodePath#childCodePaths` and `CodePathSegment#nextSegments` began empty and then were filled with the appropriate information as the traversal completed, meaning that those arrays could have different elements depending on when you checked their values. + +ESLint v9.0.0 now precalculates code path information before the traversal used by rules. As a result, the code path information is now complete regardless of where it is accessed inside of a rule. + +**To address:** If you are accessing any array properties on `CodePath` or `CodePathSegment`, you'll need to update your code. Specifically: + +* If you are checking the `length` of any array properties, ensure you are using relative comparison operators like `<`, `>`, `<=`, and `>=` instead of equals. +* If you are accessing the `nextSegments`, `prevSegments`, `allNextSegments`, or `allPrevSegments` properties on a `CodePathSegment`, or `CodePath#childCodePaths`, verify that your code will still work as expected. To be backwards compatible, consider moving the logic that checks these properties into `onCodePathEnd`. + +**Related Issues(s):** [#16999](https://github.com/eslint/eslint/issues/16999) + ## Function-style rules are no longer supported ESLint v9.0.0 drops support for function-style rules. Function-style rules are rules created by exporting a function rather than an object with a `create()` method. This rule format was deprecated in 2016. diff --git a/lib/linter/code-path-analysis/code-path-analyzer.js b/lib/linter/code-path-analysis/code-path-analyzer.js index b60e55c16de..f5f26d0f470 100644 --- a/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/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/lib/linter/linter.js b/lib/linter/linter.js index 605c8604388..8ae8ad367a3 100644 --- a/lib/linter/linter.js +++ b/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"), @@ -53,6 +52,8 @@ 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 @@ -986,22 +987,13 @@ function createRuleListeners(rule, ruleContext) { * @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. * @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, ruleFilter) { 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. @@ -1131,25 +1123,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; } diff --git a/lib/rules/constructor-super.js b/lib/rules/constructor-super.js index bab709d4188..7ded20f6075 100644 --- a/lib/rules/constructor-super.js +++ b/lib/rules/constructor-super.js @@ -119,6 +119,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 //------------------------------------------------------------------------------ @@ -159,12 +183,8 @@ 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); @@ -174,7 +194,16 @@ module.exports = { * @returns {boolean} The flag which shows `super()` is called in some paths */ function isCalledInSomePath(segment) { - return segment.reachable && segInfoMap[segment.id].calledInSomePaths; + 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]; } /** @@ -190,10 +219,10 @@ module.exports = { * If not skipped, this never becomes true after a loop. */ if (segment.nextSegments.length === 1 && - segment.nextSegments[0].isLoopedPrevSegment(segment) - ) { + segment.nextSegments[0]?.isLoopedPrevSegment(segment)) { return true; } + return segment.reachable && segInfoMap[segment.id].calledInEveryPaths; } @@ -250,9 +279,9 @@ module.exports = { } // Reports if `super()` lacked. - const segments = codePath.returnedSegments; - const calledInEveryPaths = segments.every(isCalledInEveryPath); - const calledInSomePaths = segments.some(isCalledInSomePath); + const seenSegments = codePath.returnedSegments.filter(hasSegmentBeenSeen); + const calledInEveryPaths = seenSegments.every(isCalledInEveryPath); + const calledInSomePaths = seenSegments.some(isCalledInSomePath); if (!calledInEveryPaths) { context.report({ @@ -278,18 +307,16 @@ module.exports = { } // Initialize info. - const info = segInfoMap[segment.id] = { - calledInSomePaths: false, - calledInEveryPaths: false, - validNodes: [] - }; + const info = segInfoMap[segment.id] = new SegmentInfo(); // When there are previous segments, aggregates these. const prevSegments = segment.prevSegments; if (prevSegments.length > 0) { - info.calledInSomePaths = prevSegments.some(isCalledInSomePath); - info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath); + const seenPrevSegments = prevSegments.filter(hasSegmentBeenSeen); + + info.calledInSomePaths = seenPrevSegments.some(isCalledInSomePath); + info.calledInEveryPaths = seenPrevSegments.every(isCalledInEveryPath); } }, @@ -326,12 +353,12 @@ module.exports = { funcInfo.codePath.traverseSegments( { first: toSegment, last: fromSegment }, segment => { - const info = segInfoMap[segment.id]; - const prevSegments = segment.prevSegments; + const info = segInfoMap[segment.id] ?? new SegmentInfo(); + const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen); // Updates flags. - info.calledInSomePaths = prevSegments.some(isCalledInSomePath); - info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath); + info.calledInSomePaths = seenPrevSegments.some(isCalledInSomePath); + info.calledInEveryPaths = seenPrevSegments.every(isCalledInEveryPath); // If flags become true anew, reports the valid nodes. if (info.calledInSomePaths || isRealLoop) { @@ -348,6 +375,9 @@ module.exports = { }); } } + + // save just in case we created a new SegmentInfo object + segInfoMap[segment.id] = info; } ); }, diff --git a/lib/rules/no-this-before-super.js b/lib/rules/no-this-before-super.js index bcd00a404ba..01e355acbd1 100644 --- a/lib/rules/no-this-before-super.js +++ b/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; } /** @@ -285,7 +302,7 @@ 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) { controller.skip(); @@ -295,6 +312,8 @@ module.exports = { ) { info.superCalled = true; } + + segInfoMap[segment.id] = info; } ); }, diff --git a/lib/rules/no-useless-return.js b/lib/rules/no-useless-return.js index 81d61051053..1f85cdb9aaf 100644 --- a/lib/rules/no-useless-return.js +++ b/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/lib/source-code/source-code.js b/lib/source-code/source-code.js index 31dec21cf49..f3418e7e5b7 100644 --- a/lib/source-code/source-code.js +++ b/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. @@ -972,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/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index 8e7c3c5070c..6e98b93f3a1 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -14337,6 +14337,30 @@ var a = "test2"; assert.strictEqual(suppressedMessages.length, 0); }); + it("reports no problems for no-fallthrough despite comment pattern match", () => { + const code = "switch (foo) { case 0: a(); \n// eslint-disable-next-line no-fallthrough\n case 1: }"; + const config = { + linterOptions: { + reportUnusedDisableDirectives: true + }, + rules: { + "no-fallthrough": 2 + } + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-fallthrough"); + }); + + describe("autofix", () => { const alwaysReportsRule = { create(context) { diff --git a/tests/lib/rules/constructor-super.js b/tests/lib/rules/constructor-super.js index a65999dfbdb..70fd14a8859 100644 --- a/tests/lib/rules/constructor-super.js +++ b/tests/lib/rules/constructor-super.js @@ -272,6 +272,19 @@ ruleTester.run("constructor-super", rule, { } }`, errors: [{ messageId: "missingAll", type: "MethodDefinition" }] + }, + + { + code: `class C extends D { + + constructor() { + do { + something(); + } while (foo); + } + + }`, + errors: [{ messageId: "missingAll", type: "MethodDefinition" }] } ] }); From 239a7e27209a6b861d634b3ef245ebbb805793a3 Mon Sep 17 00:00:00 2001 From: gyeongwoo park <138835573+gwBear1@users.noreply.github.com> Date: Fri, 22 Mar 2024 21:02:43 +0900 Subject: [PATCH 034/166] docs: Clarify the description of `sort-imports` options (#18198) * fix sort-imports doc * fix sort import's test cases * remove one useless test case * fix typo * applied review * better to clear description Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * Better description for ignoreCase * write default option at the top of the option. * Apply suggestions from code review Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> --------- Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> --- docs/src/rules/sort-imports.md | 102 +++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 23 deletions(-) diff --git a/docs/src/rules/sort-imports.md b/docs/src/rules/sort-imports.md index 762e4acfa43..fa284593207 100644 --- a/docs/src/rules/sort-imports.md +++ b/docs/src/rules/sort-imports.md @@ -192,40 +192,64 @@ import {b, a, c} from 'foo.js' ### `ignoreCase` -When `true` the rule ignores the case-sensitivity of the imports local name. +When `false` (default), uppercase letters of the alphabet must always precede lowercase letters. -Examples of **incorrect** code for this rule with the `{ "ignoreCase": true }` option: +When `true`, the rule ignores the case-sensitivity of the imports local name. + +Examples of **incorrect** code for this rule with the default `{ "ignoreCase": false }` option: ::: incorrect ```js -/*eslint sort-imports: ["error", { "ignoreCase": true }]*/ - -import B from 'foo.js'; +/*eslint sort-imports: ["error", { "ignoreCase": false }]*/ import a from 'bar.js'; +import B from 'foo.js'; +import c from 'baz.js'; ``` ::: -Examples of **correct** code for this rule with the `{ "ignoreCase": true }` option: +Examples of **correct** code for this rule with the default `{ "ignoreCase": false }` option: ::: correct ```js -/*eslint sort-imports: ["error", { "ignoreCase": true }]*/ - -import a from 'foo.js'; +/*eslint sort-imports: ["error", { "ignoreCase": false }]*/ import B from 'bar.js'; +import a from 'foo.js'; +import c from 'baz.js'; +``` + +::: + +Examples of **correct** code for this rule with `{ "ignoreCase": true }` option: + +::: correct + +```js +/*eslint sort-imports: ["error", { "ignoreCase": true }]*/ +import a from 'bar.js'; +import B from 'foo.js'; import c from 'baz.js'; ``` ::: -Default is `false`. +Examples of **incorrect** code for this rule with the `{ "ignoreCase": true }` option: + +::: incorrect + +```js +/*eslint sort-imports: ["error", { "ignoreCase": true }]*/ +import B from 'foo.js'; +import a from 'bar.js'; +``` + +::: ### `ignoreDeclarationSort` -Ignores the sorting of import declaration statements. +When `true`, the rule ignores the sorting of import declaration statements. Default is `false`. Examples of **incorrect** code for this rule with the default `{ "ignoreDeclarationSort": false }` option: @@ -239,18 +263,20 @@ import a from 'bar.js' ::: -Examples of **correct** code for this rule with the `{ "ignoreDeclarationSort": true }` option: +Examples of **correct** code for this rule with the default `{ "ignoreDeclarationSort": false }` option: ::: correct ```js -/*eslint sort-imports: ["error", { "ignoreDeclarationSort": true }]*/ -import a from 'foo.js' -import b from 'bar.js' +/*eslint sort-imports: ["error", { "ignoreDeclarationSort": false }]*/ +import a from 'bar.js'; +import b from 'foo.js'; ``` ::: +Examples of **correct** code for this rule with the `{ "ignoreDeclarationSort": true }` option: + ::: correct ```js @@ -261,11 +287,20 @@ import a from 'bar.js' ::: -Default is `false`. +Examples of **incorrect** code for this rule with the `{ "ignoreDeclarationSort": true }` option: + +::: incorrect + +```js +/*eslint sort-imports: ["error", { "ignoreDeclarationSort": true }]*/ +import {b, a, c} from 'foo.js'; +``` + +::: ### `ignoreMemberSort` -Ignores the member sorting within a `multiple` member import declaration. +When `true`, the rule ignores the member sorting within a `multiple` member import declaration. Default is `false`. Examples of **incorrect** code for this rule with the default `{ "ignoreMemberSort": false }` option: @@ -278,6 +313,17 @@ import {b, a, c} from 'foo.js' ::: +Examples of **correct** code for this rule with the default `{ "ignoreMemberSort": false }` option: + +::: correct + +```js +/*eslint sort-imports: ["error", { "ignoreMemberSort": false }]*/ +import {a, b, c} from 'foo.js'; +``` + +::: + Examples of **correct** code for this rule with the `{ "ignoreMemberSort": true }` option: ::: correct @@ -289,10 +335,24 @@ import {b, a, c} from 'foo.js' ::: -Default is `false`. +Examples of **incorrect** code for this rule with the `{ "ignoreMemberSort": true }` option: + +::: incorrect + +```js +/*eslint sort-imports: ["error", { "ignoreMemberSort": true }]*/ +import b from 'foo.js'; +import a from 'bar.js'; +``` + +::: ### `memberSyntaxSortOrder` +This option takes an array with four predefined elements, the order of elements specifies the order of import styles. + +Default order is `["none", "all", "multiple", "single"]`. + There are four different styles and the default member syntax sort order is: * `none` - import module without exported bindings. @@ -341,11 +401,9 @@ import {a, b} from 'foo.js'; ::: -Default is `["none", "all", "multiple", "single"]`. - ### `allowSeparatedGroups` -When `true` the rule checks the sorting of import declaration statements only for those that appear on consecutive lines. +When `true`, the rule checks the sorting of import declaration statements only for those that appear on consecutive lines. Default is `false`. In other words, a blank line or a comment line or line with any other statement after an import declaration statement will reset the sorting of import declaration statements. @@ -404,8 +462,6 @@ import a from 'baz.js'; ::: -Default is `false`. - ## When Not To Use It This rule is a formatting preference and not following it won't negatively affect the quality of your code. If alphabetizing imports isn't a part of your coding standards, then you can leave this rule disabled. From d363c51b177e085b011c7fde1c5a5a09b3db9cdb Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 22 Mar 2024 20:21:57 +0000 Subject: [PATCH 035/166] chore: package.json update for @eslint/js release --- packages/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/package.json b/packages/js/package.json index 12ef8235578..8fd90b5961c 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "9.0.0-beta.2", + "version": "9.0.0-rc.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 297416d2b41f5880554d052328aa36cd79ceb051 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 22 Mar 2024 21:42:40 +0100 Subject: [PATCH 036/166] chore: package.json update for eslint-9.0.0-rc.0 (#18223) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9be1a67532d..788c44cead8 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^3.0.2", - "@eslint/js": "9.0.0-beta.2", + "@eslint/js": "9.0.0-rc.0", "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -76,7 +76,7 @@ "cross-spawn": "^7.0.2", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.0", + "eslint-scope": "^8.0.1", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.1", "esquery": "^1.4.2", From 26010c209d2657cd401bf2550ba4f276cb318f7d Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 22 Mar 2024 20:54:12 +0000 Subject: [PATCH 037/166] Build: changelog update for 9.0.0-rc.0 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2169b2af1bb..3a9484074cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +v9.0.0-rc.0 - March 22, 2024 + +* [`297416d`](https://github.com/eslint/eslint/commit/297416d2b41f5880554d052328aa36cd79ceb051) chore: package.json update for eslint-9.0.0-rc.0 (#18223) (Francesco Trotta) +* [`d363c51`](https://github.com/eslint/eslint/commit/d363c51b177e085b011c7fde1c5a5a09b3db9cdb) chore: package.json update for @eslint/js release (Jenkins) +* [`239a7e2`](https://github.com/eslint/eslint/commit/239a7e27209a6b861d634b3ef245ebbb805793a3) docs: Clarify the description of `sort-imports` options (#18198) (gyeongwoo park) +* [`09bd7fe`](https://github.com/eslint/eslint/commit/09bd7fe09ad255a263286e90accafbe2bf04ccfc) feat!: move AST traversal into SourceCode (#18167) (Nicholas C. Zakas) +* [`b91f9dc`](https://github.com/eslint/eslint/commit/b91f9dc072f17f5ea79803deb86cf002d031b4cf) build: fix TypeError in prism-eslint-hooks.js (#18209) (Francesco Trotta) +* [`4769c86`](https://github.com/eslint/eslint/commit/4769c86cc16e0b54294c0a394a1ec7ed88fc334f) docs: fix incorrect example in `no-lone-blocks` (#18215) (Tanuj Kanti) +* [`1b841bb`](https://github.com/eslint/eslint/commit/1b841bb04ac642c5ee84d1e44be3e53317579526) chore: fix some comments (#18213) (avoidaway) +* [`b8fb572`](https://github.com/eslint/eslint/commit/b8fb57256103b908712302ccd508f464eff1c9dc) feat: add `reportUnusedFallthroughComment` option to no-fallthrough rule (#18188) (Kirk Waiblinger) +* [`ae8103d`](https://github.com/eslint/eslint/commit/ae8103de69c12c6e71644a1de9589644e6767d15) fix: load plugins in the CLI in flat config mode (#18185) (Francesco Trotta) +* [`5251327`](https://github.com/eslint/eslint/commit/5251327711a2d7083e3c629cb8e48d9d1e809add) docs: Update README (GitHub Actions Bot) +* [`29c3595`](https://github.com/eslint/eslint/commit/29c359599c2ddd168084a2c8cbca626c51d0dc13) chore: remove repetitive words (#18193) (cuithon) +* [`1dc8618`](https://github.com/eslint/eslint/commit/1dc861897e8b47280e878d609c13c9e41892f427) docs: Update README (GitHub Actions Bot) +* [`acc2e06`](https://github.com/eslint/eslint/commit/acc2e06edd55eaab58530d891c0a572c1f0ec453) chore: Introduce Knip (#18005) (Lars Kappert) + v9.0.0-beta.2 - March 8, 2024 * [`7509276`](https://github.com/eslint/eslint/commit/75092764db117252067558bd3fbbf0c66ac081b7) chore: upgrade @eslint/js@9.0.0-beta.2 (#18180) (Milos Djermanovic) From b185eb97ec60319cc39023e8615959dd598919ae Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 22 Mar 2024 20:54:13 +0000 Subject: [PATCH 038/166] 9.0.0-rc.0 --- docs/package.json | 2 +- docs/src/use/formatters/html-formatter-example.html | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/package.json b/docs/package.json index d976a151996..3c90ccfa41a 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "9.0.0-beta.2", + "version": "9.0.0-rc.0", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index 520b2bfc78c..d9a8be4e1a3 100644 --- a/docs/src/use/formatters/html-formatter-example.html +++ b/docs/src/use/formatters/html-formatter-example.html @@ -118,7 +118,7 @@

ESLint Report

- 8 problems (4 errors, 4 warnings) - Generated on Fri Mar 08 2024 21:18:52 GMT+0000 (Coordinated Universal Time) + 8 problems (4 errors, 4 warnings) - Generated on Fri Mar 22 2024 20:54:14 GMT+0000 (Coordinated Universal Time)
diff --git a/package.json b/package.json index 788c44cead8..51b5f966a66 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "9.0.0-beta.2", + "version": "9.0.0-rc.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From d85c436353d566d261798c51dadb8ed50def1a7d Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Sat, 23 Mar 2024 20:08:15 +0530 Subject: [PATCH 039/166] feat: use-isnan report NaN in `indexOf` and `lastIndexOf` with fromIndex (#18225) * fix: report NaN with fromIndex * revert known limitation title change * add tests with third argument --- docs/src/rules/use-isnan.md | 4 ++ lib/rules/use-isnan.js | 4 +- tests/lib/rules/use-isnan.js | 88 ++++++++++++++++++++++++++++++++++-- 3 files changed, 90 insertions(+), 6 deletions(-) diff --git a/docs/src/rules/use-isnan.md b/docs/src/rules/use-isnan.md index f3de5919d18..6097afb7d1f 100644 --- a/docs/src/rules/use-isnan.md +++ b/docs/src/rules/use-isnan.md @@ -226,6 +226,10 @@ var firstIndex = myArray.indexOf(NaN); var lastIndex = myArray.lastIndexOf(NaN); var indexWithSequenceExpression = myArray.indexOf((doStuff(), NaN)); + +var firstIndexFromSecondElement = myArray.indexOf(NaN, 1); + +var lastIndexFromSecondElement = myArray.lastIndexOf(NaN, 1); ``` ::: diff --git a/lib/rules/use-isnan.js b/lib/rules/use-isnan.js index 06b2284ecd5..5c0b65feefb 100644 --- a/lib/rules/use-isnan.js +++ b/lib/rules/use-isnan.js @@ -182,7 +182,7 @@ module.exports = { if ( (methodName === "indexOf" || methodName === "lastIndexOf") && - node.arguments.length === 1 && + node.arguments.length <= 2 && isNaNIdentifier(node.arguments[0]) ) { @@ -190,7 +190,7 @@ module.exports = { * 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"; + const isSuggestable = node.arguments[0].type !== "SequenceExpression" && !node.arguments[1]; const suggestedFixes = []; if (isSuggestable) { diff --git a/tests/lib/rules/use-isnan.js b/tests/lib/rules/use-isnan.js index 6cb61821952..3a12ffadb35 100644 --- a/tests/lib/rules/use-isnan.js +++ b/tests/lib/rules/use-isnan.js @@ -263,7 +263,7 @@ ruleTester.run("use-isnan", rule, { options: [{ enforceForIndexOf: true }] }, { - code: "foo.lastIndexOf(NaN, b)", + code: "foo.lastIndexOf(NaN, b, c)", options: [{ enforceForIndexOf: true }] }, { @@ -271,7 +271,7 @@ ruleTester.run("use-isnan", rule, { options: [{ enforceForIndexOf: true }] }, { - code: "foo.lastIndexOf(NaN, NaN)", + code: "foo.lastIndexOf(NaN, NaN, b)", options: [{ enforceForIndexOf: true }] }, { @@ -340,11 +340,11 @@ ruleTester.run("use-isnan", rule, { options: [{ enforceForIndexOf: true }] }, { - code: "foo.lastIndexOf(Number.NaN, b)", + code: "foo.lastIndexOf(Number.NaN, b, c)", options: [{ enforceForIndexOf: true }] }, { - code: "foo.lastIndexOf(Number.NaN, NaN)", + code: "foo.lastIndexOf(Number.NaN, NaN, b)", options: [{ enforceForIndexOf: true }] }, { @@ -1289,6 +1289,86 @@ ruleTester.run("use-isnan", rule, { data: { methodName: "lastIndexOf" }, suggestions: [] }] + }, + { + code: "foo.indexOf(NaN, 1)", + options: [{ enforceForIndexOf: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ + messageId: "indexOfNaN", + data: { methodName: "indexOf" }, + suggestions: [] + }] + }, + { + code: "foo.lastIndexOf(NaN, 1)", + options: [{ enforceForIndexOf: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ + messageId: "indexOfNaN", + data: { methodName: "lastIndexOf" }, + suggestions: [] + }] + }, + { + code: "foo.indexOf(NaN, b)", + options: [{ enforceForIndexOf: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ + messageId: "indexOfNaN", + data: { methodName: "indexOf" }, + suggestions: [] + }] + }, + { + code: "foo.lastIndexOf(NaN, b)", + options: [{ enforceForIndexOf: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ + messageId: "indexOfNaN", + data: { methodName: "lastIndexOf" }, + suggestions: [] + }] + }, + { + code: "foo.indexOf(Number.NaN, b)", + options: [{ enforceForIndexOf: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ + messageId: "indexOfNaN", + data: { methodName: "indexOf" }, + suggestions: [] + }] + }, + { + code: "foo.lastIndexOf(Number.NaN, b)", + options: [{ enforceForIndexOf: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ + messageId: "indexOfNaN", + data: { methodName: "lastIndexOf" }, + suggestions: [] + }] + }, + { + code: "foo.lastIndexOf(NaN, NaN)", + options: [{ enforceForIndexOf: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ + messageId: "indexOfNaN", + data: { methodName: "lastIndexOf" }, + suggestions: [] + }] + }, + { + code: "foo.indexOf((1, NaN), 1)", + options: [{ enforceForIndexOf: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ + messageId: "indexOfNaN", + data: { methodName: "indexOf" }, + suggestions: [] + }] } ] }); From de408743b5c3fc25ebd7ef5fb11ab49ab4d06c36 Mon Sep 17 00:00:00 2001 From: Mara Kiefer <8320933+mnkiefer@users.noreply.github.com> Date: Mon, 25 Mar 2024 19:14:19 +0100 Subject: [PATCH 040/166] feat: Rule Performance Statistics for flat ESLint (#17850) * Add stats option * Remove resetTimes, stats per file * Add suggestions from review * Update types & docs * Update times description * Add Stats Data page * Fix file verification issues * Use named exports * Update docs/src/extend/stats.md Co-authored-by: Milos Djermanovic * Update docs/src/extend/stats.md Co-authored-by: Milos Djermanovic * Update docs/src/extend/stats.md Co-authored-by: Milos Djermanovic * Update docs/src/extend/stats.md Co-authored-by: Milos Djermanovic * Update docs/src/extend/stats.md Co-authored-by: Milos Djermanovic * Update docs/src/extend/stats.md Co-authored-by: Milos Djermanovic * Update docs/src/extend/stats.md Co-authored-by: Milos Djermanovic * Add suggestions and new data * Update lib/shared/stats.js Co-authored-by: Milos Djermanovic * Update lib/shared/stats.js Co-authored-by: Milos Djermanovic * Update docs/src/extend/stats.md Co-authored-by: Milos Djermanovic * Update lib/shared/types.js Co-authored-by: Milos Djermanovic * Update docs/src/integrate/nodejs-api.md Co-authored-by: Milos Djermanovic * Update docs/src/extend/stats.md Co-authored-by: Milos Djermanovic * Update docs/src/extend/stats.md Co-authored-by: Milos Djermanovic * Update docs/src/extend/stats.md Co-authored-by: Milos Djermanovic * Update docs/src/extend/stats.md Co-authored-by: Milos Djermanovic * Update lib/linter/linter.js Co-authored-by: Milos Djermanovic * Update lib/linter/linter.js Co-authored-by: Milos Djermanovic * Update lib/linter/linter.js Co-authored-by: Milos Djermanovic * Update lib/linter/linter.js Co-authored-by: Milos Djermanovic * Update lib/linter/linter.js Co-authored-by: Milos Djermanovic * Update tests/lib/eslint/eslint.js Co-authored-by: Milos Djermanovic * Update tests/fixtures/stats-example/file-to-fix.js Co-authored-by: Milos Djermanovic * Update docs/src/integrate/nodejs-api.md Co-authored-by: Milos Djermanovic * Update docs/src/use/command-line-interface.md Co-authored-by: Milos Djermanovic * Fix missing bracket * Update docs/src/use/command-line-interface.md Co-authored-by: Milos Djermanovic * Update stats tests * Update tests/lib/eslint/eslint.js Co-authored-by: Milos Djermanovic * Update docs/src/integrate/nodejs-api.md Co-authored-by: Milos Djermanovic * Fix missing properties * Remove trailing spaces * Update nodejs-api.md * Update docs/src/extend/stats.md Co-authored-by: Nicholas C. Zakas * Update docs/src/extend/stats.md Co-authored-by: Nicholas C. Zakas * Update docs/src/extend/stats.md Co-authored-by: Nicholas C. Zakas * Update docs/src/extend/stats.md Co-authored-by: Nicholas C. Zakas * Update stats.md * Add more docs --------- Co-authored-by: Milos Djermanovic Co-authored-by: Nicholas C. Zakas --- docs/src/extend/custom-formatters.md | 1 + docs/src/extend/custom-rules.md | 2 + docs/src/extend/stats.md | 139 +++++++++++++++++ docs/src/integrate/nodejs-api.md | 12 ++ docs/src/use/command-line-interface.md | 15 ++ lib/cli.js | 2 + lib/eslint/eslint-helpers.js | 5 + lib/eslint/eslint.js | 17 +- lib/linter/linter.js | 163 ++++++++++++++++++-- lib/linter/timing.js | 24 ++- lib/options.js | 15 +- lib/shared/stats.js | 30 ++++ lib/shared/types.js | 34 ++++ tests/fixtures/stats-example/file-to-fix.js | 5 + tests/lib/eslint/eslint.js | 68 ++++++++ tests/lib/options.js | 8 + 16 files changed, 521 insertions(+), 19 deletions(-) create mode 100644 docs/src/extend/stats.md create mode 100644 lib/shared/stats.js create mode 100644 tests/fixtures/stats-example/file-to-fix.js diff --git a/docs/src/extend/custom-formatters.md b/docs/src/extend/custom-formatters.md index b95db0f8bd6..4513b42e000 100644 --- a/docs/src/extend/custom-formatters.md +++ b/docs/src/extend/custom-formatters.md @@ -98,6 +98,7 @@ Each object in the `results` array is a `result` object. Each `result` object co * **messages**: An array of [`message`](#the-message-object) objects. See below for more info about messages. * **errorCount**: The number of errors for the given file. * **warningCount**: The number of warnings for the given file. +* **stats**: The optional [`stats`](./stats#-stats-type) object that only exists when the `stats` option is used. * **source**: The source code for the given file. This property is omitted if this file has no errors/warnings or if the `output` property is present. * **output**: The source code for the given file with as many fixes applied as possible. This property is omitted if no fix is available. diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index 9b99121d22e..84d2ca46a6f 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -938,3 +938,5 @@ quotes | 18.066 | 100.0% ``` To see a longer list of results (more than 10), set the environment variable to another value such as `TIMING=50` or `TIMING=all`. + +For more granular timing information (per file per rule), use the [`stats`](./stats) option instead. diff --git a/docs/src/extend/stats.md b/docs/src/extend/stats.md new file mode 100644 index 00000000000..3e54598ab6f --- /dev/null +++ b/docs/src/extend/stats.md @@ -0,0 +1,139 @@ +--- +title: Stats Data +eleventyNavigation: + key: stats data + parent: extend eslint + title: Stats Data + order: 6 +--- + +While an analysis of the overall rule performance for an ESLint run can be carried out by setting the [TIMING](./custom-rules#profile-rule-performance) environment variable, it can sometimes be useful to acquire more *granular* timing data (lint time per file per rule) or collect other measures of interest. In particular, when developing new [custom plugins](./plugins) and evaluating/benchmarking new languages or rule sets. For these use cases, you can optionally collect runtime statistics from ESLint. + +## Enable stats collection + +To enable collection of statistics, you can either: + +1. Use the `--stats` CLI option. This will pass the stats data into the formatter used to output results from ESLint. (Note: not all formatters output stats data.) +1. Set `stats: true` as an option on the `ESLint` constructor. + +Enabling stats data adds a new `stats` key to each [LintResult](../integrate/nodejs-api#-lintresult-type) object containing data such as parse times, fix times, lint times per rule. + +As such, it is not available via stdout but made easily ingestible via a formatter using the CLI or via the Node.js API to cater to your specific needs. + +## ◆ Stats type + +The `Stats` value is the timing information of each lint run. The `stats` property of the [LintResult](../integrate/nodejs-api#-lintresult-type) type contains it. It has the following properties: + +* `fixPasses` (`number`)
+ The number of times ESLint has applied at least one fix after linting. +* `times` (`{ passes: TimePass[] }`)
+ The times spent on (parsing, fixing, linting) a file, where the linting refers to the timing information for each rule. + * `TimePass` (`{ parse: ParseTime, rules?: Record, fix: FixTime, total: number }`)
+ An object containing the times spent on (parsing, fixing, linting) + * `ParseTime` (`{ total: number }`)
+ The total time that is spent when parsing a file. + * `RuleTime` (`{ total: number }`) + The total time that is spent on a rule. + * `FixTime` (`{ total: number }`) + The total time that is spent on applying fixes to the code. + +### CLI usage + +Let's consider the following example: + +```js [file-to-fix.js] +/*eslint no-regex-spaces: "error", wrap-regex: "error"*/ + +function a() { + return / foo/.test("bar"); +} +``` + +Run ESLint with `--stats` and output to JSON via the built-in [`json` formatter](../use/formatters/): + +```bash +npx eslint file-to-fix.js --fix --stats -f json +``` + +This yields the following `stats` entry as part of the formatted lint results object: + +```json +{ + "times": { + "passes": [ + { + "parse": { + "total": 3.975959 + }, + "rules": { + "no-regex-spaces": { + "total": 0.160792 + }, + "wrap-regex": { + "total": 0.422626 + } + }, + "fix": { + "total": 0.080208 + }, + "total": 12.765959 + }, + { + "parse": { + "total": 0.623542 + }, + "rules": { + "no-regex-spaces": { + "total": 0.043084 + }, + "wrap-regex": { + "total": 0.007959 + } + }, + "fix": { + "total": 0 + }, + "total": 1.148875 + } + ] + }, + "fixPasses": 1 +} +``` + +Note, that for the simple example above, the sum of all rule times should be directly comparable to the first column of the TIMING output. Running the same command with `TIMING=all`, you can verify this: + +```bash +$ TIMING=all npx eslint file-to-fix.js --fix --stats -f json +... +Rule | Time (ms) | Relative +:---------------|----------:|--------: +wrap-regex | 0.431 | 67.9% +no-regex-spaces | 0.204 | 32.1% +``` + +### API Usage + +You can achieve the same thing using the Node.js API by passing`stats: true` as an option to the `ESLint` constructor. For example: + +```js +const { ESLint } = require("eslint"); + +(async function main() { + // 1. Create an instance. + const eslint = new ESLint({ stats: true, fix: true }); + + // 2. Lint files. + const results = await eslint.lintFiles(["file-to-fix.js"]); + + // 3. Format the results. + const formatter = await eslint.loadFormatter("json"); + const resultText = formatter.format(results); + + // 4. Output it. + console.log(resultText); +})().catch((error) => { + process.exitCode = 1; + console.error(error); +}); +``` diff --git a/docs/src/integrate/nodejs-api.md b/docs/src/integrate/nodejs-api.md index fdc06daf12f..352d0a28f0a 100644 --- a/docs/src/integrate/nodejs-api.md +++ b/docs/src/integrate/nodejs-api.md @@ -150,6 +150,8 @@ The `ESLint` constructor takes an `options` object. If you omit the `options` ob Default is `null`. The plugin implementations that ESLint uses for the `plugins` setting of your configuration. This is a map-like object. Those keys are plugin IDs and each value is implementation. * `options.ruleFilter` (`({ruleId: string, severity: number}) => boolean`)
Default is `() => true`. A predicate function that filters rules to be run. This function is called with an object containing `ruleId` and `severity`, and returns `true` if the rule should be run. +* `options.stats` (`boolean`)
+ Default is `false`. When set to `true`, additional statistics are added to the lint results (see [Stats type](../extend/stats#-stats-type)). ##### Autofix @@ -367,6 +369,8 @@ The `LintResult` value is the information of the linting result of each file. Th The modified source code text. This property is undefined if any fixable messages didn't exist. * `source` (`string | undefined`)
The original source code text. This property is undefined if any messages didn't exist or the `output` property exists. +* `stats` (`Stats | undefined`)
+ The [Stats](../extend/stats#-stats-type) object. This contains the lint performance statistics collected with the `stats` option. * `usedDeprecatedRules` (`{ ruleId: string; replacedBy: string[] }[]`)
The information about the deprecated rules that were used to check this file. @@ -715,6 +719,14 @@ const Linter = require("eslint").Linter; Linter.version; // => '9.0.0' ``` +### Linter#getTimes() + +This method is used to get the times spent on (parsing, fixing, linting) a file. See `times` property of the [Stats](../extend/stats#-stats-type) object. + +### Linter#getFixPassCount() + +This method is used to get the number of autofix passes made. See `fixPasses` property of the [Stats](../extend/stats#-stats-type) object. + --- ## RuleTester diff --git a/docs/src/use/command-line-interface.md b/docs/src/use/command-line-interface.md index f1ceddfd8f3..d4ec452611a 100644 --- a/docs/src/use/command-line-interface.md +++ b/docs/src/use/command-line-interface.md @@ -126,6 +126,7 @@ Miscellaneous: -h, --help Show help -v, --version Output the version number --print-config path::String Print the configuration for the given file + --stats Add statistics to the lint report - default: false ``` ### Basic Configuration @@ -811,6 +812,20 @@ This option outputs the configuration to be used for the file passed. When prese npx eslint --print-config file.js ``` +#### `--stats` + +This option adds a series of detailed performance statistics (see [Stats type](../extend/stats#-stats-type)) such as the *parse*-, *fix*- and *lint*-times (time per rule) to [`result`](../extend/custom-formatters#the-result-object) objects that are passed to the formatter (see [Stats CLI usage](../extend/stats#cli-usage)). + +* **Argument Type**: No argument. + +This option is intended for use with custom formatters that display statistics. It can also be used with the built-in `json` formatter. + +##### `--stats` example + +```shell +npx eslint --stats --format json file.js +``` + ## Exit Codes When linting files, ESLint exits with one of the following exit codes: diff --git a/lib/cli.js b/lib/cli.js index 20a007cbc7b..24d72d6e21a 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -131,6 +131,7 @@ async function translateOptions({ resolvePluginsRelativeTo, rule, rulesdir, + stats, warnIgnored, passOnNoPatterns, maxWarnings @@ -222,6 +223,7 @@ async function translateOptions({ if (configType === "flat") { options.ignorePatterns = ignorePattern; + options.stats = stats; options.warnIgnored = warnIgnored; /* diff --git a/lib/eslint/eslint-helpers.js b/lib/eslint/eslint-helpers.js index 0206177831c..44a5a253cd6 100644 --- a/lib/eslint/eslint-helpers.js +++ b/lib/eslint/eslint-helpers.js @@ -685,6 +685,7 @@ function processOptions({ overrideConfig = null, overrideConfigFile = null, plugins = {}, + stats = false, warnIgnored = true, passOnNoPatterns = false, ruleFilter = () => true, @@ -791,6 +792,9 @@ 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."); } @@ -818,6 +822,7 @@ function processOptions({ globInputPaths, ignore, ignorePatterns, + stats, passOnNoPatterns, warnIgnored, ruleFilter diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 42b8ddd2410..b4c38503a6e 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -84,6 +84,7 @@ const LintResultCache = require("../cli-engine/lint-result-cache"); * 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. @@ -465,6 +466,7 @@ async function calculateConfigArray(eslint, { * @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 @@ -477,6 +479,7 @@ function verifyText({ fix, allowInlineConfig, ruleFilter, + stats, linter }) { const filePath = providedFilePath || ""; @@ -497,6 +500,7 @@ function verifyText({ filename: filePathToVerify, fix, ruleFilter, + stats, /** * Check if the linter should adopt a given code block or not. @@ -528,6 +532,13 @@ function verifyText({ result.source = text; } + if (stats) { + result.stats = { + times: linter.getTimes(), + fixPasses: linter.getFixPassCount() + }; + } + return result; } @@ -808,6 +819,7 @@ class ESLint { fix, fixTypes, ruleFilter, + stats, globInputPaths, errorOnUnmatchedPattern, warnIgnored @@ -922,6 +934,7 @@ class ESLint { fix: fixer, allowInlineConfig, ruleFilter, + stats, linter }); @@ -1010,7 +1023,8 @@ class ESLint { cwd, fix, warnIgnored: constructorWarnIgnored, - ruleFilter + ruleFilter, + stats } = eslintOptions; const results = []; const startTime = Date.now(); @@ -1034,6 +1048,7 @@ class ESLint { fix, allowInlineConfig, ruleFilter, + stats, linter })); } diff --git a/lib/linter/linter.js b/lib/linter/linter.js index 8ae8ad367a3..22cf17b54ba 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -41,6 +41,7 @@ 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 { assertIsRuleSeverity } = require("../config/flat-config-schema"); const { normalizeSeverityToString } = require("../shared/severity"); @@ -68,6 +69,7 @@ const STEP_KIND_CALL = 2; /** @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 STEP_KIND_CALL = 2; * @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. */ @@ -736,6 +739,7 @@ function normalizeVerifyOptions(providedOptions, config) { : null, reportUnusedDisableDirectives, disableFixes: Boolean(providedOptions.disableFixes), + stats: providedOptions.stats, ruleFilter }; } @@ -825,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 @@ -986,10 +1020,13 @@ function createRuleListeners(rule, ruleContext) { * @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, ruleFilter) { +function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename, ruleFilter, + stats, slots) { const emitter = createEmitter(); // must happen first to assign all node.parent properties @@ -1088,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 @@ -1098,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; @@ -1112,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, @@ -1236,7 +1287,6 @@ function assertEslintrcConfig(linter) { } } - //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -1342,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]; } @@ -1398,7 +1461,9 @@ class Linter { options.disableFixes, slots.cwd, providedOptions.physicalFilename, - null + null, + options.stats, + slots ); } catch (err) { err.message += `\nOccurred while linting ${options.filename}`; @@ -1626,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]; } @@ -1841,7 +1918,9 @@ class Linter { options.disableFixes, slots.cwd, providedOptions.physicalFilename, - options.ruleFilter + options.ruleFilter, + options.stats, + slots ); } catch (err) { err.message += `\nOccurred while linting ${options.filename}`; @@ -2081,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 @@ -2157,6 +2252,7 @@ class Linter { 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: @@ -2167,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. @@ -2190,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 @@ -2200,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/lib/linter/timing.js b/lib/linter/timing.js index 1076ff25887..232c5f4f2fb 100644 --- a/lib/linter/timing.js +++ b/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/lib/options.js b/lib/options.js index 2f5380148a7..135e3f2d760 100644 --- a/lib/options.js +++ b/lib/options.js @@ -60,6 +60,7 @@ const optionator = require("optionator"); * @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 */ //------------------------------------------------------------------------------ @@ -143,6 +144,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) { @@ -400,7 +412,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/lib/shared/stats.js b/lib/shared/stats.js new file mode 100644 index 00000000000..c5d4d1885d4 --- /dev/null +++ b/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/lib/shared/types.js b/lib/shared/types.js index 15666d1c23f..f4186dd96ad 100644 --- a/lib/shared/types.js +++ b/lib/shared/types.js @@ -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/tests/fixtures/stats-example/file-to-fix.js b/tests/fixtures/stats-example/file-to-fix.js new file mode 100644 index 00000000000..104fca51a11 --- /dev/null +++ b/tests/fixtures/stats-example/file-to-fix.js @@ -0,0 +1,5 @@ +/*eslint wrap-regex: "error"*/ + +function a() { + return / foo/.test("bar"); +} diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 39eea5787b6..9eb1913c7d1 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -5060,6 +5060,74 @@ describe("ESLint", () => { }); + describe("Use stats option", () => { + + /** + * Check if the given number is a number. + * @param {number} n The number to check. + * @returns {boolean} `true` if the number is a number, `false` otherwise. + */ + function isNumber(n) { + return typeof n === "number" && !Number.isNaN(n); + } + + it("should report stats", async () => { + const engine = new ESLint({ + overrideConfigFile: true, + overrideConfig: { + rules: { + "no-regex-spaces": "error" + } + }, + cwd: getFixturePath("stats-example"), + stats: true + }); + const results = await engine.lintFiles(["file-to-fix.js"]); + + assert.strictEqual(results[0].stats.fixPasses, 0); + assert.strictEqual(results[0].stats.times.passes.length, 1); + assert.strictEqual(isNumber(results[0].stats.times.passes[0].parse.total), true); + assert.strictEqual(isNumber(results[0].stats.times.passes[0].rules["no-regex-spaces"].total), true); + assert.strictEqual(isNumber(results[0].stats.times.passes[0].rules["wrap-regex"].total), true); + assert.strictEqual(results[0].stats.times.passes[0].fix.total, 0); + assert.strictEqual(isNumber(results[0].stats.times.passes[0].total), true); + }); + + it("should report stats with fix", async () => { + const engine = new ESLint({ + overrideConfigFile: true, + overrideConfig: { + rules: { + "no-regex-spaces": "error" + } + }, + cwd: getFixturePath("stats-example"), + fix: true, + stats: true + }); + const results = await engine.lintFiles(["file-to-fix.js"]); + + assert.strictEqual(results[0].stats.fixPasses, 2); + assert.strictEqual(results[0].stats.times.passes.length, 3); + assert.strictEqual(isNumber(results[0].stats.times.passes[0].parse.total), true); + assert.strictEqual(isNumber(results[0].stats.times.passes[1].parse.total), true); + assert.strictEqual(isNumber(results[0].stats.times.passes[2].parse.total), true); + assert.strictEqual(isNumber(results[0].stats.times.passes[0].rules["no-regex-spaces"].total), true); + assert.strictEqual(isNumber(results[0].stats.times.passes[0].rules["wrap-regex"].total), true); + assert.strictEqual(isNumber(results[0].stats.times.passes[1].rules["no-regex-spaces"].total), true); + assert.strictEqual(isNumber(results[0].stats.times.passes[1].rules["wrap-regex"].total), true); + assert.strictEqual(isNumber(results[0].stats.times.passes[2].rules["no-regex-spaces"].total), true); + assert.strictEqual(isNumber(results[0].stats.times.passes[2].rules["wrap-regex"].total), true); + assert.strictEqual(isNumber(results[0].stats.times.passes[0].fix.total), true); + assert.strictEqual(isNumber(results[0].stats.times.passes[1].fix.total), true); + assert.strictEqual(results[0].stats.times.passes[2].fix.total, 0); + assert.strictEqual(isNumber(results[0].stats.times.passes[0].total), true); + assert.strictEqual(isNumber(results[0].stats.times.passes[1].total), true); + assert.strictEqual(isNumber(results[0].stats.times.passes[2].total), true); + }); + + }); + describe("getRulesMetaForResults()", () => { it("should throw an error when this instance did not lint any files", async () => { diff --git a/tests/lib/options.js b/tests/lib/options.js index 781f8250634..a75381f7241 100644 --- a/tests/lib/options.js +++ b/tests/lib/options.js @@ -437,4 +437,12 @@ describe("options", () => { }); }); + describe("--stats", () => { + it("should return true --stats is passed", () => { + const currentOptions = flatOptions.parse("--stats"); + + assert.isTrue(currentOptions.stats); + }); + }); + }); From dadc5bf843a7181b9724a261c7ac0486091207aa Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 25 Mar 2024 19:18:30 +0100 Subject: [PATCH 041/166] fix: `constructor-super` false positives with loops (#18226) --- lib/rules/constructor-super.js | 155 +++++++++++---------------- tests/lib/rules/constructor-super.js | 98 ++++++++++++++++- 2 files changed, 159 insertions(+), 94 deletions(-) diff --git a/lib/rules/constructor-super.js b/lib/rules/constructor-super.js index 7ded20f6075..6f46278f781 100644 --- a/lib/rules/constructor-super.js +++ b/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 @@ -165,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." } }, @@ -186,7 +169,7 @@ module.exports = { /** * @type {Record} */ - let segInfoMap = Object.create(null); + const segInfoMap = Object.create(null); /** * Gets the flag which shows `super()` is called in some paths. @@ -194,7 +177,7 @@ module.exports = { * @returns {boolean} The flag which shows `super()` is called in some paths */ function isCalledInSomePath(segment) { - return segment.reachable && segInfoMap[segment.id]?.calledInSomePaths; + return segment.reachable && segInfoMap[segment.id].calledInSomePaths; } /** @@ -212,17 +195,6 @@ module.exports = { * @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; } @@ -279,9 +251,9 @@ module.exports = { } // Reports if `super()` lacked. - const seenSegments = codePath.returnedSegments.filter(hasSegmentBeenSeen); - const calledInEveryPaths = seenSegments.every(isCalledInEveryPath); - const calledInSomePaths = seenSegments.some(isCalledInSomePath); + const returnedSegments = codePath.returnedSegments; + const calledInEveryPaths = returnedSegments.every(isCalledInEveryPath); + const calledInSomePaths = returnedSegments.some(isCalledInSomePath); if (!calledInEveryPaths) { context.report({ @@ -296,28 +268,38 @@ 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] = new SegmentInfo(); - // When there are previous segments, aggregates these. - const prevSegments = segment.prevSegments; - - if (prevSegments.length > 0) { - const seenPrevSegments = prevSegments.filter(hasSegmentBeenSeen); + const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen); + // When there are previous segments, aggregates these. + if (seenPrevSegments.length > 0) { info.calledInSomePaths = seenPrevSegments.some(isCalledInSomePath); info.calledInEveryPaths = seenPrevSegments.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; + } }, onUnreachableCodePathSegmentStart(segment) { @@ -343,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 => { - const info = segInfoMap[segment.id] ?? new SegmentInfo(); + (segment, controller) => { + const info = segInfoMap[segment.id]; + + // 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); - // Updates flags. - info.calledInSomePaths = seenPrevSegments.some(isCalledInSomePath); - info.calledInEveryPaths = 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 = []; @@ -375,9 +362,6 @@ module.exports = { }); } } - - // save just in case we created a new SegmentInfo object - segInfoMap[segment.id] = info; } ); }, @@ -388,7 +372,7 @@ module.exports = { * @returns {void} */ "CallExpression:exit"(node) { - if (!(funcInfo && funcInfo.isConstructor)) { + if (!(funcInfo.isConstructor && funcInfo.hasExtends)) { return; } @@ -398,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 - }); } }, @@ -442,7 +419,7 @@ module.exports = { * @returns {void} */ ReturnStatement(node) { - if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { + if (!(funcInfo.isConstructor && funcInfo.hasExtends)) { return; } @@ -462,14 +439,6 @@ module.exports = { info.calledInSomePaths = info.calledInEveryPaths = true; } } - }, - - /** - * Resets state. - * @returns {void} - */ - "Program:exit"() { - segInfoMap = Object.create(null); } }; } diff --git a/tests/lib/rules/constructor-super.js b/tests/lib/rules/constructor-super.js index 70fd14a8859..9268a13fb7d 100644 --- a/tests/lib/rules/constructor-super.js +++ b/tests/lib/rules/constructor-super.js @@ -72,6 +72,7 @@ ruleTester.run("constructor-super", rule, { // https://github.com/eslint/eslint/issues/5261 "class A extends B { constructor(a) { super(); for (const b of a) { this.a(); } } }", + "class A extends B { constructor(a) { super(); for (b in a) ( foo(b) ); } }", // https://github.com/eslint/eslint/issues/5319 "class Foo extends Object { constructor(method) { super(); this.method = method || function() {}; } }", @@ -85,6 +86,42 @@ ruleTester.run("constructor-super", rule, { " }", "}" ].join("\n"), + [ + "class A extends Object {", + " constructor() {", + " super();", + " for (; i < 0; i++);", + " }", + "}" + ].join("\n"), + [ + "class A extends Object {", + " constructor() {", + " super();", + " for (let i = 0;; i++) {", + " if (foo) break;", + " }", + " }", + "}" + ].join("\n"), + [ + "class A extends Object {", + " constructor() {", + " super();", + " for (let i = 0; i < 0;);", + " }", + "}" + ].join("\n"), + [ + "class A extends Object {", + " constructor() {", + " super();", + " for (let i = 0;;) {", + " if (foo) break;", + " }", + " }", + "}" + ].join("\n"), // https://github.com/eslint/eslint/issues/8848 ` @@ -103,7 +140,21 @@ ruleTester.run("constructor-super", rule, { `, // Optional chaining - "class A extends obj?.prop { constructor() { super(); } }" + "class A extends obj?.prop { constructor() { super(); } }", + + ` + class A extends Base { + constructor(list) { + for (const a of list) { + if (a.foo) { + super(a); + return; + } + } + super(); + } + } + ` ], invalid: [ @@ -168,6 +219,10 @@ ruleTester.run("constructor-super", rule, { code: "class A extends B { constructor() { for (var a of b) super.foo(); } }", errors: [{ messageId: "missingAll", type: "MethodDefinition" }] }, + { + code: "class A extends B { constructor() { for (var i = 1; i < 10; i++) super.foo(); } }", + errors: [{ messageId: "missingAll", type: "MethodDefinition" }] + }, // nested execution scope. { @@ -285,6 +340,47 @@ ruleTester.run("constructor-super", rule, { }`, errors: [{ messageId: "missingAll", type: "MethodDefinition" }] + }, + { + code: `class C extends D { + + constructor() { + for (let i = 1;;i++) { + if (bar) { + break; + } + } + } + + }`, + errors: [{ messageId: "missingAll", type: "MethodDefinition" }] + }, + { + code: `class C extends D { + + constructor() { + do { + super(); + } while (foo); + } + + }`, + errors: [{ messageId: "duplicate", type: "CallExpression" }] + }, + { + code: `class C extends D { + + constructor() { + while (foo) { + if (bar) { + super(); + break; + } + } + } + + }`, + errors: [{ messageId: "missingSome", type: "MethodDefinition" }] } ] }); From 778082d4fa5e2fc97549c9e5acaecc488ef928f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Tue, 26 Mar 2024 16:29:41 -0400 Subject: [PATCH 042/166] docs: add Glossary page (#18187) * docs: add Glossary page * Update docs/src/use/core-concepts/glossary.md Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nicholas C. Zakas Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * A large set of touchups * One small typo fix * Apply suggestions from code review Co-authored-by: Nicholas C. Zakas * Two more (new)s * Apply suggestions from code review Co-authored-by: Milos Djermanovic * Missing comment --------- Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Co-authored-by: Nicholas C. Zakas Co-authored-by: Milos Djermanovic --- docs/src/use/core-concepts/glossary.md | 403 ++++++++++++++++++ .../index.md} | 22 +- 2 files changed, 414 insertions(+), 11 deletions(-) create mode 100644 docs/src/use/core-concepts/glossary.md rename docs/src/use/{core-concepts.md => core-concepts/index.md} (84%) diff --git a/docs/src/use/core-concepts/glossary.md b/docs/src/use/core-concepts/glossary.md new file mode 100644 index 00000000000..7faf4aae5a1 --- /dev/null +++ b/docs/src/use/core-concepts/glossary.md @@ -0,0 +1,403 @@ +--- +title: Glossary +eleventyNavigation: + key: glossary + title: Glossary + parent: core concepts +--- + +This page serves as a reference for common terms associated with ESLint. + +## A + +### Abstract Syntax Tree (AST) + +A structured representation of code syntax. + +Each section of source code in an AST is referred to as a [node](#node). +Each node may have any number of properties, including properties that store child nodes. + +The AST format used by ESLint is the [ESTree](#estree) format. + +ESLint [rules](#rule) are given an AST and may produce [violations](#violation) on parts of the AST when they detect a [violation](#violation). + +## C + +### Config File (Configuration File) + +A file containing preferences for how ESLint should parse files and run [rules](#rule). + +ESLint config files are named like `eslint.config.(c|m)js`. +Each config file exports a [config array](#config-array) containing [config objects](#config-object). + +For example, this `eslint.config.js` file enables the `prefer-const` [rule](#rule) at the _error_ [severity](#severity): + +```js +export default [ + { + rules: { + "prefer-const": "error", + }, + }, +]; +``` + +See [Configuration Files](../configure/configuration-files) for more details. + +### Config Array + +An array of [config objects](#config-object) within a [config file](#config-file-configuration-file). + +Each config file exports an array of config objects. +The objects in the array are evaluated in order: later objects may override settings specified in earlier objects. + +See [Configuration Files](../configure/configuration-files) for more details. + +### Config Object + +A [config file](#config-file-configuration-file) entry specifying all of the information ESLint needs to execute on a set of files. + +Each configuration object may include properties describing which files to run on, how to handle different file types, which [plugins](#plugin) to include, and how to run [rules](#rule). + +See [Configuration Files > Configuration Objects](../configure/configuration-files#configuration-objects) for more details. + +## E + +### ESQuery + +The library used by ESLint to parse [selector](#selector) syntax for querying [nodes](#node) in an [AST](#abstract-syntax-tree-ast). + +ESQuery interprets CSS syntax for AST node properties. +Examples of ESQuery selectors include: + +* `BinaryExpression`: selects all nodes of type _BinaryExpression_ +* `BinaryExpression[operator='+']`: selects all _BinaryExpression_ nodes whose _operator_ is `+` +* `BinaryExpression > Literal[value=1]`: selects all _Literal_ nodes with _value_ `1` whose direct parent is a _BinaryExpression_ + +See [github.com/estools/esquery](https://github.com/estools/esquery) for more information on the ESQuery format. + +### ESTree + +The format used by ESLint for how to represent JavaScript syntax as an [AST](#abstract-syntax-tree-ast). + +For example, the ESTree representation of the code `1 + 2;` would be an object roughly like: + +```json +{ + "type": "ExpressionStatement", + "expression": { + "type": "BinaryExpression", + "left": { + "type": "Literal", + "value": 1, + "raw": "1" + }, + "operator": "+", + "right": { + "type": "Literal", + "value": 2, + "raw": "2" + } + } +} +``` + +[Static analysis](#static-analysis) tools such as ESLint typically operate by converting syntax into an AST in the ESTree format. + +See [github.com/estree/estree](https://github.com/estree/estree) for more information on the ESTree specification. + +## F + +### Fix + +An optional augmentation to a [rule](#rule) [violation](#violation) that describes how to automatically correct the violation. + +Fixes are generally "safe" to apply automatically: they shouldn't cause code behavior changes. +ESLint attempts to apply as many fixes as possible in a [report](#report) when run with the `--fix` flag, though there is no guarantee that all fixes will be applied. +Fixes may also be applied by common editor extensions. + +Rule violations may also include file changes that are unsafe and not automatically applied in the form of [suggestions](#suggestion). + +### Flat Config + +The current configuration file format for ESLint. + +Flat config files are named in the format `eslint.config.(c|m)?js`. +"Flat" config files are named as such because all nesting must be done in one configuration file. +In contrast, the ["Legacy" config format](#legacy-config) allowed nesting configuration files in sub-directories within a project. + +You can read more about the motivations behind flat configurations in [ESLint's new config system, Part 2: Introduction to flat config](https://eslint.org/blog/2022/08/new-config-system-part-2). + +### Formatter (Linting) + +A package that presents the [report](#report) generated by ESLint. + +ESLint ships with several built-in reporters, including `stylish` (default), `json`, and `html`. + +For more information, see [Formatters](../formatters). + +### Formatter (Tool) + +A [static analysis](#static-analysis) tool that quickly reformats code without changing its logic or names. + +Formatters generally only modify the "trivia" of code, such as semicolons, spacing, newlines, and whitespace in general. +Trivia changes generally don't modify the [AST](#abstract-syntax-tree-ast) of code. + +Common formatters in the ecosystem include [Prettier](https://prettier.io) and [dprint](https://dprint.dev). + +Note that although ESLint is a [linter](#linter) rather than a formatter, ESLint rules can also apply formatting changes to source code. +See [Formatting (Rule)](#formatting-rule) for more information on formatting rules. + +### Formatting (Rule) + +A rule that solely targets [formatting](#formatter-tool) concerns, such as semicolons and whitespace. +These rules don't change application logic and are a subset of [Stylistic rules](#stylistic-rule). + +ESLint no longer recommends formatting rules and previously deprecated its built-in formatting rules. +ESLint recommends instead using a dedicated formatter such as [Prettier](https://prettier.io) or [dprint](https://dprint.dev). +Alternately, the [ESLint Stylistic project](https://eslint.style) provides formatting-related lint rules. + +For more information, see [Deprecation of formatting rules](https://eslint.org/blog/2023/10/deprecating-formatting-rules). + +## G + +### Global Declaration + +A description to ESLint of a JavaScript [global variable](#global-variable) that should exist at runtime. + +Global declarations inform lint rules that check for proper uses of global variables. +For example, the [`no-undef` rule](../../rules/no-undef) will create a violation for references to global variables not defined in the configured list of globals. + +[Config files](#config-file-configuration-file) have globals defined as JavaScript objects. + +For information about configuring globals, see [Configure Language Options > Specifying Globals](../configure/language-options#specifying-globals). + +### Global Variable + +A runtime variable that exists in the global scope, meaning all modules and scripts have access to it. + +Global variables in JavaScript are declared on the `globalThis` object (generally aliased as `global` in Node.js and `window` in browsers). + +You can let ESLint know which global variables your code uses with [global declarations](#global-declaration). + +## I + +### Inline Config (Configuration Comment) + +A source code comment that configures a rule to a different severity and/or set of options. + +Inline configs use similar syntax as [config files](#config-file-configuration-file) to specify any number of rules by name, their new severity, and optionally new options for the rules. +For example, the following inline config comment simultaneously disables the `eqeqeq` rule and sets the `curly` rule to `"error"`: + +```js +/* eslint eqeqeq: "off", curly: "error" */ +``` + +For documentation on inline config comments, see [Rules > Using configuration comments](../configure/rules#using-configuration-comments). + +## L + +### Legacy Config + +The previous configuration file format for ESLint, now superseded by ["Flat" config](#flat-config). + +Legacy ESLint configurations are named in the format `.eslintrc.*` and allowed to be nested across files within sub-directories in a project. + +You can read more about the lifetime of legacy configurations in [ESLint's new config system, Part 1: Background](https://eslint.org/blog/2022/08/new-config-system-part-1). + +### Linter + +A [static analysis](#static-analysis) tool that can [report](#report) the results from running a set of [rules](#rule) on source code. +Each rule may report any number of [violations](#violation) in the source code. + +ESLint is a commonly used linter for JavaScript and other web technologies. + +Note that a _linter_ is separate from [formatters](#formatter-tool) and [type checkers](#type-checker). + +### Logical Rule + +A [rule](#rule) that inspects how code operates to find problems. + +Many logical rules look for likely crashes (e.g. [`no-undef`](../../rules/no-undef)), unintended behavior (e.g. [`no-sparse-arrays`](../../rules/no-sparse-arrays)), and unused code (e.g [`no-unused-vars`](../../rules/no-unused-vars)), + +You can see the full list of logical rules that ship with ESLint under [Rules > Possible Problems](../../rules/#possible-problems) + +## N + +### Node + +A section of code within an [AST](#abstract-syntax-tree-ast). + +Each node represents a type of syntax found in source code. +For example, the `1 + 2` in the AST for `1 + 2;` is a _BinaryExpression_. + +See [#esquery](#esquery) for the library ESLint uses to parse [selectors](#selector) that allow [rules](#rule) to search for nodes. + +## O + +### Override + +When a [config object](#config-object) or [inline config](#inline-config-configuration-comment) sets a new severity and/or rule options that supersede previously set severity and/or options. + +The following [config file](#config-file-configuration-file) overrides `no-unused-expressions` from `"error"` to `"off"` in `*.test.js` files: + +```js +export default [ + { + rules: { + "no-unused-expressions": "error" + } + }, + { + files: ["*.test.js"], + rules: { + "no-unused-expressions": "off" + } + } +]; +``` + +The following [inline config](#inline-config-configuration-comment) sets `no-unused-expressions` to `"error"`: + +```js +/* eslint no-unused-expressions: "error" */ +``` + +For more information on overrides in legacy configs, see [Configuration Files (Deprecated) > How do overrides work?](../configure/configuration-files-deprecated#how-do-overrides-work). + +## P + +### Parser + +An object containing a method that reads in a string and converts it to a standardized format. + +ESLint uses parsers to convert source code strings into an [AST](#abstract-syntax-tree-ast) shape. +By default, ESLint uses the [Espree](https://github.com/eslint/espree) parser, which generates an AST compatible with standard JavaScript runtimes and versions. + +Custom parsers let ESLint parse non-standard JavaScript syntax. +Often custom parsers are included as part of shareable configurations or plugins, so you don’t have to use them directly. +For example, [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser) is a custom parser included in the [typescript-eslint](https://typescript-eslint.io) project that lets ESLint parse TypeScript code. + +For more information on using parsers with ESLint, see [Configure a Parser](../configure/parser). + +### Plugin + +A package that can contain a set of [configurations](#shareable-config-configuration), [processors](#processor), and/or [rules](#rule). + +A popular use case for plugins is to enforce best practices for a framework. +For example, [@angular-eslint/eslint-plugin](https://www.npmjs.com/package/@angular-eslint/eslint-plugin) contains best practices for using the Angular framework. + +For more information, refer to [Configure Plugins](../configure/plugins). + +### Processor + +A part of a plugin that extracts JavaScript code from other kinds of files, then lets ESLint lint the JavaScript code. + +For example, [`eslint-plugin-markdown`](https://github.com/eslint/eslint-plugin-markdown) includes a processor that converts the text of ``` code blocks in Markdown files into code that can be linted. + +For more information on configuring processor, see [Plugins > Specify a Processor](../configure/plugins#specify-a-processor). + +## R + +### Report + +A collection of [violations](#violation) from a single ESLint run. + +When ESLint runs on source files, it will pass an [AST](#abstract-syntax-tree-ast) for each source file to each configured [rule](#rule). +The collection of violations from each of the rules will be packaged together and passed to a [formatter](#formatter-linting) to be presented to the user. + +### Rule + +Code that checks an [AST](#abstract-syntax-tree-ast) for expected patterns. When a rule's expectation is not met, it creates a [violation](#violation). + +ESLint provides a large collection of rules that check for common JavaScript code issues. +Many more rules may be loaded in by [plugins](#plugin). + +For an overview of rules provided, see [Core Concepts > Rules](../core-concepts/#rules). + +## S + +### Selector + +Syntax describing how to search for [nodes](#node) within an [AST](#abstract-syntax-tree-ast). + +ESLint [rules](#rule) use [ESQuery](#esquery) selectors to find nodes that should be checked. + +### Severity + +What level of reporting a rule is configured to run, if at all. + +ESLint supports three levels of severity: + +* `"off"` (`0`): Do not run the rule. +* `"warn"` (`1`): Run the rule, but don't exit with a non-zero status code based on its violations (excluding the [`--max-warnings` flag](../command-line-interface#--max-warnings)) +* `"error"` (`2`): Run the rule, and exit with a non-zero status code if it produces any violations + +For documentation on configuring rules, see [Configure Rules](../configure/rules). + +### Shareable Config (Configuration) + +A module that provides a predefined [config file](#config-file-configuration-file) configurations. + +Shareable configs can configure all the same information from config files, including [plugins](#plugin) and [rules](#rule). + +Shareable configs are often provided alongside [plugins](#plugin). +Many plugins provide configs with names like _"recommended"_ that enable their suggested starting set of rules. +For example, [`eslint-plugin-solid`](https://github.com/solidjs-community/eslint-plugin-solid) provides a shareable recommended config: + +```js +import js from "@eslint/js"; +import solid from "eslint-plugin-solid/configs/recommended"; + +export default [js.configs.recommended, solid]; +``` + +For information on shareable configs, see [Share Configurations](../../extend/shareable-configs). + +### Static Analysis + +The process of analyzing source code without building or running it. + +[Linters](#linter) such as ESLint, [formatters](#formatter-tool), and [type checkers](#type-checker) are examples of static analysis tools. + +Static analysis is different from _dynamic_ analysis, which is the process of evaluating source code after it is built and executed. +Unit, integration, and end-to-end tests are common examples of dynamic analysis. + +### Stylistic (Rule) + +A rule that enforces a preference rather than a logical issue. +Stylistic areas include [Formatting rules](#formatting-rule), naming conventions, and consistent choices between equivalent syntaxes. + +ESLint's built-in stylistic rules are feature frozen: except for supporting new ECMAScript versions, they won't receive new features. + +For more information, see [Changes to our rules policies](https://eslint.org/blog/2020/05/changes-to-rules-policies) and [Deprecation of formatting rules](https://eslint.org/blog/2023/10/deprecating-formatting-rules). + +### Suggestion + +An optional augmentation to a [rule](#rule) [violation](#violation) that describes how one may manually adjust the code to address the violation. + +Suggestions are not generally safe to apply automatically because they cause code behavior changes. +ESLint does not apply suggestions directly but does provide suggestion to integrations that may choose to apply suggestions (such as an editor extension). + +Rule violations may also include file changes that are safe and may be automatically applied in the form of [fixes](#fix). + +## T + +### Type Checker + +A [static analysis](#static-analysis) tool that builds a full understanding of a project's code constructs and data shapes. + +Type checkers are generally slower and more comprehensive than linters. +Whereas linters traditionally operate only on a single file's or snippet's [AST](#abstract-syntax-tree-ast) at a time, type checkers understand cross-file dependencies and types. + +[TypeScript](https://typescriptlang.org) is the most common type checker for JavaScript. +The [typescript-eslint](https://typescript-eslint.io) project provides integrations that allow using type checker in lint rules. + +## V + +### Violation + +An indication from a [rule](#rule) that an area of code doesn't meet the expectation of the rule. + +Rule violations indicate a range in source code and error message explaining the violation. +Violations may also optionally include a [fix](#fix) and/or [suggestions](#suggestion) that indicate how to improve the violating code. diff --git a/docs/src/use/core-concepts.md b/docs/src/use/core-concepts/index.md similarity index 84% rename from docs/src/use/core-concepts.md rename to docs/src/use/core-concepts/index.md index eec5630e299..a4d0f8d2a04 100644 --- a/docs/src/use/core-concepts.md +++ b/docs/src/use/core-concepts/index.md @@ -17,19 +17,19 @@ ESLint is a configurable JavaScript linter. It helps you find and fix problems i Rules are the core building block of ESLint. A rule validates if your code meets a certain expectation, and what to do if it does not meet that expectation. Rules can also contain additional configuration options specific to that rule. -For example, the [`semi`](../rules/semi) rule lets you specify whether or not JavaScript statements should end with a semicolon (`;`). You can set the rule to either always require semicolons or require that a statement never ends with a semicolon. +For example, the [`semi`](../../rules/semi) rule lets you specify whether or not JavaScript statements should end with a semicolon (`;`). You can set the rule to either always require semicolons or require that a statement never ends with a semicolon. ESLint contains hundreds of built-in rules that you can use. You can also create custom rules or use rules that others have created with [plugins](#plugins). -For more information, refer to [Rules](../rules/). +For more information, refer to [Rules](../../rules/). ### Rule Fixes Rules may optionally provide fixes for violations that they find. Fixes safely correct the violation without changing application logic. -Fixes may be applied automatically with the [`--fix` command line option](command-line-interface#--fix) and via editor extensions. +Fixes may be applied automatically with the [`--fix` command line option](../command-line-interface#--fix) and via editor extensions. -Rules that may provide fixes are marked with 🔧 in [Rules](../rules/). +Rules that may provide fixes are marked with 🔧 in [Rules](../../rules/). ### Rule Suggestions @@ -38,13 +38,13 @@ Rules may optionally provide suggestions in addition to or instead of providing 1. Suggestions may change application logic and so cannot be automatically applied. 1. Suggestions cannot be applied through the ESLint CLI and are only available through editor integrations. -Rules that may provide suggestions are marked with 💡 in [Rules](../rules/). +Rules that may provide suggestions are marked with 💡 in [Rules](../../rules/). ## Configuration Files An ESLint configuration file is a place where you put the configuration for ESLint in your project. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. -For more information, refer to [Configuration Files](./configure/configuration-files). +For more information, refer to [Configuration Files](../configure/configuration-files). ## Shareable Configurations @@ -52,7 +52,7 @@ Shareable configurations are ESLint configurations that are shared via npm. Often shareable configurations are used to enforce style guides using ESLint's built-in rules. For example the sharable configuration [eslint-config-airbnb-base](https://www.npmjs.com/package/eslint-config-airbnb-base) implements the popular Airbnb JavaScript style guide. -For more information, refer to [Using a shareable configuration package](./configure/configuration-files#using-a-shareable-configuration-package). +For more information, refer to [Using a shareable configuration package](../configure/configuration-files#using-a-shareable-configuration-package). ## Plugins @@ -60,7 +60,7 @@ An ESLint plugin is an npm module that can contain a set of ESLint rules, config A popular use case for plugins is to enforce best practices for a framework. For example, [@angular-eslint/eslint-plugin](https://www.npmjs.com/package/@angular-eslint/eslint-plugin) contains best practices for using the Angular framework. -For more information, refer to [Configure Plugins](./configure/plugins). +For more information, refer to [Configure Plugins](../configure/plugins). ## Parsers @@ -80,13 +80,13 @@ For example, [eslint-plugin-markdown](https://github.com/eslint/eslint-plugin-ma An ESLint formatter controls the appearance of the linting results in the CLI. -For more information, refer to [Formatters](./formatters/). +For more information, refer to [Formatters](../formatters/). ## Integrations One of the things that makes ESLint such a useful tool is the ecosystem of integrations that surrounds it. For example, many code editors have ESLint extensions that show you the ESLint results of your code in the file as you work so that you don't need to use the ESLint CLI to see linting results. -For more information, refer to [Integrations](./integrations). +For more information, refer to [Integrations](../integrations). ## CLI & Node.js API @@ -96,4 +96,4 @@ The ESLint Node.js API lets you use ESLint programmatically from Node.js code. T Unless you are extending ESLint in some way, you should use the CLI. -For more information, refer to [Command Line Interface](./command-line-interface) and [Node.js API](../integrate/nodejs-api). +For more information, refer to [Command Line Interface](../command-line-interface) and [Node.js API](../../integrate/nodejs-api). From 9b7bd3be066ac1f72fa35c4d31a1b178c7e2b683 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:39:17 +0800 Subject: [PATCH 043/166] chore: update dependency markdownlint to ^0.34.0 (#18237) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 51b5f966a66..d6dbd97b2fe 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "load-perf": "^0.2.0", "markdown-it": "^12.2.0", "markdown-it-container": "^3.0.0", - "markdownlint": "^0.33.0", + "markdownlint": "^0.34.0", "markdownlint-cli": "^0.39.0", "marked": "^4.0.8", "metascraper": "^5.25.7", From a98babcda227649b2299d10e3f887241099406f7 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Sat, 30 Mar 2024 00:27:38 +0100 Subject: [PATCH 044/166] chore: add npm script to run WebdriverIO test (#18238) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: re-enable WebdriverIO broswer tests * add npm script to run WebdriverIO test * rename `test:wdio` → `test:browser` --- Makefile.js | 3 --- package.json | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Makefile.js b/Makefile.js index 36cd99348e4..120be8e3534 100644 --- a/Makefile.js +++ b/Makefile.js @@ -604,9 +604,6 @@ target.wdio = () => { target.test = function() { target.checkRuleFiles(); target.mocha(); - - // target.wdio(); // Temporarily disabled due to problems on Jenkins - target.fuzz({ amount: 150, fuzzBrokenAutofixes: false }); target.checkLicenses(); }; diff --git a/package.json b/package.json index d6dbd97b2fe..04f5ece1f90 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "release:generate:rc": "node Makefile.js generatePrerelease -- rc", "release:publish": "node Makefile.js publishRelease", "test": "node Makefile.js test", + "test:browser": "node Makefile.js wdio", "test:cli": "mocha", "test:fuzz": "node Makefile.js fuzz", "test:performance": "node Makefile.js perf" From b07d427826f81c2bdb683d04879093c687479edf Mon Sep 17 00:00:00 2001 From: Kirill Gavrilov Date: Sat, 30 Mar 2024 02:50:45 +0300 Subject: [PATCH 045/166] docs: fix typo (#18246) --- docs/src/use/configure/migration-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/use/configure/migration-guide.md b/docs/src/use/configure/migration-guide.md index 32f5c9f0fe7..ee1ece3a2a1 100644 --- a/docs/src/use/configure/migration-guide.md +++ b/docs/src/use/configure/migration-guide.md @@ -256,7 +256,7 @@ export default [ ]; ``` -A flag config example configuration supporting multiple configs for different glob patterns: +A flat config example configuration supporting multiple configs for different glob patterns: ```javascript // eslint.config.js From 77470973a0c2cae8ce07a456f2ad95896bc8d1d3 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 29 Mar 2024 17:06:08 -0700 Subject: [PATCH 046/166] docs: Update PR review process (#18233) * docs: Update PR review process * Update docs/src/maintain/review-pull-requests.md Co-authored-by: Amaresh S M * Update docs/src/maintain/review-pull-requests.md Co-authored-by: Amaresh S M * Update docs/src/maintain/review-pull-requests.md Co-authored-by: Amaresh S M * Clarify what to do for PRs with open issues --------- Co-authored-by: Amaresh S M --- docs/src/maintain/manage-issues.md | 2 + docs/src/maintain/review-pull-requests.md | 53 +++++++++++++++++------ 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/docs/src/maintain/manage-issues.md b/docs/src/maintain/manage-issues.md index 9e243c12e72..14abbdbb306 100644 --- a/docs/src/maintain/manage-issues.md +++ b/docs/src/maintain/manage-issues.md @@ -43,6 +43,8 @@ All of ESLint's issues and pull requests, across all GitHub repositories, are ma * **Blocked**: The issue can't move forward due to some dependency * **Ready to Implement**: These issues have all the details necessary to start implementation * **Implementing**: There is an open pull request for each of these issues or this is a pull request that has been approved +* **Second Review Needed**: Pull requests that already have one approval and the approver is requesting a second review before merging. +* **Merge Candidates**: Pull requests that already have at least one approval and at least one approver believes the pull request is ready to merge into the next release but would still like a TSC member to verify. * **Completed**: The issue has been closed (either via pull request merge or by the team manually closing the issue) We make every attempt to automate movement between as many columns as we can, but sometimes moving issues needs to be done manually. diff --git a/docs/src/maintain/review-pull-requests.md b/docs/src/maintain/review-pull-requests.md index c1711dfd65c..be6ae39f15e 100644 --- a/docs/src/maintain/review-pull-requests.md +++ b/docs/src/maintain/review-pull-requests.md @@ -26,7 +26,7 @@ The bot will add a comment specifying the problems that it finds. You do not nee Once the bot checks have been satisfied, you check the following: -1. Double-check that the commit message tag ("Fix:", "New:", etc.) is correct based on the issue (or, if no issue is referenced, based on the stated problem). +1. Double-check that the pull request title is correct based on the issue (or, if no issue is referenced, based on the stated problem). 1. If the pull request makes a change to core, ensure that an issue exists and the pull request references the issue in the commit message. 1. Does the code follow our conventions (including header comments, JSDoc comments, etc.)? If not, please leave that feedback and reference the [Code Conventions](../contribute/code-conventions) documentation. 1. For code changes: @@ -37,28 +37,55 @@ Once the bot checks have been satisfied, you check the following: **Note:** If you are a team member and you've left a comment on the pull request, please follow up to verify that your comments have been addressed. -## Who Can Merge a Pull Request +## Required Approvals for Pull Requests -TSC members, Reviewers, Committers, and Website Team Members may merge pull requests, depending on the contents of the pull request. +Any committer, reviewer, or TSC member may approve a pull request, but the approvals required for merging differ based on the type of pull request. -Website Team Members may merge a pull request in the `eslint.org` repository if it is: +One committer approval is required to merge a non-breaking change that is: 1. A documentation change +1. A bug fix (for either rules or core) 1. A dependency upgrade +1. Related to the build 1. A chore -Committers may merge a pull request if it is a non-breaking change and is: +For a non-breaking feature, pull requests require approval from one reviewer or TSC member, plus one additional approval from any other team member. + +For a breaking change, pull requests require an approval from two TSC members. + +::: important +If you approve a pull request and don't merge it, please leave a comment explaining why you didn't merge it. You might say something like, "LGTM. Waiting three days before merging." or "LGTM. Requires TSC member approval before merging" or "LGTM. Would like another review before merging." +::: + +## Moving a Pull Request Through the Triage Board + +When a pull request is created, whether by a team member or an outside contributor, it is placed in the "Needs Triage" column of the Triage board automatically. The pull request should remain in that column until a team member begins reviewing it. + +If the pull request does not have a related issue, then it should be moved through the normal [triage process for issues](./manage-issues) to be marked as accepted. Once accepted, move the pull request to the "Implementing" column. + +If the pull request does have a related issue, then: + +* If the issue is accepted, move the pull request to the "Implementing" column. +* If the issue is not accepted, move the pull request to the "Evaluating" column until the issue is marked as accepted, at which point move the pull request to "Implementing". + +Once the pull request has one approval, one of three things can happen: + +1. The pull request has the required approvals and the waiting period (see below) has passed so it can be merged. +1. The pull request has the required approvals and the waiting period has not passed, so it should be moved to the "Merge Candidates" column. +1. The pull request requires another approval before it can be merged, so it should be moved to the "Second Review Needed" column. + +When the pull request has a second approval, it should either be merged (if 100% ready) or moved to the "Merge Candidates" column if there are any outstanding concerns that should be reviewed before the next release. + +## Who Can Merge a Pull Request + +TSC members, reviewers, committers, and website team members may merge pull requests, depending on the contents of the pull request, once it has received the required approvals. + +Website Team Members may merge a pull request in the `eslint.org` repository if it is: 1. A documentation change -1. A bug fix (for either rules or core) 1. A dependency upgrade -1. Related to the build tool 1. A chore -In addition, committers may merge any non-breaking pull request if it has been approved by at least one TSC member. - -TSC members may merge all pull requests, including those that committers may merge. - ## When to Merge a Pull Request We use the "Merge" button to merge requests into the repository. Before merging a pull request, verify that: @@ -83,9 +110,7 @@ Otherwise, team members should observe a waiting period before merging a pull re The waiting period ensures that other team members have a chance to review the pull request before it is merged. -If the pull request was created from a branch on the `eslint/eslint` repository (as opposed to a fork), delete the branch after merging the pull request. (GitHub will display a "Delete branch" button after the pull request is merged.) - -**Note:** You should not merge your own pull request unless you're received feedback from at least one other team member. +**Note:** You should not merge your pull request unless you receive the required approvals. ## When to Close a Pull Request From 26384d3367e11bd4909a3330b72741742897fa1f Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sat, 30 Mar 2024 19:05:02 +0100 Subject: [PATCH 047/166] docs: fix `ecmaVersion` in one example, add checks (#18241) * docs: fix `ecmaVersion` in one example, add checks * restore trailing spaces --- docs/src/rules/strict.md | 2 +- tests/fixtures/bad-examples.md | 16 +++++++++++++++ tests/fixtures/good-examples.md | 32 ++++++++++++++++++++++++++++++ tests/tools/check-rule-examples.js | 5 ++++- tools/check-rule-examples.js | 30 ++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 2 deletions(-) diff --git a/docs/src/rules/strict.md b/docs/src/rules/strict.md index 51404e95bc2..3fb23efe62f 100644 --- a/docs/src/rules/strict.md +++ b/docs/src/rules/strict.md @@ -169,7 +169,7 @@ function foo() { ::: -::: incorrect { "ecmaVersion": 6, "sourceType": "script" } +::: incorrect { "ecmaVersion": 2015, "sourceType": "script" } ```js /*eslint strict: ["error", "function"]*/ diff --git a/tests/fixtures/bad-examples.md b/tests/fixtures/bad-examples.md index 147dfe38e15..efede633ea4 100644 --- a/tests/fixtures/bad-examples.md +++ b/tests/fixtures/bad-examples.md @@ -52,3 +52,19 @@ const foo = "baz"; ``` ::: + +:::correct { "ecmaVersion": "latest" } + +```js +/* eslint no-restricted-syntax: ["error", "ArrayPattern"] */ +``` + +::: + +:::correct { "ecmaVersion": 6 } + +```js +/* eslint no-restricted-syntax: ["error", "ArrayPattern"] */ +``` + +::: diff --git a/tests/fixtures/good-examples.md b/tests/fixtures/good-examples.md index c68dd09c907..eaed65d3d04 100644 --- a/tests/fixtures/good-examples.md +++ b/tests/fixtures/good-examples.md @@ -31,3 +31,35 @@ The following code block is not a rule example, so it won't be checked: ```js !@#$%^&*() ``` + +:::correct { "ecmaVersion": 3, "sourceType": "script" } + +```js +var x; +``` + +::: + +:::correct { "ecmaVersion": 5, "sourceType": "script" } + +```js +var x = { import: 5 }; +``` + +::: + +:::correct { "ecmaVersion": 2015 } + +```js +let x; +``` + +::: + +:::correct { "ecmaVersion": 2024 } + +```js +let x = /a/v; +``` + +::: diff --git a/tests/tools/check-rule-examples.js b/tests/tools/check-rule-examples.js index 0326ee91633..3bd31c49cd1 100644 --- a/tests/tools/check-rule-examples.js +++ b/tests/tools/check-rule-examples.js @@ -7,6 +7,7 @@ const assert = require("assert"); const { execFile } = require("child_process"); const { promisify } = require("util"); +const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version"); //------------------------------------------------------------------------------ // Helpers @@ -78,8 +79,10 @@ describe("check-rule-examples", () => { "\x1B[0m \x1B[2m31:1\x1B[22m \x1B[31merror\x1B[39m Example code should contain a configuration comment like /* eslint no-restricted-syntax: \"error\" */\x1B[0m\n" + "\x1B[0m \x1B[2m41:1\x1B[22m \x1B[31merror\x1B[39m Failed to parse JSON from ' doesn't allow this comment'\x1B[0m\n" + "\x1B[0m \x1B[2m51:1\x1B[22m \x1B[31merror\x1B[39m Duplicate /* eslint no-restricted-syntax */ configuration comment. Each example should contain only one. Split this example into multiple examples\x1B[0m\n" + + "\x1B[0m \x1B[2m56:1\x1B[22m \x1B[31merror\x1B[39m Remove unnecessary \"ecmaVersion\":\"latest\"\x1B[0m\n" + + `\x1B[0m \x1B[2m64:1\x1B[22m \x1B[31merror\x1B[39m "ecmaVersion" must be one of ${[3, 5, ...Array.from({ length: LATEST_ECMA_VERSION - 2015 + 1 }, (_, index) => index + 2015)].join(", ")}\x1B[0m\n` + "\x1B[0m\x1B[0m\n" + - "\x1B[0m\x1B[31m\x1B[1m✖ 7 problems (7 errors, 0 warnings)\x1B[22m\x1B[39m\x1B[0m\n" + + "\x1B[0m\x1B[31m\x1B[1m✖ 9 problems (9 errors, 0 warnings)\x1B[22m\x1B[39m\x1B[0m\n" + "\x1B[0m\x1B[31m\x1B[1m\x1B[22m\x1B[39m\x1B[0m\n"; assert.strictEqual(normalizedStderr, expectedStderr); diff --git a/tools/check-rule-examples.js b/tools/check-rule-examples.js index 4365da71fb2..78408130e4c 100644 --- a/tools/check-rule-examples.js +++ b/tools/check-rule-examples.js @@ -13,6 +13,7 @@ const markdownItContainer = require("markdown-it-container"); const markdownItRuleExample = require("../docs/tools/markdown-it-rule-example"); const ConfigCommentParser = require("../lib/linter/config-comment-parser"); const rules = require("../lib/rules"); +const { LATEST_ECMA_VERSION } = require("../conf/ecma-version"); //------------------------------------------------------------------------------ // Typedefs @@ -28,6 +29,12 @@ const rules = require("../lib/rules"); const STANDARD_LANGUAGE_TAGS = new Set(["javascript", "js", "jsx"]); +const VALID_ECMA_VERSIONS = new Set([ + 3, + 5, + ...Array.from({ length: LATEST_ECMA_VERSION - 2015 + 1 }, (_, index) => index + 2015) // 2015, 2016, ..., LATEST_ECMA_VERSION +]); + const commentParser = new ConfigCommentParser(); /** @@ -79,6 +86,29 @@ async function findProblems(filename) { }); } + if (parserOptions && typeof parserOptions.ecmaVersion !== "undefined") { + const { ecmaVersion } = parserOptions; + let ecmaVersionErrorMessage; + + if (ecmaVersion === "latest") { + ecmaVersionErrorMessage = 'Remove unnecessary "ecmaVersion":"latest".'; + } else if (typeof ecmaVersion !== "number") { + ecmaVersionErrorMessage = '"ecmaVersion" must be a number.'; + } else if (!VALID_ECMA_VERSIONS.has(ecmaVersion)) { + ecmaVersionErrorMessage = `"ecmaVersion" must be one of ${[...VALID_ECMA_VERSIONS].join(", ")}.`; + } + + if (ecmaVersionErrorMessage) { + problems.push({ + fatal: false, + severity: 2, + message: ecmaVersionErrorMessage, + line: codeBlockToken.map[0] - 1, + column: 1 + }); + } + } + const { ast, error } = tryParseForPlayground(code, parserOptions); if (ast) { From b93f4085c105117a1081b249bd50c0831127fab3 Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Mon, 1 Apr 2024 22:46:47 +0530 Subject: [PATCH 048/166] docs: update shared settings example (#18251) * docs: update shared settings example * add indentation --- docs/src/use/configure/configuration-files.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/src/use/configure/configuration-files.md b/docs/src/use/configure/configuration-files.md index 15a2d6a3617..1f37f54ce8f 100644 --- a/docs/src/use/configure/configuration-files.md +++ b/docs/src/use/configure/configuration-files.md @@ -286,6 +286,26 @@ export default [ { settings: { sharedData: "Hello" + }, + plugins: { + customPlugin: { + rules: { + "my-rule": { + meta: { + // custom rule's meta information + }, + create(context) { + const sharedData = context.settings.sharedData; + return { + // code + }; + } + } + } + } + }, + rules: { + "customPlugin/my-rule": "error" } } ]; From 12f574628f2adbe1bfed07aafecf5152b5fc3f4d Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Mon, 1 Apr 2024 23:36:32 +0530 Subject: [PATCH 049/166] docs: add info about dot files and dir in flat config (#18239) * docs: add info about dot files and dir * docs: change note to important --- docs/src/use/configure/configuration-files.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/use/configure/configuration-files.md b/docs/src/use/configure/configuration-files.md index 1f37f54ce8f..5c116f13bcf 100644 --- a/docs/src/use/configure/configuration-files.md +++ b/docs/src/use/configure/configuration-files.md @@ -173,6 +173,10 @@ This configuration specifies that all of the files in the `.config` directory sh For more information on configuring rules, see [Ignore Files](ignore). +::: important +Glob patterns always match files and directories that begin with a dot, such as `.foo.js` or `.fixtures`, unless those files are explicitly ignored. The only dot directory ignored by default is `.git`. +::: + #### Cascading Configuration Objects When more than one configuration object matches a given filename, the configuration objects are merged with later objects overriding previous objects when there is a conflict. For example: From b7cf3bd29f25a0bab4102a51029bf47c50f406b5 Mon Sep 17 00:00:00 2001 From: eMerzh Date: Mon, 1 Apr 2024 20:07:19 +0200 Subject: [PATCH 050/166] fix!: correct `camelcase` rule schema for `allow` option (#18232) fix: correct json schema of camelcase to allow array of string --- docs/src/use/migrate-to-9.0.0.md | 9 +++++++++ lib/rules/camelcase.js | 8 +++----- tests/lib/rules/camelcase.js | 12 ++++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/docs/src/use/migrate-to-9.0.0.md b/docs/src/use/migrate-to-9.0.0.md index 08523b19644..78c798164b5 100644 --- a/docs/src/use/migrate-to-9.0.0.md +++ b/docs/src/use/migrate-to-9.0.0.md @@ -36,6 +36,7 @@ The lists below are ordered roughly by the number of users each change is expect * [`no-inner-declarations` has a new default behavior with a new option](#no-inner-declarations) * [`no-unused-vars` now defaults `caughtErrors` to `"all"`](#no-unused-vars) * [`no-useless-computed-key` flags unnecessary computed member names in classes by default](#no-useless-computed-key) +* [`camelcase` allow option only accepts an array of strings](#camelcase) ### Breaking changes for plugin developers @@ -434,6 +435,14 @@ class SomeClass { **Related issue(s):** [#18042](https://github.com/eslint/eslint/issues/18042) +## `camelcase` allow option only accepts an array of strings + +Previously the camelcase rule didn't enforce the `allow` option to be an array of strings. In ESLint v9.0.0, the `allow` option now only accepts an array of strings. + +**To address:** If ESLint reports invalid configuration for this rule, update your configuration. + +**Related issue(s):** [#18232](https://github.com/eslint/eslint/pull/18232) + ## Removed multiple `context` methods ESLint v9.0.0 removes multiple deprecated methods from the `context` object and moves them onto the `SourceCode` object: diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js index 51bb4122df0..3c5a7b9cec3 100644 --- a/lib/rules/camelcase.js +++ b/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/tests/lib/rules/camelcase.js b/tests/lib/rules/camelcase.js index 1ec012a69f5..eb15e1b9c68 100644 --- a/tests/lib/rules/camelcase.js +++ b/tests/lib/rules/camelcase.js @@ -337,6 +337,18 @@ ruleTester.run("camelcase", rule, { code: "__option_foo__ = 0;", options: [{ allow: ["__option_foo__"] }] }, + { + code: "__option_foo__ = 0; user_id = 0; foo = 1", + options: [{ allow: ["__option_foo__", "_id$"] }] + }, + { + code: "fo_o = 0;", + options: [{ allow: ["__option_foo__", "fo_o"] }] + }, + { + code: "user = 0;", + options: [{ allow: [] }] + }, { code: "foo = { [computedBar]: 0 };", options: [{ ignoreDestructuring: true }], From 3e9fcea3808af83bda1e610aa2d33fb92135b5de Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 1 Apr 2024 14:49:40 -0700 Subject: [PATCH 051/166] feat: Show config names in error messages (#18256) fixes #18231 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 04f5ece1f90..1d9d17b879f 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^3.0.2", "@eslint/js": "9.0.0-rc.0", - "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/config-array": "^0.12.1", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", From 950c4f11c6797de56a5b056affd0c74211840957 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Tue, 2 Apr 2024 08:06:39 +0000 Subject: [PATCH 052/166] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b24a55daded..79b7796292e 100644 --- a/README.md +++ b/README.md @@ -306,7 +306,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Automattic

Gold Sponsors

Bitwarden Salesforce Airbnb

Silver Sponsors

JetBrains Liftoff American Express Workleap

Bronze Sponsors

-

notion ThemeIsle Anagram Solver Icons8 Discord Transloadit Ignition Nx HeroCoders Nextbase Starter Kit

+

notion Anagram Solver Icons8 Discord Transloadit Ignition Nx HeroCoders Nextbase Starter Kit

## Technology Sponsors From 651ec9122d0bd8dd08082098bd1e1a24892983f2 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 2 Apr 2024 13:08:05 +0200 Subject: [PATCH 053/166] docs: remove `/* eslint-env */` comments from rule examples (#18249) * docs: remove `/* eslint-env */` comments from rule examples * also disallow empty eslint-env comments Co-authored-by: Francesco Trotta * add test case --------- Co-authored-by: Francesco Trotta --- docs/src/rules/array-bracket-spacing.md | 6 ---- docs/src/rules/arrow-body-style.md | 9 +----- docs/src/rules/arrow-parens.md | 20 ------------- docs/src/rules/arrow-spacing.md | 8 ------ docs/src/rules/capitalized-comments.md | 2 -- docs/src/rules/class-methods-use-this.md | 3 +- docs/src/rules/computed-property-spacing.md | 9 ------ docs/src/rules/constructor-super.md | 2 -- docs/src/rules/func-name-matching.md | 1 - docs/src/rules/generator-star-spacing.md | 12 -------- docs/src/rules/generator-star.md | 20 ------------- docs/src/rules/global-require.md | 1 - docs/src/rules/id-length.md | 9 ------ docs/src/rules/indent-legacy.md | 4 --- docs/src/rules/indent.md | 6 ---- docs/src/rules/init-declarations.md | 4 --- docs/src/rules/keyword-spacing.md | 1 - docs/src/rules/max-params.md | 2 -- docs/src/rules/max-statements.md | 2 -- docs/src/rules/newline-after-var.md | 4 --- docs/src/rules/no-arrow-condition.md | 1 - docs/src/rules/no-case-declarations.md | 2 -- docs/src/rules/no-class-assign.md | 9 ------ docs/src/rules/no-confusing-arrow.md | 7 ++--- docs/src/rules/no-const-assign.md | 6 ---- docs/src/rules/no-dupe-class-members.md | 2 -- docs/src/rules/no-empty-function.md | 11 -------- docs/src/rules/no-eval.md | 13 ++++----- docs/src/rules/no-extra-bind.md | 1 - docs/src/rules/no-global-assign.md | 28 ++----------------- docs/src/rules/no-implied-eval.md | 2 +- docs/src/rules/no-inner-declarations.md | 4 +-- docs/src/rules/no-invalid-this.md | 2 -- docs/src/rules/no-irregular-whitespace.md | 3 -- docs/src/rules/no-lone-blocks.md | 4 +-- docs/src/rules/no-loop-func.md | 4 --- docs/src/rules/no-native-reassign.md | 28 ++----------------- .../src/rules/no-new-native-nonconstructor.md | 2 -- docs/src/rules/no-new-symbol.md | 2 -- docs/src/rules/no-obj-calls.md | 4 +-- docs/src/rules/no-promise-executor-return.md | 3 -- docs/src/rules/no-redeclare.md | 17 +---------- docs/src/rules/no-shadow.md | 6 ---- docs/src/rules/no-this-before-super.md | 2 -- docs/src/rules/no-throw-literal.md | 1 - docs/src/rules/no-useless-concat.md | 1 - docs/src/rules/no-useless-constructor.md | 1 - docs/src/rules/no-var.md | 1 - docs/src/rules/no-with.md | 1 - docs/src/rules/object-curly-newline.md | 14 ---------- docs/src/rules/object-shorthand.md | 13 --------- .../src/rules/one-var-declaration-per-line.md | 4 --- docs/src/rules/one-var.md | 13 --------- docs/src/rules/prefer-arrow-callback.md | 2 -- docs/src/rules/prefer-const.md | 6 ---- docs/src/rules/prefer-numeric-literals.md | 1 - docs/src/rules/prefer-spread.md | 2 -- docs/src/rules/prefer-template.md | 3 -- docs/src/rules/quote-props.md | 2 -- docs/src/rules/quotes.md | 5 ---- docs/src/rules/require-yield.md | 2 -- docs/src/rules/sort-keys.md | 14 +--------- docs/src/rules/space-before-blocks.md | 6 ---- docs/src/rules/space-before-function-paren.md | 10 ------- .../space-before-function-parentheses.md | 16 ----------- docs/src/rules/space-before-keywords.md | 2 -- docs/src/rules/space-in-brackets.md | 4 --- docs/src/rules/space-infix-ops.md | 2 -- docs/src/rules/space-unary-ops.md | 2 -- docs/src/rules/strict.md | 1 - docs/src/rules/symbol-description.md | 2 -- docs/src/rules/valid-jsdoc.md | 3 -- docs/src/rules/yield-star-spacing.md | 4 --- tests/fixtures/bad-examples.md | 12 ++++++++ tests/tools/check-rule-examples.js | 5 +++- tools/check-rule-examples.js | 11 ++++++++ 76 files changed, 49 insertions(+), 405 deletions(-) diff --git a/docs/src/rules/array-bracket-spacing.md b/docs/src/rules/array-bracket-spacing.md index 53fa82cf562..4b03bdae94b 100644 --- a/docs/src/rules/array-bracket-spacing.md +++ b/docs/src/rules/array-bracket-spacing.md @@ -13,8 +13,6 @@ A number of style guides require or disallow spaces between array brackets and o applies to both array literals and destructuring assignments (ECMAScript 6). ```js -/*eslint-env es6*/ - var arr = [ 'foo', 'bar' ]; var [ x, y ] = z; @@ -58,7 +56,6 @@ Examples of **incorrect** code for this rule with the default `"never"` option: ```js /*eslint array-bracket-spacing: ["error", "never"]*/ -/*eslint-env es6*/ var arr = [ 'foo', 'bar' ]; var arr = ['foo', 'bar' ]; @@ -81,7 +78,6 @@ Examples of **correct** code for this rule with the default `"never"` option: ```js /*eslint array-bracket-spacing: ["error", "never"]*/ -/*eslint-env es6*/ var arr = []; var arr = ['foo', 'bar', 'baz']; @@ -114,7 +110,6 @@ Examples of **incorrect** code for this rule with the `"always"` option: ```js /*eslint array-bracket-spacing: ["error", "always"]*/ -/*eslint-env es6*/ var arr = ['foo', 'bar']; var arr = ['foo', 'bar' ]; @@ -140,7 +135,6 @@ Examples of **correct** code for this rule with the `"always"` option: ```js /*eslint array-bracket-spacing: ["error", "always"]*/ -/*eslint-env es6*/ var arr = []; var arr = [ 'foo', 'bar', 'baz' ]; diff --git a/docs/src/rules/arrow-body-style.md b/docs/src/rules/arrow-body-style.md index 1dc6fcc36e8..20fb76d49cb 100644 --- a/docs/src/rules/arrow-body-style.md +++ b/docs/src/rules/arrow-body-style.md @@ -31,7 +31,6 @@ Examples of **incorrect** code for this rule with the `"always"` option: ```js /*eslint arrow-body-style: ["error", "always"]*/ -/*eslint-env es6*/ let foo = () => 0; ``` @@ -44,7 +43,6 @@ Examples of **correct** code for this rule with the `"always"` option: ```js /*eslint arrow-body-style: ["error", "always"]*/ -/*eslint-env es6*/ let foo = () => { return 0; @@ -65,7 +63,6 @@ Examples of **incorrect** code for this rule with the default `"as-needed"` opti ```js /*eslint arrow-body-style: ["error", "as-needed"]*/ -/*eslint-env es6*/ let foo = () => { return 0; @@ -88,7 +85,6 @@ Examples of **correct** code for this rule with the default `"as-needed"` option ```js /*eslint arrow-body-style: ["error", "as-needed"]*/ -/*eslint-env es6*/ let foo1 = () => 0; let foo2 = (retv, name) => { @@ -122,7 +118,7 @@ Examples of **incorrect** code for this rule with the `{ "requireReturnForObject ```js /*eslint arrow-body-style: ["error", "as-needed", { "requireReturnForObjectLiteral": true }]*/ -/*eslint-env es6*/ + let foo = () => ({}); let bar = () => ({ bar: 0 }); ``` @@ -135,7 +131,6 @@ Examples of **correct** code for this rule with the `{ "requireReturnForObjectLi ```js /*eslint arrow-body-style: ["error", "as-needed", { "requireReturnForObjectLiteral": true }]*/ -/*eslint-env es6*/ let foo = () => {}; let bar = () => { return { bar: 0 }; }; @@ -151,7 +146,6 @@ Examples of **incorrect** code for this rule with the `"never"` option: ```js /*eslint arrow-body-style: ["error", "never"]*/ -/*eslint-env es6*/ let foo = () => { return 0; @@ -170,7 +164,6 @@ Examples of **correct** code for this rule with the `"never"` option: ```js /*eslint arrow-body-style: ["error", "never"]*/ -/*eslint-env es6*/ let foo = () => 0; let bar = () => ({ foo: 0 }); diff --git a/docs/src/rules/arrow-parens.md b/docs/src/rules/arrow-parens.md index d84e1999db8..55fac9673a0 100644 --- a/docs/src/rules/arrow-parens.md +++ b/docs/src/rules/arrow-parens.md @@ -15,8 +15,6 @@ be wrapped in parentheses. This rule enforces the consistent use of parentheses This rule enforces parentheses around arrow function parameters regardless of arity. For example: ```js -/*eslint-env es6*/ - // Bad a => {} @@ -28,8 +26,6 @@ Following this style will help you find arrow functions (`=>`) which may be mist when a comparison such as `>=` was the intent. ```js -/*eslint-env es6*/ - // Bad if (a => 2) { } @@ -42,8 +38,6 @@ if (a >= 2) { The rule can also be configured to discourage the use of parens when they are not required: ```js -/*eslint-env es6*/ - // Bad (a) => {} @@ -72,7 +66,6 @@ Examples of **incorrect** code for this rule with the default `"always"` option: ```js /*eslint arrow-parens: ["error", "always"]*/ -/*eslint-env es6*/ a => {}; a => a; @@ -90,7 +83,6 @@ Examples of **correct** code for this rule with the default `"always"` option: ```js /*eslint arrow-parens: ["error", "always"]*/ -/*eslint-env es6*/ () => {}; (a) => {}; @@ -107,8 +99,6 @@ a.then((foo) => { if (true) {} }); One of the benefits of this option is that it prevents the incorrect use of arrow functions in conditionals: ```js -/*eslint-env es6*/ - var a = 1; var b = 2; // ... @@ -125,8 +115,6 @@ The contents of the `if` statement is an arrow function, not a comparison. If the arrow function is intentional, it should be wrapped in parens to remove ambiguity. ```js -/*eslint-env es6*/ - var a = 1; var b = 0; // ... @@ -141,8 +129,6 @@ if ((a) => b) { The following is another example of this behavior: ```js -/*eslint-env es6*/ - var a = 1, b = 2, c = 3, d = 4; var f = a => b ? c: d; // f = ? @@ -153,8 +139,6 @@ var f = a => b ? c: d; This should be rewritten like so: ```js -/*eslint-env es6*/ - var a = 1, b = 2, c = 3, d = 4; var f = (a) => b ? c: d; ``` @@ -167,7 +151,6 @@ Examples of **incorrect** code for this rule with the `"as-needed"` option: ```js /*eslint arrow-parens: ["error", "as-needed"]*/ -/*eslint-env es6*/ (a) => {}; (a) => a; @@ -188,7 +171,6 @@ Examples of **correct** code for this rule with the `"as-needed"` option: ```js /*eslint arrow-parens: ["error", "as-needed"]*/ -/*eslint-env es6*/ () => {}; a => {}; @@ -215,7 +197,6 @@ Examples of **incorrect** code for the `{ "requireForBlockBody": true }` option: ```js /*eslint arrow-parens: [2, "as-needed", { "requireForBlockBody": true }]*/ -/*eslint-env es6*/ (a) => a; a => {}; @@ -235,7 +216,6 @@ Examples of **correct** code for the `{ "requireForBlockBody": true }` option: ```js /*eslint arrow-parens: [2, "as-needed", { "requireForBlockBody": true }]*/ -/*eslint-env es6*/ (a) => {}; (a) => {'\n'}; diff --git a/docs/src/rules/arrow-spacing.md b/docs/src/rules/arrow-spacing.md index c0e4c3e1e13..da76bba70f0 100644 --- a/docs/src/rules/arrow-spacing.md +++ b/docs/src/rules/arrow-spacing.md @@ -8,8 +8,6 @@ This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding ru This rule normalize style of spacing before/after an arrow function's arrow(`=>`). ```js -/*eslint-env es6*/ - // { "before": true, "after": true } (a) => {} @@ -31,7 +29,6 @@ Examples of **incorrect** code for this rule with the default `{ "before": true, ```js /*eslint arrow-spacing: "error"*/ -/*eslint-env es6*/ ()=> {}; () =>{}; @@ -51,7 +48,6 @@ Examples of **correct** code for this rule with the default `{ "before": true, " ```js /*eslint arrow-spacing: "error"*/ -/*eslint-env es6*/ () => {}; (a) => {}; @@ -67,7 +63,6 @@ Examples of **incorrect** code for this rule with the `{ "before": false, "after ```js /*eslint arrow-spacing: ["error", { "before": false, "after": false }]*/ -/*eslint-env es6*/ () =>{}; (a) => {}; @@ -82,7 +77,6 @@ Examples of **correct** code for this rule with the `{ "before": false, "after": ```js /*eslint arrow-spacing: ["error", { "before": false, "after": false }]*/ -/*eslint-env es6*/ ()=>{}; (a)=>{}; @@ -97,7 +91,6 @@ Examples of **incorrect** code for this rule with the `{ "before": false, "after ```js /*eslint arrow-spacing: ["error", { "before": false, "after": true }]*/ -/*eslint-env es6*/ () =>{}; (a) => {}; @@ -112,7 +105,6 @@ Examples of **correct** code for this rule with the `{ "before": false, "after": ```js /*eslint arrow-spacing: ["error", { "before": false, "after": true }]*/ -/*eslint-env es6*/ ()=> {}; (a)=> {}; diff --git a/docs/src/rules/capitalized-comments.md b/docs/src/rules/capitalized-comments.md index b5c48854efc..2a2043a5adf 100644 --- a/docs/src/rules/capitalized-comments.md +++ b/docs/src/rules/capitalized-comments.md @@ -42,7 +42,6 @@ Examples of **correct** code for this rule: // 丈 Non-Latin character at beginning of comment /* eslint semi:off */ -/* eslint-env node */ /* eslint-disable */ /* eslint-enable */ /* istanbul ignore next */ @@ -118,7 +117,6 @@ Examples of **correct** code for this rule: // 丈 Non-Latin character at beginning of comment /* eslint semi:off */ -/* eslint-env node */ /* eslint-disable */ /* eslint-enable */ /* istanbul ignore next */ diff --git a/docs/src/rules/class-methods-use-this.md b/docs/src/rules/class-methods-use-this.md index 5a12f9af3c9..c4aa9c3c007 100644 --- a/docs/src/rules/class-methods-use-this.md +++ b/docs/src/rules/class-methods-use-this.md @@ -62,7 +62,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint class-methods-use-this: "error"*/ -/*eslint-env es6*/ class A { foo() { @@ -79,7 +78,7 @@ Examples of **correct** code for this rule: ```js /*eslint class-methods-use-this: "error"*/ -/*eslint-env es6*/ + class A { foo() { this.bar = "Hello World"; // OK, this is used diff --git a/docs/src/rules/computed-property-spacing.md b/docs/src/rules/computed-property-spacing.md index 8e8b2d5abc0..5102b706e80 100644 --- a/docs/src/rules/computed-property-spacing.md +++ b/docs/src/rules/computed-property-spacing.md @@ -13,8 +13,6 @@ While formatting preferences are very personal, a number of style guides require or disallow spaces between computed properties in the following situations: ```js -/*eslint-env es6*/ - var obj = { prop: "value" }; var a = "prop"; var x = obj[a]; // computed property in object member expression @@ -57,7 +55,6 @@ Examples of **incorrect** code for this rule with the default `"never"` option: ```js /*eslint computed-property-spacing: ["error", "never"]*/ -/*eslint-env es6*/ obj[foo ] obj[ 'foo'] @@ -76,7 +73,6 @@ Examples of **correct** code for this rule with the default `"never"` option: ```js /*eslint computed-property-spacing: ["error", "never"]*/ -/*eslint-env es6*/ obj[foo] obj['foo'] @@ -97,7 +93,6 @@ Examples of **incorrect** code for this rule with the `"always"` option: ```js /*eslint computed-property-spacing: ["error", "always"]*/ -/*eslint-env es6*/ obj[foo] var x = {[b]: a} @@ -117,7 +112,6 @@ Examples of **correct** code for this rule with the `"always"` option: ```js /*eslint computed-property-spacing: ["error", "always"]*/ -/*eslint-env es6*/ obj[ foo ] obj[ 'foo' ] @@ -139,7 +133,6 @@ Examples of **incorrect** code for this rule with `"never"` and `{ "enforceForCl ```js /*eslint computed-property-spacing: ["error", "never", { "enforceForClassMembers": true }]*/ -/*eslint-env es6*/ class Foo { [a ]() {} @@ -163,7 +156,6 @@ Examples of **correct** code for this rule with `"never"` and `{ "enforceForClas ```js /*eslint computed-property-spacing: ["error", "never", { "enforceForClassMembers": true }]*/ -/*eslint-env es6*/ class Foo { [a]() {} @@ -187,7 +179,6 @@ Examples of **correct** code for this rule with `"never"` and `{ "enforceForClas ```js /*eslint computed-property-spacing: ["error", "never", { "enforceForClassMembers": false }]*/ -/*eslint-env es6*/ class Foo { [a ]() {} diff --git a/docs/src/rules/constructor-super.md b/docs/src/rules/constructor-super.md index c6f008f13d6..bc48b122ede 100644 --- a/docs/src/rules/constructor-super.md +++ b/docs/src/rules/constructor-super.md @@ -30,7 +30,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint constructor-super: "error"*/ -/*eslint-env es6*/ class A extends B { constructor() { } // Would throw a ReferenceError. @@ -56,7 +55,6 @@ Examples of **correct** code for this rule: ```js /*eslint constructor-super: "error"*/ -/*eslint-env es6*/ class A { constructor() { } diff --git a/docs/src/rules/func-name-matching.md b/docs/src/rules/func-name-matching.md index 82748623f76..f104bd0e648 100644 --- a/docs/src/rules/func-name-matching.md +++ b/docs/src/rules/func-name-matching.md @@ -100,7 +100,6 @@ module['exports'] = function foo(name) {}; ```js /*eslint func-name-matching: ["error", "never"] */ -/*eslint-env es6*/ var foo = function bar() {}; var foo = function() {}; diff --git a/docs/src/rules/generator-star-spacing.md b/docs/src/rules/generator-star-spacing.md index 2157da18fe8..22ef3a1ec57 100644 --- a/docs/src/rules/generator-star-spacing.md +++ b/docs/src/rules/generator-star-spacing.md @@ -13,8 +13,6 @@ These special functions are indicated by placing an `*` after the `function` key Here is an example of a generator function: ```js -/*eslint-env es6*/ - function* generator() { yield "44"; yield "55"; @@ -24,8 +22,6 @@ function* generator() { This is also valid: ```js -/*eslint-env es6*/ - function *generator() { yield "44"; yield "55"; @@ -35,8 +31,6 @@ function *generator() { This is valid as well: ```js -/*eslint-env es6*/ - function * generator() { yield "44"; yield "55"; @@ -113,7 +107,6 @@ Examples of **correct** code for this rule with the `"before"` option: ```js /*eslint generator-star-spacing: ["error", {"before": true, "after": false}]*/ -/*eslint-env es6*/ function *generator() {} @@ -132,7 +125,6 @@ Examples of **correct** code for this rule with the `"after"` option: ```js /*eslint generator-star-spacing: ["error", {"before": false, "after": true}]*/ -/*eslint-env es6*/ function* generator() {} @@ -151,7 +143,6 @@ Examples of **correct** code for this rule with the `"both"` option: ```js /*eslint generator-star-spacing: ["error", {"before": true, "after": true}]*/ -/*eslint-env es6*/ function * generator() {} @@ -170,7 +161,6 @@ Examples of **correct** code for this rule with the `"neither"` option: ```js /*eslint generator-star-spacing: ["error", {"before": false, "after": false}]*/ -/*eslint-env es6*/ function*generator() {} @@ -192,7 +182,6 @@ Examples of **incorrect** code for this rule with overrides present: "anonymous": "neither", "method": {"before": true, "after": true} }]*/ -/*eslint-env es6*/ function * generator() {} @@ -216,7 +205,6 @@ Examples of **correct** code for this rule with overrides present: "anonymous": "neither", "method": {"before": true, "after": true} }]*/ -/*eslint-env es6*/ function* generator() {} diff --git a/docs/src/rules/generator-star.md b/docs/src/rules/generator-star.md index 4a79edd1820..0ece6e7b0b4 100644 --- a/docs/src/rules/generator-star.md +++ b/docs/src/rules/generator-star.md @@ -16,8 +16,6 @@ These special functions are indicated by placing an `*` after the `function` key Here is an example of a generator function: ```js -/*eslint-env es6*/ - function* generator() { yield "44"; yield "55"; @@ -27,8 +25,6 @@ function* generator() { This is also valid: ```js -/*eslint-env es6*/ - function *generator() { yield "44"; yield "55"; @@ -38,8 +34,6 @@ function *generator() { This is valid as well: ```js -/*eslint-env es6*/ - function * generator() { yield "44"; yield "55"; @@ -63,8 +57,6 @@ You can set the style in configuration like this: When using `"start"` this placement will be enforced: ```js -/*eslint-env es6*/ - function* generator() { } ``` @@ -72,8 +64,6 @@ function* generator() { When using `"middle"` this placement will be enforced: ```js -/*eslint-env es6*/ - function * generator() { } ``` @@ -81,8 +71,6 @@ function * generator() { When using `"end"` this placement will be enforced: ```js -/*eslint-env es6*/ - function *generator() { } ``` @@ -90,8 +78,6 @@ function *generator() { When using the expression syntax `"start"` will be enforced here: ```js -/*eslint-env es6*/ - var generator = function* () { } ``` @@ -99,8 +85,6 @@ var generator = function* () { When using the expression syntax `"middle"` will be enforced here: ```js -/*eslint-env es6*/ - var generator = function * () { } ``` @@ -108,8 +92,6 @@ var generator = function * () { When using the expression syntax `"end"` will be enforced here: ```js -/*eslint-env es6*/ - var generator = function *() { } ``` @@ -117,8 +99,6 @@ var generator = function *() { When using the expression syntax this is valid for both `"start"` and `"end"`: ```js -/*eslint-env es6*/ - var generator = function*() { } ``` diff --git a/docs/src/rules/global-require.md b/docs/src/rules/global-require.md index 8460acb89ad..3c7423726e1 100644 --- a/docs/src/rules/global-require.md +++ b/docs/src/rules/global-require.md @@ -36,7 +36,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint global-require: "error"*/ -/*eslint-env es6*/ // calling require() inside of a function is not allowed function readFile(filename, callback) { diff --git a/docs/src/rules/id-length.md b/docs/src/rules/id-length.md index ea5d2fa9d1a..7baced2fe69 100644 --- a/docs/src/rules/id-length.md +++ b/docs/src/rules/id-length.md @@ -29,7 +29,6 @@ Examples of **incorrect** code for this rule with the default options: ```js /*eslint id-length: "error"*/ // default is minimum 2-chars ({ "min": 2 }) -/*eslint-env es6*/ var x = 5; obj.e = document.body; @@ -64,7 +63,6 @@ Examples of **correct** code for this rule with the default options: ```js /*eslint id-length: "error"*/ // default is minimum 2-chars ({ "min": 2 }) -/*eslint-env es6*/ var num = 5; function _f() { return 42; } @@ -117,7 +115,6 @@ Examples of **incorrect** code for this rule with the `{ "min": 4 }` option: ```js /*eslint id-length: ["error", { "min": 4 }]*/ -/*eslint-env es6*/ var val = 5; obj.e = document.body; @@ -147,7 +144,6 @@ Examples of **correct** code for this rule with the `{ "min": 4 }` option: ```js /*eslint id-length: ["error", { "min": 4 }]*/ -/*eslint-env es6*/ var value = 5; function func() { return 42; } @@ -183,7 +179,6 @@ Examples of **incorrect** code for this rule with the `{ "max": 10 }` option: ```js /*eslint id-length: ["error", { "max": 10 }]*/ -/*eslint-env es6*/ var reallyLongVarName = 5; function reallyLongFuncName() { return 42; } @@ -206,7 +201,6 @@ Examples of **correct** code for this rule with the `{ "max": 10 }` option: ```js /*eslint id-length: ["error", { "max": 10 }]*/ -/*eslint-env es6*/ var varName = 5; function funcName() { return 42; } @@ -231,7 +225,6 @@ Examples of **correct** code for this rule with the `{ "properties": "never" }` ```js /*eslint id-length: ["error", { "properties": "never" }]*/ -/*eslint-env es6*/ var myObj = { a: 1 }; ({ a: obj.x.y.z } = {}); @@ -248,7 +241,6 @@ Examples of additional **correct** code for this rule with the `{ "exceptions": ```js /*eslint id-length: ["error", { "exceptions": ["x", "y", "z", "ζ"] }]*/ -/*eslint-env es6*/ var x = 5; function y() { return 42; } @@ -275,7 +267,6 @@ Examples of additional **correct** code for this rule with the `{ "exceptionPatt ```js /*eslint id-length: ["error", { "exceptionPatterns": ["E|S", "[x-z]"] }]*/ -/*eslint-env es6*/ var E = 5; function S() { return 42; } diff --git a/docs/src/rules/indent-legacy.md b/docs/src/rules/indent-legacy.md index a8097b22932..209234a1638 100644 --- a/docs/src/rules/indent-legacy.md +++ b/docs/src/rules/indent-legacy.md @@ -202,7 +202,6 @@ Examples of **incorrect** code for this rule with the `2, { "VariableDeclarator" ```js /*eslint indent-legacy: ["error", 2, { "VariableDeclarator": 1 }]*/ -/*eslint-env es6*/ var a, b, @@ -223,7 +222,6 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ```js /*eslint indent-legacy: ["error", 2, { "VariableDeclarator": 1 }]*/ -/*eslint-env es6*/ var a, b, @@ -244,7 +242,6 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ```js /*eslint indent-legacy: ["error", 2, { "VariableDeclarator": 2 }]*/ -/*eslint-env es6*/ var a, b, @@ -265,7 +262,6 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ```js /*eslint indent-legacy: ["error", 2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }]*/ -/*eslint-env es6*/ var a, b, diff --git a/docs/src/rules/indent.md b/docs/src/rules/indent.md index 768c1b18a4b..66e5d8b5921 100644 --- a/docs/src/rules/indent.md +++ b/docs/src/rules/indent.md @@ -246,7 +246,6 @@ Examples of **incorrect** code for this rule with the `2, { "VariableDeclarator" ```js /*eslint indent: ["error", 2, { "VariableDeclarator": 1 }]*/ -/*eslint-env es6*/ var a, b, @@ -267,7 +266,6 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ```js /*eslint indent: ["error", 2, { "VariableDeclarator": 1 }]*/ -/*eslint-env es6*/ var a, b, @@ -288,7 +286,6 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ```js /*eslint indent: ["error", 2, { "VariableDeclarator": 2 }]*/ -/*eslint-env es6*/ var a, b, @@ -309,7 +306,6 @@ Examples of **incorrect** code for this rule with the `2, { "VariableDeclarator" ```js /*eslint indent: ["error", 2, { "VariableDeclarator": "first" }]*/ -/*eslint-env es6*/ var a, b, @@ -330,7 +326,6 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ```js /*eslint indent: ["error", 2, { "VariableDeclarator": "first" }]*/ -/*eslint-env es6*/ var a, b, @@ -351,7 +346,6 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ```js /*eslint indent: ["error", 2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }]*/ -/*eslint-env es6*/ var a, b, diff --git a/docs/src/rules/init-declarations.md b/docs/src/rules/init-declarations.md index 569e5079c72..32760b96cb3 100644 --- a/docs/src/rules/init-declarations.md +++ b/docs/src/rules/init-declarations.md @@ -71,7 +71,6 @@ Examples of **incorrect** code for the default `"always"` option: ```js /*eslint init-declarations: ["error", "always"]*/ -/*eslint-env es6*/ function foo() { var bar; @@ -87,7 +86,6 @@ Examples of **correct** code for the default `"always"` option: ```js /*eslint init-declarations: ["error", "always"]*/ -/*eslint-env es6*/ function foo() { var bar = 1; @@ -106,7 +104,6 @@ Examples of **incorrect** code for the `"never"` option: ```js /*eslint init-declarations: ["error", "never"]*/ -/*eslint-env es6*/ function foo() { var bar = 1; @@ -124,7 +121,6 @@ Examples of **correct** code for the `"never"` option: ```js /*eslint init-declarations: ["error", "never"]*/ -/*eslint-env es6*/ function foo() { var bar; diff --git a/docs/src/rules/keyword-spacing.md b/docs/src/rules/keyword-spacing.md index cd4ab2e6007..e6c485a75af 100644 --- a/docs/src/rules/keyword-spacing.md +++ b/docs/src/rules/keyword-spacing.md @@ -62,7 +62,6 @@ Examples of **correct** code for this rule with the default `{ "before": true }` ```jsx /*eslint keyword-spacing: ["error", { "before": true }]*/ -/*eslint-env es6*/ if (foo) { //... diff --git a/docs/src/rules/max-params.md b/docs/src/rules/max-params.md index b0a55e9009f..92d7dde8dfe 100644 --- a/docs/src/rules/max-params.md +++ b/docs/src/rules/max-params.md @@ -40,7 +40,6 @@ Examples of **incorrect** code for this rule with the default `{ "max": 3 }` opt ```js /*eslint max-params: ["error", 3]*/ -/*eslint-env es6*/ function foo1 (bar, baz, qux, qxx) { doSomething(); @@ -59,7 +58,6 @@ Examples of **correct** code for this rule with the default `{ "max": 3 }` optio ```js /*eslint max-params: ["error", 3]*/ -/*eslint-env es6*/ function foo1 (bar, baz, qux) { doSomething(); diff --git a/docs/src/rules/max-statements.md b/docs/src/rules/max-statements.md index 5bdc7e8c2f4..0688029ec2a 100644 --- a/docs/src/rules/max-statements.md +++ b/docs/src/rules/max-statements.md @@ -46,7 +46,6 @@ Examples of **incorrect** code for this rule with the default `{ "max": 10 }` op ```js /*eslint max-statements: ["error", 10]*/ -/*eslint-env es6*/ function foo() { var foo1 = 1; @@ -87,7 +86,6 @@ Examples of **correct** code for this rule with the default `{ "max": 10 }` opti ```js /*eslint max-statements: ["error", 10]*/ -/*eslint-env es6*/ function foo() { var foo1 = 1; diff --git a/docs/src/rules/newline-after-var.md b/docs/src/rules/newline-after-var.md index 9d6dc216ece..394ced72615 100644 --- a/docs/src/rules/newline-after-var.md +++ b/docs/src/rules/newline-after-var.md @@ -46,7 +46,6 @@ Examples of **incorrect** code for this rule with the default `"always"` option: ```js /*eslint newline-after-var: ["error", "always"]*/ -/*eslint-env es6*/ var greet = "hello,", name = "world"; @@ -74,7 +73,6 @@ Examples of **correct** code for this rule with the default `"always"` option: ```js /*eslint newline-after-var: ["error", "always"]*/ -/*eslint-env es6*/ var greet = "hello,", name = "world"; @@ -108,7 +106,6 @@ Examples of **incorrect** code for this rule with the `"never"` option: ```js /*eslint newline-after-var: ["error", "never"]*/ -/*eslint-env es6*/ var greet = "hello,", name = "world"; @@ -140,7 +137,6 @@ Examples of **correct** code for this rule with the `"never"` option: ```js /*eslint newline-after-var: ["error", "never"]*/ -/*eslint-env es6*/ var greet = "hello,", name = "world"; diff --git a/docs/src/rules/no-arrow-condition.md b/docs/src/rules/no-arrow-condition.md index c2a52a030fd..0097865a2bd 100644 --- a/docs/src/rules/no-arrow-condition.md +++ b/docs/src/rules/no-arrow-condition.md @@ -43,7 +43,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-arrow-condition: "error"*/ -/*eslint-env es6*/ if (a => 1) {} while (a => 1) {} diff --git a/docs/src/rules/no-case-declarations.md b/docs/src/rules/no-case-declarations.md index 49e6f41f08d..dcf4d74f0e0 100644 --- a/docs/src/rules/no-case-declarations.md +++ b/docs/src/rules/no-case-declarations.md @@ -25,7 +25,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-case-declarations: "error"*/ -/*eslint-env es6*/ switch (foo) { case 1: @@ -50,7 +49,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-case-declarations: "error"*/ -/*eslint-env es6*/ // Declarations outside switch-statements are valid const a = 0; diff --git a/docs/src/rules/no-class-assign.md b/docs/src/rules/no-class-assign.md index 360192ca2f4..53b415a2fd9 100644 --- a/docs/src/rules/no-class-assign.md +++ b/docs/src/rules/no-class-assign.md @@ -8,8 +8,6 @@ rule_type: problem `ClassDeclaration` creates a variable, and we can modify the variable. ```js -/*eslint-env es6*/ - class A { } A = 0; ``` @@ -26,7 +24,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-class-assign: "error"*/ -/*eslint-env es6*/ class A { } A = 0; @@ -38,7 +35,6 @@ A = 0; ```js /*eslint no-class-assign: "error"*/ -/*eslint-env es6*/ A = 0; class A { } @@ -50,7 +46,6 @@ class A { } ```js /*eslint no-class-assign: "error"*/ -/*eslint-env es6*/ class A { b() { @@ -65,7 +60,6 @@ class A { ```js /*eslint no-class-assign: "error"*/ -/*eslint-env es6*/ let A = class A { b() { @@ -83,7 +77,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-class-assign: "error"*/ -/*eslint-env es6*/ let A = class A { } A = 0; // A is a variable. @@ -95,7 +88,6 @@ A = 0; // A is a variable. ```js /*eslint no-class-assign: "error"*/ -/*eslint-env es6*/ let A = class { b() { @@ -110,7 +102,6 @@ let A = class { ```js /*eslint no-class-assign: 2*/ -/*eslint-env es6*/ class A { b(A) { diff --git a/docs/src/rules/no-confusing-arrow.md b/docs/src/rules/no-confusing-arrow.md index 349984587b8..0e8e18ac8fb 100644 --- a/docs/src/rules/no-confusing-arrow.md +++ b/docs/src/rules/no-confusing-arrow.md @@ -31,7 +31,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-confusing-arrow: "error"*/ -/*eslint-env es6*/ var x = a => 1 ? 2 : 3; var x = (a) => 1 ? 2 : 3; @@ -45,7 +44,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-confusing-arrow: "error"*/ -/*eslint-env es6*/ + var x = a => (1 ? 2 : 3); var x = (a) => (1 ? 2 : 3); var x = (a) => { @@ -82,7 +81,7 @@ Examples of **incorrect** code for this rule with the `{"allowParens": false}` o ```js /*eslint no-confusing-arrow: ["error", {"allowParens": false}]*/ -/*eslint-env es6*/ + var x = a => (1 ? 2 : 3); var x = (a) => (1 ? 2 : 3); ``` @@ -100,7 +99,7 @@ Examples of **correct** code for this rule with the `{"onlyOneSimpleParam": true ```js /*eslint no-confusing-arrow: ["error", {"onlyOneSimpleParam": true}]*/ -/*eslint-env es6*/ + () => 1 ? 2 : 3; (a, b) => 1 ? 2 : 3; (a = b) => 1 ? 2 : 3; diff --git a/docs/src/rules/no-const-assign.md b/docs/src/rules/no-const-assign.md index ca62132a757..9f93286ae01 100644 --- a/docs/src/rules/no-const-assign.md +++ b/docs/src/rules/no-const-assign.md @@ -21,7 +21,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-const-assign: "error"*/ -/*eslint-env es6*/ const a = 0; a = 1; @@ -33,7 +32,6 @@ a = 1; ```js /*eslint no-const-assign: "error"*/ -/*eslint-env es6*/ const a = 0; a += 1; @@ -45,7 +43,6 @@ a += 1; ```js /*eslint no-const-assign: "error"*/ -/*eslint-env es6*/ const a = 0; ++a; @@ -59,7 +56,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-const-assign: "error"*/ -/*eslint-env es6*/ const a = 0; console.log(a); @@ -71,7 +67,6 @@ console.log(a); ```js /*eslint no-const-assign: "error"*/ -/*eslint-env es6*/ for (const a in [1, 2, 3]) { // `a` is re-defined (not modified) on each loop step. console.log(a); @@ -84,7 +79,6 @@ for (const a in [1, 2, 3]) { // `a` is re-defined (not modified) on each loop st ```js /*eslint no-const-assign: "error"*/ -/*eslint-env es6*/ for (const a of [1, 2, 3]) { // `a` is re-defined (not modified) on each loop step. console.log(a); diff --git a/docs/src/rules/no-dupe-class-members.md b/docs/src/rules/no-dupe-class-members.md index 6565c9f15ee..d186d33e21c 100644 --- a/docs/src/rules/no-dupe-class-members.md +++ b/docs/src/rules/no-dupe-class-members.md @@ -10,8 +10,6 @@ If there are declarations of the same name in class members, the last declaratio It can cause unexpected behaviors. ```js -/*eslint-env es6*/ - class Foo { bar() { console.log("hello"); } bar() { console.log("goodbye"); } diff --git a/docs/src/rules/no-empty-function.md b/docs/src/rules/no-empty-function.md index 68aa2ad887b..2810175e3ee 100644 --- a/docs/src/rules/no-empty-function.md +++ b/docs/src/rules/no-empty-function.md @@ -34,7 +34,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-empty-function: "error"*/ -/*eslint-env es6*/ function foo() {} @@ -89,7 +88,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-empty-function: "error"*/ -/*eslint-env es6*/ function foo() { // do nothing. @@ -222,7 +220,6 @@ Examples of **correct** code for the `{ "allow": ["arrowFunctions"] }` option: ```js /*eslint no-empty-function: ["error", { "allow": ["arrowFunctions"] }]*/ -/*eslint-env es6*/ var foo = () => {}; ``` @@ -237,7 +234,6 @@ Examples of **correct** code for the `{ "allow": ["generatorFunctions"] }` optio ```js /*eslint no-empty-function: ["error", { "allow": ["generatorFunctions"] }]*/ -/*eslint-env es6*/ function* foo() {} @@ -258,7 +254,6 @@ Examples of **correct** code for the `{ "allow": ["methods"] }` option: ```js /*eslint no-empty-function: ["error", { "allow": ["methods"] }]*/ -/*eslint-env es6*/ var obj = { foo() {} @@ -280,7 +275,6 @@ Examples of **correct** code for the `{ "allow": ["generatorMethods"] }` option: ```js /*eslint no-empty-function: ["error", { "allow": ["generatorMethods"] }]*/ -/*eslint-env es6*/ var obj = { *foo() {} @@ -302,7 +296,6 @@ Examples of **correct** code for the `{ "allow": ["getters"] }` option: ```js /*eslint no-empty-function: ["error", { "allow": ["getters"] }]*/ -/*eslint-env es6*/ var obj = { get foo() {} @@ -324,7 +317,6 @@ Examples of **correct** code for the `{ "allow": ["setters"] }` option: ```js /*eslint no-empty-function: ["error", { "allow": ["setters"] }]*/ -/*eslint-env es6*/ var obj = { set foo(value) {} @@ -346,7 +338,6 @@ Examples of **correct** code for the `{ "allow": ["constructors"] }` option: ```js /*eslint no-empty-function: ["error", { "allow": ["constructors"] }]*/ -/*eslint-env es6*/ class A { constructor() {} @@ -363,7 +354,6 @@ Examples of **correct** code for the `{ "allow": ["asyncFunctions"] }` options: ```js /*eslint no-empty-function: ["error", { "allow": ["asyncFunctions"] }]*/ -/*eslint-env es2017*/ async function a(){} ``` @@ -378,7 +368,6 @@ Examples of **correct** code for the `{ "allow": ["asyncMethods"] }` options: ```js /*eslint no-empty-function: ["error", { "allow": ["asyncMethods"] }]*/ -/*eslint-env es2017*/ var obj = { async foo() {} diff --git a/docs/src/rules/no-eval.md b/docs/src/rules/no-eval.md index 64f7c78b3d6..69b4f19828d 100644 --- a/docs/src/rules/no-eval.md +++ b/docs/src/rules/no-eval.md @@ -43,26 +43,26 @@ this.eval("var a = 0"); ::: -Example of additional **incorrect** code for this rule when `browser` environment is set to `true`: +Example of additional **incorrect** code for this rule with `window` global variable: ::: incorrect ```js /*eslint no-eval: "error"*/ -/*eslint-env browser*/ +/*global window*/ window.eval("var a = 0"); ``` ::: -Example of additional **incorrect** code for this rule when `node` environment is set to `true`: +Example of additional **incorrect** code for this rule with `global` global variable: ::: incorrect ```js /*eslint no-eval: "error"*/ -/*eslint-env node*/ +/*global global*/ global.eval("var a = 0"); ``` @@ -75,7 +75,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-eval: "error"*/ -/*eslint-env es6*/ var obj = { x: "foo" }, key = "x", @@ -150,7 +149,7 @@ this.eval("var a = 0"); ```js /*eslint no-eval: ["error", {"allowIndirect": true} ]*/ -/*eslint-env browser*/ +/*global window*/ window.eval("var a = 0"); ``` @@ -161,7 +160,7 @@ window.eval("var a = 0"); ```js /*eslint no-eval: ["error", {"allowIndirect": true} ]*/ -/*eslint-env node*/ +/*global global*/ global.eval("var a = 0"); ``` diff --git a/docs/src/rules/no-extra-bind.md b/docs/src/rules/no-extra-bind.md index 073425cf13e..7617e48196a 100644 --- a/docs/src/rules/no-extra-bind.md +++ b/docs/src/rules/no-extra-bind.md @@ -45,7 +45,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-extra-bind: "error"*/ -/*eslint-env es6*/ var x = function () { foo(); diff --git a/docs/src/rules/no-global-assign.md b/docs/src/rules/no-global-assign.md index dcdd05d519e..9791b0bba37 100644 --- a/docs/src/rules/no-global-assign.md +++ b/docs/src/rules/no-global-assign.md @@ -43,22 +43,9 @@ undefined = 1 ```js /*eslint no-global-assign: "error"*/ -/*eslint-env browser*/ +/*global window:readonly*/ window = {} -length = 1 -top = 1 -``` - -::: - -::: incorrect - -```js -/*eslint no-global-assign: "error"*/ -/*global a:readonly*/ - -a = 1 ``` ::: @@ -81,24 +68,13 @@ b = 2 ```js /*eslint no-global-assign: "error"*/ -/*eslint-env browser*/ +/*global onload:writable*/ onload = function() {} ``` ::: -::: correct - -```js -/*eslint no-global-assign: "error"*/ -/*global a:writable*/ - -a = 1 -``` - -::: - ## Options This rule accepts an `exceptions` option, which can be used to specify a list of builtins for which reassignments will be allowed: diff --git a/docs/src/rules/no-implied-eval.md b/docs/src/rules/no-implied-eval.md index cfd067769cc..f763059bc5b 100644 --- a/docs/src/rules/no-implied-eval.md +++ b/docs/src/rules/no-implied-eval.md @@ -35,7 +35,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-implied-eval: "error"*/ -/*eslint-env browser*/ +/*global window*/ setTimeout("alert('Hi!');", 100); diff --git a/docs/src/rules/no-inner-declarations.md b/docs/src/rules/no-inner-declarations.md index d5a36cf0f28..61aec8c9850 100644 --- a/docs/src/rules/no-inner-declarations.md +++ b/docs/src/rules/no-inner-declarations.md @@ -47,8 +47,6 @@ doSomething(); // error A variable declaration is permitted anywhere a statement can go, even nested deeply inside other blocks. This is often undesirable due to variable hoisting, and moving declarations to the root of the program or function body can increase clarity. Note that [block bindings](https://leanpub.com/understandinges6/read#leanpub-auto-block-bindings) (`let`, `const`) are not hoisted and therefore they are not affected by this rule. ```js -/*eslint-env es6*/ - // Good var foo = 42; @@ -126,7 +124,7 @@ function doSomethingElse() { function doSomethingElse() { "use strict"; - + if (test) { function doAnotherThing() { } } diff --git a/docs/src/rules/no-invalid-this.md b/docs/src/rules/no-invalid-this.md index 454fdbd7185..31c1a3b12f6 100644 --- a/docs/src/rules/no-invalid-this.md +++ b/docs/src/rules/no-invalid-this.md @@ -53,7 +53,6 @@ Examples of **incorrect** code for this rule in strict mode: ```js /*eslint no-invalid-this: "error"*/ -/*eslint-env es6*/ "use strict"; @@ -101,7 +100,6 @@ Examples of **correct** code for this rule in strict mode: ```js /*eslint no-invalid-this: "error"*/ -/*eslint-env es6*/ "use strict"; diff --git a/docs/src/rules/no-irregular-whitespace.md b/docs/src/rules/no-irregular-whitespace.md index 4118defe631..d7cd83402fc 100644 --- a/docs/src/rules/no-irregular-whitespace.md +++ b/docs/src/rules/no-irregular-whitespace.md @@ -117,7 +117,6 @@ var thing = function() { return / regexp/; } -/*eslint-env es6*/ var thing = function() { return `template string`; } @@ -191,7 +190,6 @@ Examples of additional **correct** code for this rule with the `{ "skipTemplates ```js /*eslint no-irregular-whitespace: ["error", { "skipTemplates": true }]*/ -/*eslint-env es6*/ function thing() { return `template string`; @@ -208,7 +206,6 @@ Examples of additional **correct** code for this rule with the `{ "skipJSXText": ```jsx /*eslint no-irregular-whitespace: ["error", { "skipJSXText": true }]*/ -/*eslint-env es6*/ function Thing() { return
text in JSX
; // before `JSX` diff --git a/docs/src/rules/no-lone-blocks.md b/docs/src/rules/no-lone-blocks.md index 01eee9f0600..2fda916140a 100644 --- a/docs/src/rules/no-lone-blocks.md +++ b/docs/src/rules/no-lone-blocks.md @@ -60,13 +60,12 @@ class C { ::: -Examples of **correct** code for this rule with ES6 environment: +Examples of **correct** code for this rule: ::: correct ```js /*eslint no-lone-blocks: "error"*/ -/*eslint-env es6*/ while (foo) { bar(); @@ -118,7 +117,6 @@ Examples of **correct** code for this rule with ES6 environment and strict mode ```js /*eslint no-lone-blocks: "error"*/ -/*eslint-env es6*/ "use strict"; diff --git a/docs/src/rules/no-loop-func.md b/docs/src/rules/no-loop-func.md index 8fed2d6d909..7fe45e21647 100644 --- a/docs/src/rules/no-loop-func.md +++ b/docs/src/rules/no-loop-func.md @@ -19,8 +19,6 @@ In this case, you would expect each function created within the loop to return a `let` or `const` mitigate this problem. ```js -/*eslint-env es6*/ - for (let i = 0; i < 10; i++) { funcs[i] = function() { return i; @@ -42,7 +40,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-loop-func: "error"*/ -/*eslint-env es6*/ for (var i=10; i; i--) { (function() { return i; })(); @@ -86,7 +83,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-loop-func: "error"*/ -/*eslint-env es6*/ var a = function() {}; diff --git a/docs/src/rules/no-native-reassign.md b/docs/src/rules/no-native-reassign.md index c2c658f7705..2ec916404b6 100644 --- a/docs/src/rules/no-native-reassign.md +++ b/docs/src/rules/no-native-reassign.md @@ -44,22 +44,9 @@ undefined = 1 ```js /*eslint no-native-reassign: "error"*/ -/*eslint-env browser*/ +/*global window:readonly*/ window = {} -length = 1 -top = 1 -``` - -::: - -::: incorrect - -```js -/*eslint no-native-reassign: "error"*/ -/*global a:readonly*/ - -a = 1 ``` ::: @@ -82,24 +69,13 @@ b = 2 ```js /*eslint no-native-reassign: "error"*/ -/*eslint-env browser*/ +/*global onload:writable*/ onload = function() {} ``` ::: -::: correct - -```js -/*eslint no-native-reassign: "error"*/ -/*global a:writable*/ - -a = 1 -``` - -::: - ## Options This rule accepts an `exceptions` option, which can be used to specify a list of builtins for which reassignments will be allowed: diff --git a/docs/src/rules/no-new-native-nonconstructor.md b/docs/src/rules/no-new-native-nonconstructor.md index c9b75679a1e..721f6697f37 100644 --- a/docs/src/rules/no-new-native-nonconstructor.md +++ b/docs/src/rules/no-new-native-nonconstructor.md @@ -38,7 +38,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-new-native-nonconstructor: "error"*/ -/*eslint-env es2022*/ var foo = new Symbol('foo'); var bar = new BigInt(9007199254740991); @@ -52,7 +51,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-new-native-nonconstructor: "error"*/ -/*eslint-env es2022*/ var foo = Symbol('foo'); var bar = BigInt(9007199254740991); diff --git a/docs/src/rules/no-new-symbol.md b/docs/src/rules/no-new-symbol.md index 2be75ee9e24..bc93ce212c5 100644 --- a/docs/src/rules/no-new-symbol.md +++ b/docs/src/rules/no-new-symbol.md @@ -28,7 +28,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-new-symbol: "error"*/ -/*eslint-env es6*/ var foo = new Symbol('foo'); ``` @@ -41,7 +40,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-new-symbol: "error"*/ -/*eslint-env es6*/ var foo = Symbol('foo'); diff --git a/docs/src/rules/no-obj-calls.md b/docs/src/rules/no-obj-calls.md index 2fde92e5cbf..56e9f914153 100644 --- a/docs/src/rules/no-obj-calls.md +++ b/docs/src/rules/no-obj-calls.md @@ -38,7 +38,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-obj-calls: "error"*/ -/*eslint-env es2017, browser */ +/*global Intl*/ var math = Math(); @@ -69,7 +69,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-obj-calls: "error"*/ -/*eslint-env es2017, browser*/ +/*global Intl*/ function area(r) { return Math.PI * r * r; diff --git a/docs/src/rules/no-promise-executor-return.md b/docs/src/rules/no-promise-executor-return.md index f163d9ebf0b..d82e4473477 100644 --- a/docs/src/rules/no-promise-executor-return.md +++ b/docs/src/rules/no-promise-executor-return.md @@ -38,7 +38,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-promise-executor-return: "error"*/ -/*eslint-env es6*/ new Promise((resolve, reject) => { if (someCondition) { @@ -76,7 +75,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-promise-executor-return: "error"*/ -/*eslint-env es6*/ // Turn return inline into two lines new Promise((resolve, reject) => { @@ -125,7 +123,6 @@ Examples of **correct** code for this rule with the `{ "allowVoid": true }` opti ```js /*eslint no-promise-executor-return: ["error", { allowVoid: true }]*/ -/*eslint-env es6*/ new Promise((resolve, reject) => { if (someCondition) { diff --git a/docs/src/rules/no-redeclare.md b/docs/src/rules/no-redeclare.md index e3ce497753e..28104809a48 100644 --- a/docs/src/rules/no-redeclare.md +++ b/docs/src/rules/no-redeclare.md @@ -87,19 +87,4 @@ var Object = 0; ::: -Examples of **incorrect** code for the `{ "builtinGlobals": true }` option and the `browser` environment: - -::: incorrect { "sourceType": "script" } - -```js -/*eslint no-redeclare: ["error", { "builtinGlobals": true }]*/ -/*eslint-env browser*/ - -var top = 0; -``` - -::: - -The `browser` environment has many built-in global variables (for example, `top`). Some of built-in global variables cannot be redeclared. - -Note that when using the `node` or `commonjs` environments (or `ecmaFeatures.globalReturn`, if using the default parser), the top scope of a program is not actually the global scope, but rather a "module" scope. When this is the case, declaring a variable named after a builtin global is not a redeclaration, but rather a shadowing of the global variable. In that case, the [`no-shadow`](no-shadow) rule with the `"builtinGlobals"` option should be used. +Note that when using `sourceType: "commonjs"` (or `ecmaFeatures.globalReturn`, if using the default parser), the top scope of a program is not actually the global scope, but rather a "module" scope. When this is the case, declaring a variable named after a builtin global is not a redeclaration, but rather a shadowing of the global variable. In that case, the [`no-shadow`](no-shadow) rule with the `"builtinGlobals"` option should be used. diff --git a/docs/src/rules/no-shadow.md b/docs/src/rules/no-shadow.md index 9b9a27ee7d3..a2c7d0a4407 100644 --- a/docs/src/rules/no-shadow.md +++ b/docs/src/rules/no-shadow.md @@ -29,7 +29,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-shadow: "error"*/ -/*eslint-env es6*/ var a = 3; function b() { @@ -97,7 +96,6 @@ Examples of **incorrect** code for the default `{ "hoist": "functions" }` option ```js /*eslint no-shadow: ["error", { "hoist": "functions" }]*/ -/*eslint-env es6*/ if (true) { let b = 6; @@ -116,7 +114,6 @@ Examples of **correct** code for the default `{ "hoist": "functions" }` option: ```js /*eslint no-shadow: ["error", { "hoist": "functions" }]*/ -/*eslint-env es6*/ if (true) { let a = 3; @@ -137,7 +134,6 @@ Examples of **incorrect** code for the `{ "hoist": "all" }` option: ```js /*eslint no-shadow: ["error", { "hoist": "all" }]*/ -/*eslint-env es6*/ if (true) { let a = 3; @@ -158,7 +154,6 @@ Examples of **correct** code for the `{ "hoist": "never" }` option: ```js /*eslint no-shadow: ["error", { "hoist": "never" }]*/ -/*eslint-env es6*/ if (true) { let a = 3; @@ -183,7 +178,6 @@ Examples of **correct** code for the `{ "allow": ["done"] }` option: ```js /*eslint no-shadow: ["error", { "allow": ["done"] }]*/ -/*eslint-env es6*/ import async from 'async'; diff --git a/docs/src/rules/no-this-before-super.md b/docs/src/rules/no-this-before-super.md index a06b5735553..b26c920dd73 100644 --- a/docs/src/rules/no-this-before-super.md +++ b/docs/src/rules/no-this-before-super.md @@ -22,7 +22,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-this-before-super: "error"*/ -/*eslint-env es6*/ class A1 extends B { constructor() { @@ -60,7 +59,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-this-before-super: "error"*/ -/*eslint-env es6*/ class A1 { constructor() { diff --git a/docs/src/rules/no-throw-literal.md b/docs/src/rules/no-throw-literal.md index 1ab939ff3a0..a837e5a9e92 100644 --- a/docs/src/rules/no-throw-literal.md +++ b/docs/src/rules/no-throw-literal.md @@ -19,7 +19,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-throw-literal: "error"*/ -/*eslint-env es6*/ throw "error"; diff --git a/docs/src/rules/no-useless-concat.md b/docs/src/rules/no-useless-concat.md index 4cdf32d5d71..a0f214a50c2 100644 --- a/docs/src/rules/no-useless-concat.md +++ b/docs/src/rules/no-useless-concat.md @@ -26,7 +26,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-useless-concat: "error"*/ -/*eslint-env es6*/ var a = `some` + `string`; diff --git a/docs/src/rules/no-useless-constructor.md b/docs/src/rules/no-useless-constructor.md index bf2c4c32984..f54a3cbdb77 100644 --- a/docs/src/rules/no-useless-constructor.md +++ b/docs/src/rules/no-useless-constructor.md @@ -31,7 +31,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-useless-constructor: "error"*/ -/*eslint-env es6*/ class A { constructor () { diff --git a/docs/src/rules/no-var.md b/docs/src/rules/no-var.md index d1e02ad14d7..c34f56050cd 100644 --- a/docs/src/rules/no-var.md +++ b/docs/src/rules/no-var.md @@ -47,7 +47,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-var: "error"*/ -/*eslint-env es6*/ let x = "y"; const CONFIG = {}; diff --git a/docs/src/rules/no-with.md b/docs/src/rules/no-with.md index c4e880f812d..b2f1f15b7a4 100644 --- a/docs/src/rules/no-with.md +++ b/docs/src/rules/no-with.md @@ -35,7 +35,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-with: "error"*/ -/*eslint-env es6*/ const r = ({x, y}) => Math.sqrt(x * x + y * y); ``` diff --git a/docs/src/rules/object-curly-newline.md b/docs/src/rules/object-curly-newline.md index fd990920c69..1def4d8d7e3 100644 --- a/docs/src/rules/object-curly-newline.md +++ b/docs/src/rules/object-curly-newline.md @@ -55,7 +55,6 @@ Examples of **incorrect** code for this rule with the `"always"` option: ```js /*eslint object-curly-newline: ["error", "always"]*/ -/*eslint-env es6*/ let a = {}; let b = {foo: 1}; @@ -84,7 +83,6 @@ Examples of **correct** code for this rule with the `"always"` option: ```js /*eslint object-curly-newline: ["error", "always"]*/ -/*eslint-env es6*/ let a = { }; @@ -133,7 +131,6 @@ Examples of **incorrect** code for this rule with the `"never"` option: ```js /*eslint object-curly-newline: ["error", "never"]*/ -/*eslint-env es6*/ let a = { }; @@ -180,7 +177,6 @@ Examples of **correct** code for this rule with the `"never"` option: ```js /*eslint object-curly-newline: ["error", "never"]*/ -/*eslint-env es6*/ let a = {}; let b = {foo: 1}; @@ -211,7 +207,6 @@ Examples of **incorrect** code for this rule with the `{ "multiline": true }` op ```js /*eslint object-curly-newline: ["error", { "multiline": true }]*/ -/*eslint-env es6*/ let a = { }; @@ -250,7 +245,6 @@ Examples of **correct** code for this rule with the `{ "multiline": true }` opti ```js /*eslint object-curly-newline: ["error", { "multiline": true }]*/ -/*eslint-env es6*/ let a = {}; let b = {foo: 1}; @@ -289,7 +283,6 @@ Examples of **incorrect** code for this rule with the `{ "minProperties": 2 }` o ```js /*eslint object-curly-newline: ["error", { "minProperties": 2 }]*/ -/*eslint-env es6*/ let a = { }; @@ -328,7 +321,6 @@ Examples of **correct** code for this rule with the `{ "minProperties": 2 }` opt ```js /*eslint object-curly-newline: ["error", { "minProperties": 2 }]*/ -/*eslint-env es6*/ let a = {}; let b = {foo: 1}; @@ -367,7 +359,6 @@ Examples of **incorrect** code for this rule with the default `{ "consistent": t ```js /*eslint object-curly-newline: ["error", { "consistent": true }]*/ -/*eslint-env es6*/ let a = {foo: 1 }; @@ -415,7 +406,6 @@ Examples of **correct** code for this rule with the default `{ "consistent": tru ```js /*eslint object-curly-newline: ["error", { "consistent": true }]*/ -/*eslint-env es6*/ let empty1 = {}; let empty2 = { @@ -473,7 +463,6 @@ Examples of **incorrect** code for this rule with the `{ "ObjectExpression": "al ```js /*eslint object-curly-newline: ["error", { "ObjectExpression": "always", "ObjectPattern": "never" }]*/ -/*eslint-env es6*/ let a = {}; let b = {foo: 1}; @@ -511,7 +500,6 @@ Examples of **correct** code for this rule with the `{ "ObjectExpression": "alwa ```js /*eslint object-curly-newline: ["error", { "ObjectExpression": "always", "ObjectPattern": "never" }]*/ -/*eslint-env es6*/ let a = { }; @@ -551,7 +539,6 @@ Examples of **incorrect** code for this rule with the `{ "ImportDeclaration": "a ```js /*eslint object-curly-newline: ["error", { "ImportDeclaration": "always", "ExportDeclaration": "never" }]*/ -/*eslint-env es6*/ import {foo, bar} from 'foo-bar'; import {foo as f, baz} from 'foo-bar'; @@ -576,7 +563,6 @@ Examples of **correct** code for this rule with the `{ "ImportDeclaration": "alw ```js /*eslint object-curly-newline: ["error", { "ImportDeclaration": "always", "ExportDeclaration": "never" }]*/ -/*eslint-env es6*/ import { foo, diff --git a/docs/src/rules/object-shorthand.md b/docs/src/rules/object-shorthand.md index 299d46b9664..55976410004 100644 --- a/docs/src/rules/object-shorthand.md +++ b/docs/src/rules/object-shorthand.md @@ -32,8 +32,6 @@ var foo = { Now here are ES6 equivalents: ```js -/*eslint-env es6*/ - // properties var foo = {x, y, z}; @@ -54,7 +52,6 @@ Each of the following properties would warn: ```js /*eslint object-shorthand: "error"*/ -/*eslint-env es6*/ var foo = { w: function() {}, @@ -68,7 +65,6 @@ In that case the expected syntax would have been: ```js /*eslint object-shorthand: "error"*/ -/*eslint-env es6*/ var foo = { w() {}, @@ -83,7 +79,6 @@ The following will *not* warn: ```js /*eslint object-shorthand: "error"*/ -/*eslint-env es6*/ var foo = { x: (y) => y @@ -130,7 +125,6 @@ Example of **incorrect** code for this rule with the `"always", { "avoidQuotes": ```js /*eslint object-shorthand: ["error", "always", { "avoidQuotes": true }]*/ -/*eslint-env es6*/ var foo = { "bar-baz"() {} @@ -145,7 +139,6 @@ Example of **correct** code for this rule with the `"always", { "avoidQuotes": t ```js /*eslint object-shorthand: ["error", "always", { "avoidQuotes": true }]*/ -/*eslint-env es6*/ var foo = { "bar-baz": function() {}, @@ -169,7 +162,6 @@ Example of **correct** code for this rule with the `"always", { "ignoreConstruct ```js /*eslint object-shorthand: ["error", "always", { "ignoreConstructors": true }]*/ -/*eslint-env es6*/ var foo = { ConstructorFunction: function() {} @@ -208,7 +200,6 @@ Example of **incorrect** code for this rule with the `"always", { "avoidExplicit ```js /*eslint object-shorthand: ["error", "always", { "avoidExplicitReturnArrows": true }]*/ -/*eslint-env es6*/ var foo = { foo: (bar, baz) => { @@ -229,7 +220,6 @@ Example of **correct** code for this rule with the `"always", { "avoidExplicitRe ```js /*eslint object-shorthand: ["error", "always", { "avoidExplicitReturnArrows": true }]*/ -/*eslint-env es6*/ var foo = { foo(bar, baz) { @@ -248,7 +238,6 @@ Example of **incorrect** code for this rule with the `"consistent"` option: ```js /*eslint object-shorthand: [2, "consistent"]*/ -/*eslint-env es6*/ var foo = { a, @@ -264,7 +253,6 @@ Examples of **correct** code for this rule with the `"consistent"` option: ```js /*eslint object-shorthand: [2, "consistent"]*/ -/*eslint-env es6*/ var foo = { a: a, @@ -285,7 +273,6 @@ Example of **incorrect** code with the `"consistent-as-needed"` option, which is ```js /*eslint object-shorthand: [2, "consistent-as-needed"]*/ -/*eslint-env es6*/ var foo = { a: a, diff --git a/docs/src/rules/one-var-declaration-per-line.md b/docs/src/rules/one-var-declaration-per-line.md index 8f06275780f..ec98b4afb2e 100644 --- a/docs/src/rules/one-var-declaration-per-line.md +++ b/docs/src/rules/one-var-declaration-per-line.md @@ -42,7 +42,6 @@ Examples of **incorrect** code for this rule with the default `"initializations" ```js /*eslint one-var-declaration-per-line: ["error", "initializations"]*/ -/*eslint-env es6*/ var a, b, c = 0; @@ -58,7 +57,6 @@ Examples of **correct** code for this rule with the default `"initializations"` ```js /*eslint one-var-declaration-per-line: ["error", "initializations"]*/ -/*eslint-env es6*/ var a, b; @@ -79,7 +77,6 @@ Examples of **incorrect** code for this rule with the `"always"` option: ```js /*eslint one-var-declaration-per-line: ["error", "always"]*/ -/*eslint-env es6*/ var a, b; @@ -96,7 +93,6 @@ Examples of **correct** code for this rule with the `"always"` option: ```js /*eslint one-var-declaration-per-line: ["error", "always"]*/ -/*eslint-env es6*/ var a, b; diff --git a/docs/src/rules/one-var.md b/docs/src/rules/one-var.md index 0066757e1da..8f9e5c450ec 100644 --- a/docs/src/rules/one-var.md +++ b/docs/src/rules/one-var.md @@ -347,7 +347,6 @@ Examples of **incorrect** code for this rule with the `{ var: "always", let: "ne ```js /*eslint one-var: ["error", { var: "always", let: "never", const: "never" }]*/ -/*eslint-env es6*/ function foo1() { var bar; @@ -372,7 +371,6 @@ Examples of **correct** code for this rule with the `{ var: "always", let: "neve ```js /*eslint one-var: ["error", { var: "always", let: "never", const: "never" }]*/ -/*eslint-env es6*/ function foo1() { var bar, @@ -397,7 +395,6 @@ Examples of **incorrect** code for this rule with the `{ var: "never" }` option: ```js /*eslint one-var: ["error", { var: "never" }]*/ -/*eslint-env es6*/ function foo() { var bar, @@ -413,7 +410,6 @@ Examples of **correct** code for this rule with the `{ var: "never" }` option: ```js /*eslint one-var: ["error", { var: "never" }]*/ -/*eslint-env es6*/ function foo() { var bar; @@ -437,7 +433,6 @@ Examples of **incorrect** code for this rule with the `{ separateRequires: true ```js /*eslint one-var: ["error", { separateRequires: true, var: "always" }]*/ -/*eslint-env node*/ var foo = require("foo"), bar = "bar"; @@ -451,7 +446,6 @@ Examples of **correct** code for this rule with the `{ separateRequires: true }` ```js /*eslint one-var: ["error", { separateRequires: true, var: "always" }]*/ -/*eslint-env node*/ var foo = require("foo"); var bar = "bar"; @@ -463,7 +457,6 @@ var bar = "bar"; ```js /*eslint one-var: ["error", { separateRequires: true, var: "always" }]*/ -/*eslint-env node*/ var foo = require("foo"), bar = require("bar"); @@ -477,7 +470,6 @@ Examples of **incorrect** code for this rule with the `{ var: "never", let: "con ```js /*eslint one-var: ["error", { var: "never", let: "consecutive", const: "consecutive" }]*/ -/*eslint-env es6*/ function foo1() { let a, @@ -506,7 +498,6 @@ Examples of **correct** code for this rule with the `{ var: "never", let: "conse ```js /*eslint one-var: ["error", { var: "never", let: "consecutive", const: "consecutive" }]*/ -/*eslint-env es6*/ function foo1() { let a, @@ -537,7 +528,6 @@ Examples of **incorrect** code for this rule with the `{ var: "consecutive" }` o ```js /*eslint one-var: ["error", { var: "consecutive" }]*/ -/*eslint-env es6*/ function foo() { var a; @@ -553,7 +543,6 @@ Examples of **correct** code for this rule with the `{ var: "consecutive" }` opt ```js /*eslint one-var: ["error", { var: "consecutive" }]*/ -/*eslint-env es6*/ function foo() { var a, @@ -575,7 +564,6 @@ Examples of **incorrect** code for this rule with the `{ "initialized": "always" ```js /*eslint one-var: ["error", { "initialized": "always", "uninitialized": "never" }]*/ -/*eslint-env es6*/ function foo() { var a, b, c; @@ -619,7 +607,6 @@ Examples of **incorrect** code for this rule with the `{ "initialized": "never" ```js /*eslint one-var: ["error", { "initialized": "never" }]*/ -/*eslint-env es6*/ function foo() { var foo = true, diff --git a/docs/src/rules/prefer-arrow-callback.md b/docs/src/rules/prefer-arrow-callback.md index d3e9000561c..dd9537d2600 100644 --- a/docs/src/rules/prefer-arrow-callback.md +++ b/docs/src/rules/prefer-arrow-callback.md @@ -45,7 +45,6 @@ The following examples **will not** be flagged: ```js /* eslint prefer-arrow-callback: "error" */ -/* eslint-env es6 */ // arrow function callback foo(a => a); // OK @@ -101,7 +100,6 @@ When set to `false` this option prohibits the use of function expressions as cal ```js /* eslint prefer-arrow-callback: [ "error", { "allowUnboundThis": false } ] */ -/* eslint-env es6 */ foo(function() { this.a; }); diff --git a/docs/src/rules/prefer-const.md b/docs/src/rules/prefer-const.md index 41b144fe466..b61b5e6ae73 100644 --- a/docs/src/rules/prefer-const.md +++ b/docs/src/rules/prefer-const.md @@ -149,7 +149,6 @@ Examples of **incorrect** code for the default `{"destructuring": "any"}` option ```js /*eslint prefer-const: "error"*/ -/*eslint-env es6*/ let {a, b} = obj; /*error 'b' is never reassigned, use 'const' instead.*/ a = a + 1; @@ -163,7 +162,6 @@ Examples of **correct** code for the default `{"destructuring": "any"}` option: ```js /*eslint prefer-const: "error"*/ -/*eslint-env es6*/ // using const. const {a: a0, b} = obj; @@ -183,7 +181,6 @@ Examples of **incorrect** code for the `{"destructuring": "all"}` option: ```js /*eslint prefer-const: ["error", {"destructuring": "all"}]*/ -/*eslint-env es6*/ // all of `a` and `b` should be const, so those are warned. let {a, b} = obj; /*error 'a' is never reassigned, use 'const' instead. @@ -198,7 +195,6 @@ Examples of **correct** code for the `{"destructuring": "all"}` option: ```js /*eslint prefer-const: ["error", {"destructuring": "all"}]*/ -/*eslint-env es6*/ // 'b' is never reassigned, but all of `a` and `b` should not be const, so those are ignored. let {a, b} = obj; @@ -219,7 +215,6 @@ Examples of **correct** code for the `{"ignoreReadBeforeAssign": true}` option: ```js /*eslint prefer-const: ["error", {"ignoreReadBeforeAssign": true}]*/ -/*eslint-env es6*/ let timer; function initialize() { @@ -238,7 +233,6 @@ Examples of **correct** code for the default `{"ignoreReadBeforeAssign": false}` ```js /*eslint prefer-const: ["error", {"ignoreReadBeforeAssign": false}]*/ -/*eslint-env es6*/ const timer = setInterval(initialize, 100); function initialize() { diff --git a/docs/src/rules/prefer-numeric-literals.md b/docs/src/rules/prefer-numeric-literals.md index a90fd7dffd2..ad579640140 100644 --- a/docs/src/rules/prefer-numeric-literals.md +++ b/docs/src/rules/prefer-numeric-literals.md @@ -40,7 +40,6 @@ Examples of **correct** code for this rule: ```js /*eslint prefer-numeric-literals: "error"*/ -/*eslint-env es6*/ parseInt(1); parseInt(1, 3); diff --git a/docs/src/rules/prefer-spread.md b/docs/src/rules/prefer-spread.md index 4b696b55cac..18a4538e6b6 100644 --- a/docs/src/rules/prefer-spread.md +++ b/docs/src/rules/prefer-spread.md @@ -16,8 +16,6 @@ Math.max.apply(Math, args); In ES2015, one can use spread syntax to call variadic functions. ```js -/*eslint-env es6*/ - var args = [1, 2, 3, 4]; Math.max(...args); ``` diff --git a/docs/src/rules/prefer-template.md b/docs/src/rules/prefer-template.md index c2397b5b2f7..015865bc231 100644 --- a/docs/src/rules/prefer-template.md +++ b/docs/src/rules/prefer-template.md @@ -15,8 +15,6 @@ var str = "Hello, " + name + "!"; ``` ```js -/*eslint-env es6*/ - var str = `Hello, ${name}!`; ``` @@ -45,7 +43,6 @@ Examples of **correct** code for this rule: ```js /*eslint prefer-template: "error"*/ -/*eslint-env es6*/ var str = "Hello World!"; var str = `Hello, ${name}!`; diff --git a/docs/src/rules/quote-props.md b/docs/src/rules/quote-props.md index 8a22c1a3868..cbc8f252b0d 100644 --- a/docs/src/rules/quote-props.md +++ b/docs/src/rules/quote-props.md @@ -83,7 +83,6 @@ Examples of **correct** code for this rule with the default `"always"` option: ```js /*eslint quote-props: ["error", "always"]*/ -/*eslint-env es6*/ var object1 = { "foo": "bar", @@ -131,7 +130,6 @@ Examples of **correct** code for this rule with the `"as-needed"` option: ```js /*eslint quote-props: ["error", "as-needed"]*/ -/*eslint-env es6*/ var object1 = { "a-b": 0, diff --git a/docs/src/rules/quotes.md b/docs/src/rules/quotes.md index 27dede284d6..83e4f628884 100644 --- a/docs/src/rules/quotes.md +++ b/docs/src/rules/quotes.md @@ -8,8 +8,6 @@ This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding ru JavaScript allows you to define strings in one of three ways: double quotes, single quotes, and backticks (as of ECMAScript 6). For example: ```js -/*eslint-env es6*/ - var double = "double"; var single = 'single'; var backtick = `backtick`; // ES6 only @@ -64,7 +62,6 @@ Examples of **correct** code for this rule with the default `"double"` option: ```js /*eslint quotes: ["error", "double"]*/ -/*eslint-env es6*/ var double = "double"; var backtick = `back @@ -95,7 +92,6 @@ Examples of **correct** code for this rule with the `"single"` option: ```js /*eslint quotes: ["error", "single"]*/ -/*eslint-env es6*/ var single = 'single'; var backtick = `back${x}tick`; // backticks are allowed due to substitution @@ -125,7 +121,6 @@ Examples of **correct** code for this rule with the `"backtick"` option: ```js /*eslint quotes: ["error", "backtick"]*/ -/*eslint-env es6*/ "use strict"; // directives must use single or double quotes var backtick = `backtick`; diff --git a/docs/src/rules/require-yield.md b/docs/src/rules/require-yield.md index eec9d0cfc94..2ad53fd4d84 100644 --- a/docs/src/rules/require-yield.md +++ b/docs/src/rules/require-yield.md @@ -19,7 +19,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint require-yield: "error"*/ -/*eslint-env es6*/ function* foo() { return 10; @@ -34,7 +33,6 @@ Examples of **correct** code for this rule: ```js /*eslint require-yield: "error"*/ -/*eslint-env es6*/ function* foo() { yield 5; diff --git a/docs/src/rules/sort-keys.md b/docs/src/rules/sort-keys.md index 7f731193d80..05b973e0f2b 100644 --- a/docs/src/rules/sort-keys.md +++ b/docs/src/rules/sort-keys.md @@ -19,7 +19,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint sort-keys: "error"*/ -/*eslint-env es6*/ let obj1 = {a: 1, c: 3, b: 2}; let obj2 = {a: 1, "c": 3, b: 2}; @@ -45,7 +44,6 @@ Examples of **correct** code for this rule: ```js /*eslint sort-keys: "error"*/ -/*eslint-env es6*/ let obj1 = {a: 1, b: 2, c: 3}; let obj2 = {a: 1, "b": 2, c: 3}; @@ -116,7 +114,6 @@ Examples of **incorrect** code for the `"desc"` option: ```js /*eslint sort-keys: ["error", "desc"]*/ -/*eslint-env es6*/ let obj1 = {b: 2, c: 3, a: 1}; let obj2 = {"b": 2, c: 3, a: 1}; @@ -136,7 +133,6 @@ Examples of **correct** code for the `"desc"` option: ```js /*eslint sort-keys: ["error", "desc"]*/ -/*eslint-env es6*/ let obj1 = {c: 3, b: 2, a: 1}; let obj2 = {c: 3, "b": 2, a: 1}; @@ -158,7 +154,6 @@ Examples of **incorrect** code for the `{caseSensitive: false}` option: ```js /*eslint sort-keys: ["error", "asc", {caseSensitive: false}]*/ -/*eslint-env es6*/ let obj1 = {a: 1, c: 3, C: 4, b: 2}; let obj2 = {a: 1, C: 3, c: 4, b: 2}; @@ -172,7 +167,6 @@ Examples of **correct** code for the `{caseSensitive: false}` option: ```js /*eslint sort-keys: ["error", "asc", {caseSensitive: false}]*/ -/*eslint-env es6*/ let obj1 = {a: 1, b: 2, c: 3, C: 4}; let obj2 = {a: 1, b: 2, C: 3, c: 4}; @@ -188,7 +182,6 @@ Examples of **incorrect** code for the `{natural: true}` option: ```js /*eslint sort-keys: ["error", "asc", {natural: true}]*/ -/*eslint-env es6*/ let obj = {1: a, 10: c, 2: b}; ``` @@ -201,7 +194,6 @@ Examples of **correct** code for the `{natural: true}` option: ```js /*eslint sort-keys: ["error", "asc", {natural: true}]*/ -/*eslint-env es6*/ let obj = {1: a, 2: b, 10: c}; ``` @@ -216,7 +208,6 @@ Examples of **incorrect** code for the `{minKeys: 4}` option: ```js /*eslint sort-keys: ["error", "asc", {minKeys: 4}]*/ -/*eslint-env es6*/ // 4 keys let obj1 = { @@ -244,7 +235,6 @@ Examples of **correct** code for the `{minKeys: 4}` option: ```js /*eslint sort-keys: ["error", "asc", {minKeys: 4}]*/ -/*eslint-env es6*/ // 3 keys let obj1 = { @@ -270,7 +260,6 @@ Examples of **incorrect** code for the `{allowLineSeparatedGroups: true}` option ```js /*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*/ -/*eslint-env es6*/ let obj1 = { b: 1, @@ -316,7 +305,6 @@ Examples of **correct** code for the `{allowLineSeparatedGroups: true}` option: ```js /*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*/ -/*eslint-env es6*/ let obj1 = { e: 1, @@ -342,7 +330,7 @@ let obj3 = { b () { - }, + }, e: 3, } diff --git a/docs/src/rules/space-before-blocks.md b/docs/src/rules/space-before-blocks.md index 61c60eb51ad..58edc3a3bbb 100644 --- a/docs/src/rules/space-before-blocks.md +++ b/docs/src/rules/space-before-blocks.md @@ -151,7 +151,6 @@ Examples of **incorrect** code for this rule when configured `{ "functions": "ne ```js /*eslint space-before-blocks: ["error", { "functions": "never", "keywords": "always", "classes": "never" }]*/ -/*eslint-env es6*/ function a() {} @@ -170,7 +169,6 @@ Examples of **correct** code for this rule when configured `{ "functions": "neve ```js /*eslint space-before-blocks: ["error", { "functions": "never", "keywords": "always", "classes": "never" }]*/ -/*eslint-env es6*/ for (;;) { // ... @@ -193,7 +191,6 @@ Examples of **incorrect** code for this rule when configured `{ "functions": "al ```js /*eslint space-before-blocks: ["error", { "functions": "always", "keywords": "never", "classes": "never" }]*/ -/*eslint-env es6*/ function a(){} @@ -212,7 +209,6 @@ Examples of **correct** code for this rule when configured `{ "functions": "alwa ```js /*eslint space-before-blocks: ["error", { "functions": "always", "keywords": "never", "classes": "never" }]*/ -/*eslint-env es6*/ if (a){ b(); @@ -233,7 +229,6 @@ Examples of **incorrect** code for this rule when configured `{ "functions": "ne ```js /*eslint space-before-blocks: ["error", { "functions": "never", "keywords": "never", "classes": "always" }]*/ -/*eslint-env es6*/ class Foo{ constructor(){} @@ -248,7 +243,6 @@ Examples of **correct** code for this rule when configured `{ "functions": "neve ```js /*eslint space-before-blocks: ["error", { "functions": "never", "keywords": "never", "classes": "always" }]*/ -/*eslint-env es6*/ class Foo { constructor(){} diff --git a/docs/src/rules/space-before-function-paren.md b/docs/src/rules/space-before-function-paren.md index 1736ff44ef1..f4621c9599f 100644 --- a/docs/src/rules/space-before-function-paren.md +++ b/docs/src/rules/space-before-function-paren.md @@ -65,7 +65,6 @@ Examples of **incorrect** code for this rule with the default `"always"` option: ```js /*eslint space-before-function-paren: "error"*/ -/*eslint-env es6*/ function foo() { // ... @@ -102,7 +101,6 @@ Examples of **correct** code for this rule with the default `"always"` option: ```js /*eslint space-before-function-paren: "error"*/ -/*eslint-env es6*/ function foo () { // ... @@ -141,7 +139,6 @@ Examples of **incorrect** code for this rule with the `"never"` option: ```js /*eslint space-before-function-paren: ["error", "never"]*/ -/*eslint-env es6*/ function foo () { // ... @@ -178,7 +175,6 @@ Examples of **correct** code for this rule with the `"never"` option: ```js /*eslint space-before-function-paren: ["error", "never"]*/ -/*eslint-env es6*/ function foo() { // ... @@ -217,7 +213,6 @@ Examples of **incorrect** code for this rule with the `{"anonymous": "always", " ```js /*eslint space-before-function-paren: ["error", {"anonymous": "always", "named": "never", "asyncArrow": "always"}]*/ -/*eslint-env es6*/ function foo () { // ... @@ -250,7 +245,6 @@ Examples of **correct** code for this rule with the `{"anonymous": "always", "na ```js /*eslint space-before-function-paren: ["error", {"anonymous": "always", "named": "never", "asyncArrow": "always"}]*/ -/*eslint-env es6*/ function foo() { // ... @@ -285,7 +279,6 @@ Examples of **incorrect** code for this rule with the `{"anonymous": "never", "n ```js /*eslint space-before-function-paren: ["error", { "anonymous": "never", "named": "always" }]*/ -/*eslint-env es6*/ function foo() { // ... @@ -316,7 +309,6 @@ Examples of **correct** code for this rule with the `{"anonymous": "never", "nam ```js /*eslint space-before-function-paren: ["error", { "anonymous": "never", "named": "always" }]*/ -/*eslint-env es6*/ function foo () { // ... @@ -349,7 +341,6 @@ Examples of **incorrect** code for this rule with the `{"anonymous": "ignore", " ```js /*eslint space-before-function-paren: ["error", { "anonymous": "ignore", "named": "always" }]*/ -/*eslint-env es6*/ function foo() { // ... @@ -376,7 +367,6 @@ Examples of **correct** code for this rule with the `{"anonymous": "ignore", "na ```js /*eslint space-before-function-paren: ["error", { "anonymous": "ignore", "named": "always" }]*/ -/*eslint-env es6*/ var bar = function() { // ... diff --git a/docs/src/rules/space-before-function-parentheses.md b/docs/src/rules/space-before-function-parentheses.md index 4d3e84fb139..d083f6d4efb 100644 --- a/docs/src/rules/space-before-function-parentheses.md +++ b/docs/src/rules/space-before-function-parentheses.md @@ -41,8 +41,6 @@ Examples of **incorrect** code for this rule with the default `"always"` option: ::: incorrect ```js -/*eslint-env es6*/ - function foo() { // ... } @@ -75,8 +73,6 @@ Examples of **correct** code for this rule with the default `"always"` option: ::: correct ```js -/*eslint-env es6*/ - function foo () { // ... } @@ -109,8 +105,6 @@ Examples of **incorrect** code for this rule with the `"never"` option: ::: incorrect ```js -/*eslint-env es6*/ - function foo () { // ... } @@ -143,8 +137,6 @@ Examples of **correct** code for this rule with the `"never"` option: ::: correct ```js -/*eslint-env es6*/ - function foo() { // ... } @@ -177,8 +169,6 @@ Examples of **incorrect** code for this rule with the `{"anonymous": "always", " ::: incorrect ```js -/*eslint-env es6*/ - function foo () { // ... } @@ -207,8 +197,6 @@ Examples of **correct** code for this rule with the `{"anonymous": "always", "na ::: correct ```js -/*eslint-env es6*/ - function foo() { // ... } @@ -237,8 +225,6 @@ Examples of **incorrect** code for this rule with the `{"anonymous": "never", "n ::: incorrect ```js -/*eslint-env es6*/ - function foo() { // ... } @@ -267,8 +253,6 @@ Examples of **correct** code for this rule with the `{"anonymous": "never", "nam ::: correct ```js -/*eslint-env es6*/ - function foo () { // ... } diff --git a/docs/src/rules/space-before-keywords.md b/docs/src/rules/space-before-keywords.md index b49fe4b8c36..7d969d2c83d 100644 --- a/docs/src/rules/space-before-keywords.md +++ b/docs/src/rules/space-before-keywords.md @@ -49,7 +49,6 @@ Examples of **incorrect** code for this rule with the default `"always"` option: ```js /*eslint space-before-keywords: ["error", "always"]*/ -/*eslint-env es6*/ if (foo) { // ... @@ -72,7 +71,6 @@ Examples of **correct** code for this rule with the default `"always"` option: ```js /*eslint space-before-keywords: ["error", "always"]*/ -/*eslint-env es6*/ if (foo) { // ... diff --git a/docs/src/rules/space-in-brackets.md b/docs/src/rules/space-in-brackets.md index 05b9ed88103..15262ed21fe 100644 --- a/docs/src/rules/space-in-brackets.md +++ b/docs/src/rules/space-in-brackets.md @@ -50,8 +50,6 @@ Examples of **incorrect** code for this rule with the default `"never"` option: ::: incorrect ```js -/*eslint-env es6*/ - foo[ 'bar' ]; foo['bar' ]; @@ -122,8 +120,6 @@ Examples of **incorrect** code for this rule with the `"always"` option: ::: incorrect ```js -/*eslint-env es6*/ - foo['bar']; foo['bar' ]; foo[ 'bar']; diff --git a/docs/src/rules/space-infix-ops.md b/docs/src/rules/space-infix-ops.md index 821a6e276c0..c15bc0763af 100644 --- a/docs/src/rules/space-infix-ops.md +++ b/docs/src/rules/space-infix-ops.md @@ -45,7 +45,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint space-infix-ops: "error"*/ -/*eslint-env es6*/ a+b @@ -70,7 +69,6 @@ Examples of **correct** code for this rule: ```js /*eslint space-infix-ops: "error"*/ -/*eslint-env es6*/ a + b diff --git a/docs/src/rules/space-unary-ops.md b/docs/src/rules/space-unary-ops.md index 8379cfb7219..30b91452fdd 100644 --- a/docs/src/rules/space-unary-ops.md +++ b/docs/src/rules/space-unary-ops.md @@ -96,7 +96,6 @@ foo --; ```js /*eslint space-unary-ops: "error"*/ -/*eslint-env es6*/ function *foo() { yield(0) @@ -155,7 +154,6 @@ foo--; ```js /*eslint space-unary-ops: "error"*/ -/*eslint-env es6*/ function *foo() { yield (0) diff --git a/docs/src/rules/strict.md b/docs/src/rules/strict.md index 3fb23efe62f..a072ff2db04 100644 --- a/docs/src/rules/strict.md +++ b/docs/src/rules/strict.md @@ -173,7 +173,6 @@ function foo() { ```js /*eslint strict: ["error", "function"]*/ -/*eslint-env es6*/ // Illegal "use strict" directive in function with non-simple parameter list. // This is a syntax error since ES2016. diff --git a/docs/src/rules/symbol-description.md b/docs/src/rules/symbol-description.md index 438cb013ae9..6ccc284b928 100644 --- a/docs/src/rules/symbol-description.md +++ b/docs/src/rules/symbol-description.md @@ -38,7 +38,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint symbol-description: "error"*/ -/*eslint-env es6*/ var foo = Symbol(); ``` @@ -51,7 +50,6 @@ Examples of **correct** code for this rule: ```js /*eslint symbol-description: "error"*/ -/*eslint-env es6*/ var foo = Symbol("some description"); diff --git a/docs/src/rules/valid-jsdoc.md b/docs/src/rules/valid-jsdoc.md index 5a859fac346..7522ba6514a 100644 --- a/docs/src/rules/valid-jsdoc.md +++ b/docs/src/rules/valid-jsdoc.md @@ -94,7 +94,6 @@ Examples of **correct** code for this rule: ```js /*eslint valid-jsdoc: "error"*/ -/*eslint-env es6*/ /** * Add two numbers. @@ -192,7 +191,6 @@ Examples of additional **incorrect** code for this rule with sample `"prefer": { ```js /*eslint valid-jsdoc: ["error", { "prefer": { "arg": "param", "argument": "param", "class": "constructor", "return": "returns", "virtual": "abstract" } }]*/ -/*eslint-env es6*/ /** * Add two numbers. @@ -238,7 +236,6 @@ Examples of additional **incorrect** code for this rule with sample `"preferType ```js /*eslint valid-jsdoc: ["error", { "preferType": { "Boolean": "boolean", "Number": "number", "object": "Object", "String": "string" } }]*/ -/*eslint-env es6*/ /** * Add two numbers. diff --git a/docs/src/rules/yield-star-spacing.md b/docs/src/rules/yield-star-spacing.md index 73b8dae0836..0bbf1b885b7 100644 --- a/docs/src/rules/yield-star-spacing.md +++ b/docs/src/rules/yield-star-spacing.md @@ -48,7 +48,6 @@ Examples of **correct** code for this rule with the default `"after"` option: ```js /*eslint yield-star-spacing: ["error", "after"]*/ -/*eslint-env es6*/ function* generator() { yield* other(); @@ -65,7 +64,6 @@ Examples of **correct** code for this rule with the `"before"` option: ```js /*eslint yield-star-spacing: ["error", "before"]*/ -/*eslint-env es6*/ function *generator() { yield *other(); @@ -82,7 +80,6 @@ Examples of **correct** code for this rule with the `"both"` option: ```js /*eslint yield-star-spacing: ["error", "both"]*/ -/*eslint-env es6*/ function * generator() { yield * other(); @@ -99,7 +96,6 @@ Examples of **correct** code for this rule with the `"neither"` option: ```js /*eslint yield-star-spacing: ["error", "neither"]*/ -/*eslint-env es6*/ function*generator() { yield*other(); diff --git a/tests/fixtures/bad-examples.md b/tests/fixtures/bad-examples.md index efede633ea4..b11765a7754 100644 --- a/tests/fixtures/bad-examples.md +++ b/tests/fixtures/bad-examples.md @@ -68,3 +68,15 @@ const foo = "baz"; ``` ::: + +:::correct + +```js +/* eslint no-restricted-syntax: ["error", "ArrayPattern"] */ +/* eslint-env es6 */ + +/*eslint-env node */ +/*eslint-env*/ +``` + +::: diff --git a/tests/tools/check-rule-examples.js b/tests/tools/check-rule-examples.js index 3bd31c49cd1..442d0bedc1f 100644 --- a/tests/tools/check-rule-examples.js +++ b/tests/tools/check-rule-examples.js @@ -81,8 +81,11 @@ describe("check-rule-examples", () => { "\x1B[0m \x1B[2m51:1\x1B[22m \x1B[31merror\x1B[39m Duplicate /* eslint no-restricted-syntax */ configuration comment. Each example should contain only one. Split this example into multiple examples\x1B[0m\n" + "\x1B[0m \x1B[2m56:1\x1B[22m \x1B[31merror\x1B[39m Remove unnecessary \"ecmaVersion\":\"latest\"\x1B[0m\n" + `\x1B[0m \x1B[2m64:1\x1B[22m \x1B[31merror\x1B[39m "ecmaVersion" must be one of ${[3, 5, ...Array.from({ length: LATEST_ECMA_VERSION - 2015 + 1 }, (_, index) => index + 2015)].join(", ")}\x1B[0m\n` + + "\x1B[0m \x1B[2m76:1\x1B[22m \x1B[31merror\x1B[39m /* eslint-env */ comments are no longer supported. Remove the comment\x1B[0m\n" + + "\x1B[0m \x1B[2m78:1\x1B[22m \x1B[31merror\x1B[39m /* eslint-env */ comments are no longer supported. Remove the comment\x1B[0m\n" + + "\x1B[0m \x1B[2m79:1\x1B[22m \x1B[31merror\x1B[39m /* eslint-env */ comments are no longer supported. Remove the comment\x1B[0m\n" + "\x1B[0m\x1B[0m\n" + - "\x1B[0m\x1B[31m\x1B[1m✖ 9 problems (9 errors, 0 warnings)\x1B[22m\x1B[39m\x1B[0m\n" + + "\x1B[0m\x1B[31m\x1B[1m✖ 12 problems (12 errors, 0 warnings)\x1B[22m\x1B[39m\x1B[0m\n" + "\x1B[0m\x1B[31m\x1B[1m\x1B[22m\x1B[39m\x1B[0m\n"; assert.strictEqual(normalizedStderr, expectedStderr); diff --git a/tools/check-rule-examples.js b/tools/check-rule-examples.js index 78408130e4c..224a8c2b42b 100644 --- a/tools/check-rule-examples.js +++ b/tools/check-rule-examples.js @@ -115,6 +115,17 @@ async function findProblems(filename) { let hasRuleConfigComment = false; for (const comment of ast.comments) { + + if (comment.type === "Block" && /^\s*eslint-env(?!\S)/u.test(comment.value)) { + problems.push({ + fatal: false, + severity: 2, + message: "/* eslint-env */ comments are no longer supported. Remove the comment.", + line: codeBlockToken.map[0] + 1 + comment.loc.start.line, + column: comment.loc.start.column + 1 + }); + } + if (comment.type !== "Block" || !/^\s*eslint(?!\S)/u.test(comment.value)) { continue; } From 97ce45bcdaf2320efd59bb7974e0c8e073aab672 Mon Sep 17 00:00:00 2001 From: Pearce Ropion <32177944+Pearce-Ropion@users.noreply.github.com> Date: Tue, 2 Apr 2024 07:58:28 -0700 Subject: [PATCH 054/166] feat: Add `reportUsedIgnorePattern` option to `no-unused-vars` rule (#17662) * Add `reportUsedIgnorePattern` option to `no-unused-vars` rule Address comments Fix tests Report on all variables except for initializers Report only on declarations Match message formatting with other rule messages Move variable.eslintUsed into isUsedVariable function Refactor report messages for consistency Fix tests * Report when ignored variable use used. * Fix docs and jsdocs * Full revert jsdoc update * Refactor message data handlers * Fix english * english is hard * Fix auto prettier --- docs/src/rules/no-unused-vars.md | 43 +++++- lib/rules/no-unused-vars.js | 208 +++++++++++++++++++++++++----- tests/lib/rules/no-unused-vars.js | 88 ++++++++++++- 3 files changed, 305 insertions(+), 34 deletions(-) diff --git a/docs/src/rules/no-unused-vars.md b/docs/src/rules/no-unused-vars.md index cc361e955e4..f66487f4fdb 100644 --- a/docs/src/rules/no-unused-vars.md +++ b/docs/src/rules/no-unused-vars.md @@ -137,7 +137,13 @@ By default this rule is enabled with `all` option for caught errors and variable ```json { "rules": { - "no-unused-vars": ["error", { "vars": "all", "args": "after-used", "caughtErrors": "all", "ignoreRestSiblings": false }] + "no-unused-vars": ["error", { + "vars": "all", + "args": "after-used", + "caughtErrors": "all", + "ignoreRestSiblings": false, + "reportUsedIgnorePattern": false + }] } } ``` @@ -455,6 +461,41 @@ class Foo { ::: +### reportUsedIgnorePattern + +The `reportUsedIgnorePattern` option is a boolean (default: `false`). +Using this option will report variables that match any of the valid ignore +pattern options (`varsIgnorePattern`, `argsIgnorePattern`, `caughtErrorsIgnorePattern`, or +`destructuredArrayIgnorePattern`) if they have been used. + +Examples of **incorrect** code for the `{ "reportUsedIgnorePattern": true }` option: + +::: incorrect + +```js +/*eslint no-unused-vars: ["error", { "reportUsedIgnorePattern": true, "varsIgnorePattern": "[iI]gnored" }]*/ + +var firstVarIgnored = 1; +var secondVar = 2; +console.log(firstVarIgnored, secondVar); +``` + +::: + +Examples of **correct** code for the `{ "reportUsedIgnorePattern": true }` option: + +::: correct + +```js +/*eslint no-unused-vars: ["error", { "reportUsedIgnorePattern": true, "varsIgnorePattern": "[iI]gnored" }]*/ + +var firstVar = 1; +var secondVar = 2; +console.log(firstVar, secondVar); +``` + +::: + ## When Not To Use It If you don't want to be notified about unused variables or function arguments, you can safely turn this rule off. diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js index 90b76e6f2d1..d17253acf74 100644 --- a/lib/rules/no-unused-vars.js +++ b/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 //------------------------------------------------------------------------------ @@ -73,6 +85,9 @@ module.exports = { }, ignoreClassWithStaticInitBlock: { type: "boolean" + }, + reportUsedIgnorePattern: { + type: "boolean" } }, additionalProperties: false @@ -82,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}}." } }, @@ -96,7 +112,8 @@ module.exports = { args: "after-used", ignoreRestSiblings: false, caughtErrors: "all", - ignoreClassWithStaticInitBlock: false + ignoreClassWithStaticInitBlock: false, + reportUsedIgnorePattern: false }; const firstOption = context.options[0]; @@ -110,6 +127,7 @@ module.exports = { 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"); @@ -129,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. @@ -136,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" && defType !== "CatchClause" && config.varsIgnorePattern) { - type = "vars"; - pattern = config.varsIgnorePattern.toString(); - } + if (def) { + let pattern; + let variableDescription; + + switch (def.type) { + case "CatchClause": + if (config.caughtErrorsIgnorePattern) { + [variableDescription, pattern] = getVariableDescription("catch-clause"); + } + break; - const additional = type ? `. Allowed unused ${type} must match ${pattern}` : ""; + 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 }; } @@ -167,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 }; } @@ -532,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 => { @@ -589,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; } @@ -615,6 +734,14 @@ 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; } @@ -634,6 +761,14 @@ 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; } } else if (type === "Parameter") { @@ -650,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; } @@ -661,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; } } @@ -724,6 +875,5 @@ module.exports = { } } }; - } }; diff --git a/tests/lib/rules/no-unused-vars.js b/tests/lib/rules/no-unused-vars.js index de85050248f..b25e718f41b 100644 --- a/tests/lib/rules/no-unused-vars.js +++ b/tests/lib/rules/no-unused-vars.js @@ -88,6 +88,24 @@ function assignedError(varName, additional = "", type = "Identifier") { }; } +/** + * Returns an expected error for used-but-ignored variables. + * @param {string} varName The name of the variable + * @param {string} [additional] The additional text for the message data + * @param {string} [type] The node type (defaults to "Identifier") + * @returns {Object} An expected error object + */ +function usedIgnoredError(varName, additional = "", type = "Identifier") { + return { + messageId: "usedIgnoredVar", + data: { + varName, + additional + }, + type + }; +} + ruleTester.run("no-unused-vars", rule, { valid: [ "var foo = 5;\n\nlabel: while (true) {\n console.log(foo);\n break label;\n}", @@ -462,6 +480,22 @@ ruleTester.run("no-unused-vars", rule, { code: "class Foo { static {} }", options: [{ ignoreClassWithStaticInitBlock: false, varsIgnorePattern: "^Foo" }], languageOptions: { ecmaVersion: 2022 } + }, + + // https://github.com/eslint/eslint/issues/17568 + { + code: "const a = 5; const _c = a + 5;", + options: [{ args: "all", varsIgnorePattern: "^_", reportUsedIgnorePattern: true }], + languageOptions: { ecmaVersion: 6 } + }, + { + code: "(function foo(a, _b) { return a + 5 })(5)", + options: [{ args: "all", argsIgnorePattern: "^_", reportUsedIgnorePattern: true }] + }, + { + code: "const [ a, _b, c ] = items;\nconsole.log(a+c);", + options: [{ destructuredArrayIgnorePattern: "^_", reportUsedIgnorePattern: true }], + languageOptions: { ecmaVersion: 6 } } ], invalid: [ @@ -621,12 +655,12 @@ ruleTester.run("no-unused-vars", rule, { languageOptions: { ecmaVersion: 2020 }, errors: [ { - ...assignedError("a", ". Allowed unused elements of array destructuring patterns must match /^_/u"), + ...assignedError("a", ". Allowed unused elements of array destructuring must match /^_/u"), line: 3, column: 20 }, { - ...assignedError("c", ". Allowed unused elements of array destructuring patterns must match /^_/u"), + ...assignedError("c", ". Allowed unused elements of array destructuring must match /^_/u"), line: 3, column: 27 } @@ -644,12 +678,12 @@ ruleTester.run("no-unused-vars", rule, { languageOptions: { ecmaVersion: 2020 }, errors: [ { - ...assignedError("a", ". Allowed unused elements of array destructuring patterns must match /^_/u"), + ...assignedError("a", ". Allowed unused elements of array destructuring must match /^_/u"), line: 3, column: 20 }, { - ...assignedError("c", ". Allowed unused elements of array destructuring patterns must match /^_/u"), + ...assignedError("c", ". Allowed unused elements of array destructuring must match /^_/u"), line: 3, column: 27 }, @@ -1611,6 +1645,52 @@ c = foo1`, options: [{ ignoreClassWithStaticInitBlock: true }], languageOptions: { ecmaVersion: 2022 }, errors: [{ ...definedError("Foo"), line: 1, column: 7 }] + }, + + // https://github.com/eslint/eslint/issues/17568 + { + code: "const _a = 5;const _b = _a + 5", + options: [{ args: "all", varsIgnorePattern: "^_", reportUsedIgnorePattern: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + usedIgnoredError("_a", ". Used vars must not match /^_/u") + ] + }, + { + code: "const _a = 42; foo(() => _a);", + options: [{ args: "all", varsIgnorePattern: "^_", reportUsedIgnorePattern: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [usedIgnoredError("_a", ". Used vars must not match /^_/u")] + }, + { + code: "(function foo(_a) { return _a + 5 })(5)", + options: [{ args: "all", argsIgnorePattern: "^_", reportUsedIgnorePattern: true }], + errors: [usedIgnoredError("_a", ". Used args must not match /^_/u")] + }, + { + code: "const [ a, _b ] = items;\nconsole.log(a+_b);", + options: [{ destructuredArrayIgnorePattern: "^_", reportUsedIgnorePattern: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + usedIgnoredError("_b", ". Used elements of array destructuring must not match /^_/u") + ] + }, + { + code: "let _x;\n[_x] = arr;\nfoo(_x);", + options: [{ destructuredArrayIgnorePattern: "^_", reportUsedIgnorePattern: true, varsIgnorePattern: "[iI]gnored" }], + languageOptions: { ecmaVersion: 6 }, + errors: [usedIgnoredError("_x", ". Used elements of array destructuring must not match /^_/u")] + }, + { + code: "const [ignored] = arr;\nfoo(ignored);", + options: [{ destructuredArrayIgnorePattern: "^_", reportUsedIgnorePattern: true, varsIgnorePattern: "[iI]gnored" }], + languageOptions: { ecmaVersion: 6 }, + errors: [usedIgnoredError("ignored", ". Used vars must not match /[iI]gnored/u")] + }, + { + code: "try{}catch(_err){console.error(_err)}", + options: [{ caughtErrors: "all", caughtErrorsIgnorePattern: "^_", reportUsedIgnorePattern: true }], + errors: [usedIgnoredError("_err", ". Used args must not match /^_/u")] } ] }); From a129acba0bd2d44480b56fd96c3d5444e850ba5b Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 2 Apr 2024 13:32:00 -0700 Subject: [PATCH 055/166] fix: flat config name on ignores object (#18258) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1d9d17b879f..f5e600a1f71 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^3.0.2", "@eslint/js": "9.0.0-rc.0", - "@humanwhocodes/config-array": "^0.12.1", + "@humanwhocodes/config-array": "^0.12.2", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", From e508800658d0a71356ccc8b94a30e06140fc8858 Mon Sep 17 00:00:00 2001 From: fnx <966276+DMartens@users.noreply.github.com> Date: Tue, 2 Apr 2024 23:20:56 +0200 Subject: [PATCH 056/166] fix: rule tester ignore irrelevant test case properties (#18235) * fix: ignore irrelevant test case properties for the duplication check * fix: only ignore top-level test case properties * chore: test case merge fixes --- lib/rule-tester/rule-tester.js | 20 +++- tests/lib/rule-tester/rule-tester.js | 105 +++++++++++++++++++- tests/lib/rules/accessor-pairs.js | 8 +- tests/lib/rules/array-bracket-newline.js | 19 ---- tests/lib/rules/array-callback-return.js | 1 - tests/lib/rules/constructor-super.js | 4 - tests/lib/rules/no-object-constructor.js | 1 - tests/lib/rules/no-unused-vars.js | 24 +---- tests/lib/rules/no-useless-backreference.js | 4 +- 9 files changed, 130 insertions(+), 56 deletions(-) diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index 261a1bb73bf..f965fbbe472 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -136,6 +136,15 @@ 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", @@ -848,7 +857,7 @@ class RuleTester { /** * Check if this test case is a duplicate of one we have seen before. - * @param {Object} item test case object + * @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 @@ -863,7 +872,14 @@ class RuleTester { return; } - const serializedTestCase = stringify(item); + 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), diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js index 28860af6f7f..4cb34b96dfe 100644 --- a/tests/lib/rule-tester/rule-tester.js +++ b/tests/lib/rule-tester/rule-tester.js @@ -3148,7 +3148,7 @@ describe("RuleTester", () => { } }, { valid: ["foo", "foo"], - invalid: [{ code: "foo", errors: [{ message: "foo bar" }] }] + invalid: [] }); }, "detected duplicate test case"); }); @@ -3162,10 +3162,69 @@ describe("RuleTester", () => { } }, { valid: [{ code: "foo" }, { code: "foo" }], - invalid: [{ code: "foo", errors: [{ message: "foo bar" }] }] + invalid: [] + }); + }, "detected duplicate test case"); + }); + + it("throws with string and object test cases", () => { + assert.throws(() => { + ruleTester.run("foo", { + meta: {}, + create() { + return {}; + } + }, { + valid: ["foo", { code: "foo" }], + invalid: [] + }); + }, "detected duplicate test case"); + }); + + it("ignores the name property", () => { + assert.throws(() => { + ruleTester.run("foo", { + meta: {}, + create() { + return {}; + } + }, { + valid: [{ code: "foo" }, { name: "bar", code: "foo" }], + invalid: [] }); }, "detected duplicate test case"); }); + + it("does not ignore top level test case properties nested in other test case properties", () => { + ruleTester.run("foo", { + meta: { schema: [{ type: "object" }] }, + create() { + return {}; + } + }, { + valid: [{ options: [{ name: "foo" }], name: "foo", code: "same" }, { options: [{ name: "bar" }], name: "bar", code: "same" }], + invalid: [] + }); + }); + + it("does not throw an error for defining the same test case in different run calls", () => { + const rule = { + meta: {}, + create() { + return {}; + } + }; + + ruleTester.run("foo", rule, { + valid: ["foo"], + invalid: [] + }); + + ruleTester.run("foo", rule, { + valid: ["foo"], + invalid: [] + }); + }); }); describe("invalid test cases", () => { @@ -3328,6 +3387,48 @@ describe("RuleTester", () => { ] }); }); + + it("detects duplicate test cases even if the error matchers differ", () => { + assert.throws(() => { + ruleTester.run("foo", { + meta: { schema: false }, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + } + }; + } + }, { + valid: [], + invalid: [ + { code: "const x = 123;", errors: [{ message: "foo bar" }] }, + { code: "const x = 123;", errors: 1 } + ] + }); + }, "detected duplicate test case"); + }); + + it("detects duplicate test cases even if the presence of the output property differs", () => { + assert.throws(() => { + ruleTester.run("foo", { + meta: { schema: false }, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + } + }; + } + }, { + valid: [], + invalid: [ + { code: "const x = 123;", errors: 1 }, + { code: "const x = 123;", errors: 1, output: null } + ] + }); + }, "detected duplicate test case"); + }); }); }); diff --git a/tests/lib/rules/accessor-pairs.js b/tests/lib/rules/accessor-pairs.js index c183bbacdd7..91a902d87f6 100644 --- a/tests/lib/rules/accessor-pairs.js +++ b/tests/lib/rules/accessor-pairs.js @@ -1047,10 +1047,10 @@ ruleTester.run("accessor-pairs", rule, { // Full location tests { - code: "var o = { get a() {} };", + code: "var o = { get b() {} };", options: [{ setWithoutGet: true, getWithoutSet: true }], errors: [{ - message: "Setter is not present for getter 'a'.", + message: "Setter is not present for getter 'b'.", type: "Property", line: 1, column: 11, @@ -1831,11 +1831,11 @@ ruleTester.run("accessor-pairs", rule, { }] }, { - code: "class A { static get a() {} };", + code: "class A { static get b() {} };", options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], languageOptions: { ecmaVersion: 6 }, errors: [{ - message: "Setter is not present for class static getter 'a'.", + message: "Setter is not present for class static getter 'b'.", type: "MethodDefinition", line: 1, column: 11, diff --git a/tests/lib/rules/array-bracket-newline.js b/tests/lib/rules/array-bracket-newline.js index b4d8fd9198c..9b6a9e1ce9f 100644 --- a/tests/lib/rules/array-bracket-newline.js +++ b/tests/lib/rules/array-bracket-newline.js @@ -761,25 +761,6 @@ ruleTester.run("array-bracket-newline", rule, { } ] }, - { - code: "var foo = [\n1\n];", - output: "var foo = [1];", - options: ["never"], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, { code: "var foo = [ /* any comment */\n1, 2\n];", output: "var foo = [ /* any comment */\n1, 2];", diff --git a/tests/lib/rules/array-callback-return.js b/tests/lib/rules/array-callback-return.js index 938f73f7a2d..9bb9334391e 100644 --- a/tests/lib/rules/array-callback-return.js +++ b/tests/lib/rules/array-callback-return.js @@ -273,7 +273,6 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.forEach(function(x) { if (a == b) {return x;}})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] }, { code: "foo.forEach(function(x) { if (a == b) {return undefined;}})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] }, { code: "foo.forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "foo.forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: ["Array.prototype.forEach() expects no useless return value from function 'bar'."] }, { code: "foo.bar().forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] }, { code: "[\"foo\",\"bar\"].forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] }, { code: "foo.forEach((x) => { return x;})", options: checkForEachOptions, languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, diff --git a/tests/lib/rules/constructor-super.js b/tests/lib/rules/constructor-super.js index 9268a13fb7d..ed61640e81f 100644 --- a/tests/lib/rules/constructor-super.js +++ b/tests/lib/rules/constructor-super.js @@ -225,10 +225,6 @@ ruleTester.run("constructor-super", rule, { }, // nested execution scope. - { - code: "class A extends B { constructor() { class C extends D { constructor() { super(); } } } }", - errors: [{ messageId: "missingAll", type: "MethodDefinition" }] - }, { code: "class A extends B { constructor() { var c = class extends D { constructor() { super(); } } } }", errors: [{ messageId: "missingAll", type: "MethodDefinition" }] diff --git a/tests/lib/rules/no-object-constructor.js b/tests/lib/rules/no-object-constructor.js index 5b94a12ffea..c865f3e146c 100644 --- a/tests/lib/rules/no-object-constructor.js +++ b/tests/lib/rules/no-object-constructor.js @@ -193,7 +193,6 @@ ruleTester.run("no-object-constructor", rule, { ...[ // No semicolon required before `({})` because ASI does not occur - { code: "Object()" }, { code: ` {} diff --git a/tests/lib/rules/no-unused-vars.js b/tests/lib/rules/no-unused-vars.js index b25e718f41b..2f2d4f2125c 100644 --- a/tests/lib/rules/no-unused-vars.js +++ b/tests/lib/rules/no-unused-vars.js @@ -1512,21 +1512,16 @@ ruleTester.run("no-unused-vars", rule, { // https://github.com/eslint/eslint/issues/10982 { code: "var a = function() { a(); };", - errors: [assignedError("a")] + errors: [{ ...assignedError("a"), line: 1, column: 5 }] }, { code: "var a = function(){ return function() { a(); } };", - errors: [assignedError("a")] - }, - { - code: "const a = () => { a(); };", - languageOptions: { ecmaVersion: 2015 }, - errors: [assignedError("a")] + errors: [{ ...assignedError("a"), line: 1, column: 5 }] }, { code: "const a = () => () => { a(); };", languageOptions: { ecmaVersion: 2015 }, - errors: [assignedError("a")] + errors: [{ ...assignedError("a"), line: 1, column: 7 }] }, { code: `let myArray = [1,2,3,4].filter((x) => x == 0); @@ -1539,24 +1534,11 @@ ruleTester.run("no-unused-vars", rule, { languageOptions: { ecmaVersion: 2015 }, errors: [{ ...assignedError("a"), line: 1, column: 14 }] }, - { - code: "var a = function() { a(); };", - errors: [{ ...assignedError("a"), line: 1, column: 5 }] - }, - { - code: "var a = function(){ return function() { a(); } };", - errors: [{ ...assignedError("a"), line: 1, column: 5 }] - }, { code: "const a = () => { a(); };", languageOptions: { ecmaVersion: 2015 }, errors: [{ ...assignedError("a"), line: 1, column: 7 }] }, - { - code: "const a = () => () => { a(); };", - languageOptions: { ecmaVersion: 2015 }, - errors: [{ ...assignedError("a"), line: 1, column: 7 }] - }, // https://github.com/eslint/eslint/issues/14324 { diff --git a/tests/lib/rules/no-useless-backreference.js b/tests/lib/rules/no-useless-backreference.js index 666ec0a1742..c868184d06a 100644 --- a/tests/lib/rules/no-useless-backreference.js +++ b/tests/lib/rules/no-useless-backreference.js @@ -162,9 +162,9 @@ ruleTester.run("no-useless-backreference", rule, { }] }, { - code: String.raw`/\k(?a)/`, + code: String.raw`/\k(?bar)/`, errors: [{ - message: String.raw`Backreference '\k' will be ignored. It references group '(?a)' which appears later in the pattern.`, + message: String.raw`Backreference '\k' will be ignored. It references group '(?bar)' which appears later in the pattern.`, type: "Literal" }] }, From 96607d0581845fab19f832cd435547f9da960733 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Thu, 4 Apr 2024 21:16:56 +0200 Subject: [PATCH 057/166] docs: version selectors synchronization (#18260) * support vNN.x branches * fetch versions * update `versions.json` during releases * trigger rebuilds on Netlify --- .github/workflows/rebuild-docs-sites.yml | 17 +++++ Makefile.js | 74 +++++++++++++++++- docs/.eleventy.js | 2 + docs/src/_data/config.json | 4 +- docs/src/_data/eslintNextVersion.js | 32 -------- docs/src/_data/eslintVersion.js | 35 --------- docs/src/_data/eslintVersions.js | 75 +++++++++++++++++++ docs/src/_data/versions.json | 15 ++++ .../components/nav-version-switcher.html | 12 +-- .../components/version-switcher.html | 12 +-- .../src/_includes/partials/versions-list.html | 9 +-- 11 files changed, 188 insertions(+), 99 deletions(-) create mode 100644 .github/workflows/rebuild-docs-sites.yml delete mode 100644 docs/src/_data/eslintNextVersion.js delete mode 100644 docs/src/_data/eslintVersion.js create mode 100644 docs/src/_data/eslintVersions.js diff --git a/.github/workflows/rebuild-docs-sites.yml b/.github/workflows/rebuild-docs-sites.yml new file mode 100644 index 00000000000..47ef47b6dd4 --- /dev/null +++ b/.github/workflows/rebuild-docs-sites.yml @@ -0,0 +1,17 @@ +name: Rebuild Docs Sites + +on: + push: + branches: [main] + paths: + - 'docs/src/_data/versions.json' + +jobs: + rebuild: + name: 'Trigger rebuild on Netlify' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: > + jq -r '.items | map(.branch) | join(",")' docs/src/_data/versions.json + | xargs -I{LIST} curl -X POST -d {} "${{ secrets.NETLIFY_DOCS_BUILD_HOOK }}?trigger_branch={{LIST}}" diff --git a/Makefile.js b/Makefile.js index 120be8e3534..6d37023f79a 100644 --- a/Makefile.js +++ b/Makefile.js @@ -272,6 +272,73 @@ function publishSite() { cd(currentDir); } +/** + * Determines whether the given version is a prerelease. + * @param {string} version The version to check. + * @returns {boolean} `true` if it is a prerelease, `false` otherwise. + */ +function isPreRelease(version) { + return /[a-z]/u.test(version); +} + +/** + * Updates docs/src/_data/versions.json + * @param {string} oldVersion Current version. + * @param {string} newVersion New version to be released. + * @returns {void} + */ +function updateVersions(oldVersion, newVersion) { + echo("Updating ESLint versions list in docs package"); + + const filePath = path.join(__dirname, "docs", "src", "_data", "versions.json"); + const data = require(filePath); + const { items } = data; + + const isOldVersionPrerelease = isPreRelease(oldVersion); + const isNewVersionPrerelease = isPreRelease(newVersion); + + if (isOldVersionPrerelease) { + if (isNewVersionPrerelease) { + + // prerelease -> prerelease. Just update the version. + items.find(item => item.branch === "next").version = newVersion; + } else { + + // prerelease -> release. First, update the item for the previous latest version + const latestVersionItem = items.find(item => item.branch === "latest"); + const latestVersion = latestVersionItem.version; + const versionBranch = `v${latestVersion.slice(0, latestVersion.indexOf("."))}.x`; // "v8.x", "v9.x", "v10.x" ... + + latestVersionItem.branch = versionBranch; + latestVersionItem.path = `/docs/${versionBranch}/`; + + // Then, replace the item for the prerelease with a new item for the new latest version + items.splice(items.findIndex(item => item.branch === "next"), 1, { + version: newVersion, + branch: "latest", + path: "/docs/latest/" + }); + + } + } else { + if (isNewVersionPrerelease) { + + // release -> prerelease. Insert an item for the prerelease. + items.splice(1, 0, { + version: newVersion, + branch: "next", + path: "/docs/next/" + }); + } else { + + // release -> release. Just update the version. + items.find(item => item.branch === "latest").version = newVersion; + } + } + + fs.writeFileSync(filePath, `${JSON.stringify(data, null, 4)}\n`); +} + /** * Updates the changelog, bumps the version number in package.json, creates a local git commit and tag, * and generates the site in an adjacent `website` folder. @@ -280,6 +347,8 @@ function publishSite() { * @returns {void} */ function generateRelease(prereleaseId) { + const oldVersion = require("./package.json").version; + ReleaseOps.generateRelease(prereleaseId); const releaseInfo = JSON.parse(cat(".eslint-release-info.json")); @@ -295,6 +364,8 @@ function generateRelease(prereleaseId) { docsPackage.version = releaseInfo.version; fs.writeFileSync(docsPackagePath, `${JSON.stringify(docsPackage, null, 4)}\n`); + updateVersions(oldVersion, releaseInfo.version); + echo("Updating commit with docs data"); exec("git add docs/ && git commit --amend --no-edit"); exec(`git tag -a -f v${releaseInfo.version} -m ${releaseInfo.version}`); @@ -308,13 +379,12 @@ function generateRelease(prereleaseId) { function publishRelease() { ReleaseOps.publishRelease(); const releaseInfo = JSON.parse(cat(".eslint-release-info.json")); - const isPreRelease = /[a-z]/u.test(releaseInfo.version); /* * for a pre-release, push to the "next" branch to trigger docs deploy * for a release, push to the "latest" branch to trigger docs deploy */ - if (isPreRelease) { + if (isPreRelease(releaseInfo.version)) { exec("git push origin HEAD:next -f"); } else { exec("git push origin HEAD:latest -f"); diff --git a/docs/.eleventy.js b/docs/.eleventy.js index 29d94d78dfb..629f98bb585 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -44,6 +44,8 @@ module.exports = function(eleventyConfig) { pathPrefix = "/docs/latest/"; } else if (process.env.BRANCH === "next") { pathPrefix = "/docs/next/"; + } else if (process.env.BRANCH && /^v\d+\.x$/u.test(process.env.BRANCH)) { + pathPrefix = `/docs/${process.env.BRANCH}/`; // `/docs/v8.x/`, `/docs/v9.x/`, `/docs/v10.x/` ... } //------------------------------------------------------------------------------ diff --git a/docs/src/_data/config.json b/docs/src/_data/config.json index 8ca381bbead..5b081b7c830 100644 --- a/docs/src/_data/config.json +++ b/docs/src/_data/config.json @@ -1,5 +1,3 @@ { - "lang": "en", - "version": "8.57.0", - "showNextVersion": true + "lang": "en" } diff --git a/docs/src/_data/eslintNextVersion.js b/docs/src/_data/eslintNextVersion.js deleted file mode 100644 index d3742364c54..00000000000 --- a/docs/src/_data/eslintNextVersion.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @fileoverview - * @author Nicholas C. Zakas - */ - -//----------------------------------------------------------------------------- -// Requirements -//----------------------------------------------------------------------------- - -const eleventyFetch = require("@11ty/eleventy-fetch"); - -//----------------------------------------------------------------------------- -// Exports -//----------------------------------------------------------------------------- - -module.exports = async function() { - - // if we're on the next branch, we can just read the package.json file - if (process.env.BRANCH === "next") { - return require("../../package.json").version; - } - - // otherwise, we need to fetch the latest version from the GitHub API - const url = "https://raw.githubusercontent.com/eslint/eslint/next/docs/package.json"; - - const response = await eleventyFetch(url, { - duration: "1d", - type: "json" - }); - - return response.version; -} diff --git a/docs/src/_data/eslintVersion.js b/docs/src/_data/eslintVersion.js deleted file mode 100644 index 4cca24f35f7..00000000000 --- a/docs/src/_data/eslintVersion.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @fileoverview Data file for package information - * @author Nicholas C. Zakas - */ - -//----------------------------------------------------------------------------- -// Requirements -//----------------------------------------------------------------------------- - -const fs = require("fs"); -const path = require("path"); - -//----------------------------------------------------------------------------- -// Initialization -//----------------------------------------------------------------------------- - -const pkgPath = path.resolve(__dirname, "../../package.json"); -const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); -const config = JSON.parse(fs.readFileSync(path.resolve(__dirname, "./config.json"), "utf8")); -const { ESLINT_VERSION } = process.env; - -//----------------------------------------------------------------------------- -// Exports -//----------------------------------------------------------------------------- - -/* - * Because we want to differentiate between the development branch and the - * most recent release, we need a way to override the version. The - * ESLINT_VERSION environment variable allows us to set this to override - * the value displayed on the website. The most common case is we will set - * this equal to "HEAD" for the version that is currently in development on - * GitHub. Otherwise, we will use the version from package.json. - */ - -module.exports = ESLINT_VERSION ?? config.version ?? pkg.version; diff --git a/docs/src/_data/eslintVersions.js b/docs/src/_data/eslintVersions.js new file mode 100644 index 00000000000..728269464d9 --- /dev/null +++ b/docs/src/_data/eslintVersions.js @@ -0,0 +1,75 @@ +/** + * @fileoverview Data for version selectors + * @author Milos Djermanovic + */ + +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + +const eleventyFetch = require("@11ty/eleventy-fetch"); + +//----------------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------------- + +module.exports = async function() { + + const thisBranch = process.env.BRANCH; + const thisVersion = require("../../package.json").version; + + // Fetch the current list of ESLint versions from the `main` branch on GitHub + const url = "https://raw.githubusercontent.com/eslint/eslint/main/docs/src/_data/versions.json"; + + const data = await eleventyFetch(url, { + duration: "1d", // Cache for local development. Netlify does not keep this cache and will therefore always fetch from GitHub. + type: "json" + }); + + const { items } = data; + + // For initial commit purpose only + if (items.length === 0) { + const localData = require("./versions.json"); + items.push(...localData.items); + } + + let foundItemForThisBranch = false; + + for (const item of items) { + const isItemForThisBranch = item.branch === thisBranch; + + foundItemForThisBranch ||= isItemForThisBranch; + + const isNumberVersion = /^\d/.test(item.version); // `false` for HEAD + + if (isNumberVersion) { + + // Make sure the version is correct + if (isItemForThisBranch) { + item.version = thisVersion; + } + + item.display = `v${item.version}`; + } else { + item.display = item.version; + } + + if (isItemForThisBranch) { + item.selected = true; + } + } + + // Add an empty item if this is not a production branch + if (!foundItemForThisBranch) { + items.unshift({ + version: "", + branch: "", + display: "", + path: "", + selected: true + }) + } + + return data; +} diff --git a/docs/src/_data/versions.json b/docs/src/_data/versions.json index 6e585d6ad5a..c5642ae9ad7 100644 --- a/docs/src/_data/versions.json +++ b/docs/src/_data/versions.json @@ -1,4 +1,19 @@ { "items": [ + { + "version": "HEAD", + "branch": "main", + "path": "/docs/head/" + }, + { + "version": "9.0.0-rc.0", + "branch": "next", + "path": "/docs/next/" + }, + { + "version": "8.57.0", + "branch": "latest", + "path": "/docs/latest/" + } ] } diff --git a/docs/src/_includes/components/nav-version-switcher.html b/docs/src/_includes/components/nav-version-switcher.html index b070e90a954..63704681f54 100644 --- a/docs/src/_includes/components/nav-version-switcher.html +++ b/docs/src/_includes/components/nav-version-switcher.html @@ -12,16 +12,8 @@ Version diff --git a/docs/src/_includes/components/version-switcher.html b/docs/src/_includes/components/version-switcher.html index 96418804bb0..8dfc70f03e6 100644 --- a/docs/src/_includes/components/version-switcher.html +++ b/docs/src/_includes/components/version-switcher.html @@ -12,16 +12,8 @@ Version diff --git a/docs/src/_includes/partials/versions-list.html b/docs/src/_includes/partials/versions-list.html index 2b859eb9c77..b9d16fb63fe 100644 --- a/docs/src/_includes/partials/versions-list.html +++ b/docs/src/_includes/partials/versions-list.html @@ -1,10 +1,5 @@ From e80b60c342f59db998afefd856b31159a527886a Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Thu, 4 Apr 2024 23:25:06 +0200 Subject: [PATCH 058/166] chore: remove code for testing version selectors (#18266) --- docs/src/_data/eslintVersions.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/src/_data/eslintVersions.js b/docs/src/_data/eslintVersions.js index 728269464d9..2fc2b981828 100644 --- a/docs/src/_data/eslintVersions.js +++ b/docs/src/_data/eslintVersions.js @@ -28,12 +28,6 @@ module.exports = async function() { const { items } = data; - // For initial commit purpose only - if (items.length === 0) { - const localData = require("./versions.json"); - items.push(...localData.items); - } - let foundItemForThisBranch = false; for (const item of items) { From 1765c24df2f48ab1c1565177b8c6dbef63acf977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Thu, 4 Apr 2024 22:25:42 +0100 Subject: [PATCH 059/166] docs: add Troubleshooting page (#18181) * docs: add Troubleshooting page * Add a mention of updating to the latest * Separated into sections * Tweaked plugin uniquely * Tweaked plugin uniquely (2) * Apply suggestions from code review Co-authored-by: Nicholas C. Zakas * no quotes * Fixed up docs links post-merge * One last link fix: configuration-file(-new) * Update docs/src/use/troubleshooting/index.md Co-authored-by: Nicholas C. Zakas --------- Co-authored-by: Nicholas C. Zakas --- .../couldnt-determine-the-plugin-uniquely.md | 54 +++++++++++++++ .../couldnt-find-the-config.md | 62 ++++++++++++++++++ .../couldnt-find-the-plugin.md | 65 +++++++++++++++++++ docs/src/use/troubleshooting/index.md | 18 +++++ messages/plugin-conflict.js | 2 +- messages/plugin-invalid.js | 2 +- messages/plugin-missing.js | 2 +- 7 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 docs/src/use/troubleshooting/couldnt-determine-the-plugin-uniquely.md create mode 100644 docs/src/use/troubleshooting/couldnt-find-the-config.md create mode 100644 docs/src/use/troubleshooting/couldnt-find-the-plugin.md create mode 100644 docs/src/use/troubleshooting/index.md diff --git a/docs/src/use/troubleshooting/couldnt-determine-the-plugin-uniquely.md b/docs/src/use/troubleshooting/couldnt-determine-the-plugin-uniquely.md new file mode 100644 index 00000000000..5065cc2416b --- /dev/null +++ b/docs/src/use/troubleshooting/couldnt-determine-the-plugin-uniquely.md @@ -0,0 +1,54 @@ +--- +title: ESLint couldn't determine the plugin … uniquely +eleventyNavigation: + key: couldn't determine plugin uniquely + parent: troubleshooting + title: ESLint couldn't determine the plugin … uniquely +--- + +## Symptoms + +When using the [legacy ESLint config system](../configure/configuration-files-deprecated), you may see this error running ESLint after installing dependencies: + +```plaintext +ESLint couldn't determine the plugin "${pluginId}" uniquely. + +- ${filePath} (loaded in "${importerName}") +- ${filePath} (loaded in "${importerName}") +... + +Please remove the "plugins" setting from either config or remove either plugin installation. +``` + +## Cause + +ESLint configuration files allow loading in plugins that may include other plugins. +A plugin package might be specified as a dependency of both your package and one or more ESLint plugins. +Legacy ESLint configuration files may use `extends` to include other configurations. +Those configurations may depend on plugins to provide certain functionality in the configuration. + +For example, if your config depends on `eslint-plugin-a@2` and `eslint-plugin-b@3`, and you extend `eslint-config-b` that depends on `eslint-plugin-a@1`, then the `eslint-plugin-a` package might have two different versions on disk: + +* `node_modules/eslint-plugin-a` +* `node_modules/eslint-plugin-b/node_modules/eslint-plugin-a` + +If the legacy ESLint configuration system sees that both plugins exists in multiple places with different versions, it won't know which one to use. + +Note that this issue is only present in the legacy eslintrc configurations. +The new ["flat" config system](../configure/configuration-files) has you `import` the dependencies yourself, removing the need for ESLint to attempt to determine their version uniquely. + +## Resolution + +Common resolutions for this issue include: + +* Upgrading all versions of all packages to their latest version +* Running `npm dedupe` or the equivalent package manager command to deduplicate packages, if their version ranges are compatible +* Using `overrides` or the equivalent package manager `package.json` field, to force a specific version of a plugin package + * Note that this may cause bugs in linting if the plugin package had breaking changes between versions + +## Resources + +For more information, see: + +* [Configure Plugins](../configure/plugins) for documentation on how to extend from plugins +* [Create Plugins](../../extend/plugins#configs-in-plugins) for documentation on how to define plugins diff --git a/docs/src/use/troubleshooting/couldnt-find-the-config.md b/docs/src/use/troubleshooting/couldnt-find-the-config.md new file mode 100644 index 00000000000..6208e5087ca --- /dev/null +++ b/docs/src/use/troubleshooting/couldnt-find-the-config.md @@ -0,0 +1,62 @@ +--- +title: ESLint couldn't find the config … to extend from +eleventyNavigation: + key: couldn't find the config to extend from + parent: troubleshooting + title: ESLint couldn't find the config … to extend from +--- + +## Symptoms + +When using the [legacy ESLint config system](../configure/configuration-files-deprecated), you may see this error running ESLint after installing dependencies: + +```plaintext +ESLint couldn't find the config "${configName}" to extend from. Please check that the name of the config is correct. + +The config "${configName}" was referenced from the config file in "${importerName}". +``` + +## Cause + +ESLint configuration files specify shareable configs by their package name in the `extends` array. +That package name is passed to the Node.js `require()`, which looks up the package under local `node_modules/` directories. +For example, the following ESLint config will first try to load a module located at `node_modules/eslint-config-yours`: + +```js +module.exports = { + extends: ["eslint-config-yours"], +}; +``` + +The error is output when you attempt to extend from a configuration and the package for that configuration is not found in any searched `node_modules/`. + +Common reasons for this occurring include: + +* Not running `npm install` or the equivalent package manager command +* Mistyping the case-sensitive name of the package and/or configuration + +### Config Name Variations + +Note that ESLint supports several config name formats: + +* The `eslint-config-` config name prefix may be omitted for brevity, e.g. `extends: ["yours"]` + * [`@` npm scoped packages](https://docs.npmjs.com/cli/v10/using-npm/scope) put the `eslint-config-` prefix after the org scope, e.g. `extends: ["@org/yours"]` to load from `@org/eslint-config-yours` +* A `plugin:` prefix indicates a config is loaded from a shared plugin, e.g. `extends: [plugin:yours/recommended]` to load from `eslint-plugin-yours` + +## Resolution + +Common resolutions for this issue include: + +* Upgrading all versions of all packages to their latest version +* Adding the config as a `devDependency` in your `package.json` +* Running `npm install` or the equivalent package manager command +* Checking that the name in your config file matches the name of the config package + +## Resources + +For more information, see: + +* [Legacy ESLint configuration files](../configure/configuration-files-deprecated#using-a-shareable-configuration-package) for documentation on the legacy ESLint configuration format + * [Legacy ESLint configuration files > Using a shareable configuration package](../configure/configuration-files-deprecated#using-a-shareable-configuration-package) for documentation on using shareable configurations +* [Share Configurations](../../extend/shareable-configs) for documentation on how to define standalone shared configs +* [Create Plugins > Configs in Plugins](../../extend/plugins#configs-in-plugins) for documentation on how to define shared configs in plugins diff --git a/docs/src/use/troubleshooting/couldnt-find-the-plugin.md b/docs/src/use/troubleshooting/couldnt-find-the-plugin.md new file mode 100644 index 00000000000..bc2241e8fa6 --- /dev/null +++ b/docs/src/use/troubleshooting/couldnt-find-the-plugin.md @@ -0,0 +1,65 @@ +--- +title: ESLint couldn't find the plugin … +eleventyNavigation: + key: couldn't find the plugin + parent: troubleshooting + title: ESLint couldn't find the plugin … +--- + +## Symptoms + +When using the [legacy ESLint config system](../configure/configuration-files-deprecated), you may see this error running ESLint after installing dependencies: + +```plaintext +ESLint couldn't find the plugin "${pluginName}". + +(The package "${pluginName}" was not found when loaded as a Node module from the directory "${resolvePluginsRelativeTo}".) + +It's likely that the plugin isn't installed correctly. Try reinstalling by running the following: + + npm install ${pluginName}@latest --save-dev + +The plugin "${pluginName}" was referenced from the config file in "${importerName}". +``` + +## Cause + +[Legacy ESLint configuration files](../configure/configuration-files-deprecated) specify shareable configs by their package name. +That package name is passed to the Node.js `require()`, which looks up the package under local `node_modules/` directories. +For example, the following ESLint config will first try to load a module located at `node_modules/eslint-plugin-yours`: + +```js +module.exports = { + extends: ["plugin:eslint-plugin-yours/config-name"], +}; +``` + +If the package is not found in any searched `node_modules/`, ESLint will print the aforementioned error. + +Common reasons for this occurring include: + +* Not running `npm install` or the equivalent package manager command +* Mistyping the case-sensitive name of the plugin + +### Plugin Name Variations + +Note that the `eslint-plugin-` plugin name prefix may be omitted for brevity, e.g. `extends: ["yours"]`. + +[`@` npm scoped packages](https://docs.npmjs.com/cli/v10/using-npm/scope) put the `eslint-plugin-` prefix after the org scope, e.g. `extends: ["@org/yours"]` to load from `@org/eslint-plugin-yours`. + +## Resolution + +Common resolutions for this issue include: + +* Upgrading all versions of all packages to their latest version +* Adding the plugin as a `devDependency` in your `package.json` +* Running `npm install` or the equivalent package manager command +* Checking that the name in your config file matches the name of the plugin package + +## Resources + +For more information, see: + +* [Legacy ESLint configuration files](../configure/configuration-files-deprecated#using-a-shareable-configuration-package) for documentation on the legacy ESLint configuration format +* [Configure Plugins](../configure/plugins) for documentation on how to extend from plugins +* [Create Plugins](../../extend/plugins#configs-in-plugins) for documentation on how to define plugins diff --git a/docs/src/use/troubleshooting/index.md b/docs/src/use/troubleshooting/index.md new file mode 100644 index 00000000000..25cbbc87f3a --- /dev/null +++ b/docs/src/use/troubleshooting/index.md @@ -0,0 +1,18 @@ +--- +title: Troubleshooting +eleventyNavigation: + key: troubleshooting + title: Troubleshooting + parent: use eslint + order: 8 +--- + +This page serves as a reference for common issues working with ESLint. + +* [`ESLint couldn't determine the plugin … uniquely`](./couldnt-determine-the-plugin-uniquely) +* [`ESLint couldn't find the config … to extend from`](./couldnt-find-the-config) +* [`ESLint couldn't find the plugin …`](./couldnt-find-the-plugin) + +Issues oftentimes can be resolved by updating the to latest versions of the `eslint` package and any related packages, such as for ESLint shareable configs and plugins. + +If you still can't figure out the problem, please stop by to chat with the team. diff --git a/messages/plugin-conflict.js b/messages/plugin-conflict.js index c8c060e2f05..4113a538fc9 100644 --- a/messages/plugin-conflict.js +++ b/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/messages/plugin-invalid.js b/messages/plugin-invalid.js index 8b471d4a336..4c60e41d319 100644 --- a/messages/plugin-invalid.js +++ b/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/messages/plugin-missing.js b/messages/plugin-missing.js index 0b7d34e3aa5..366ec4500e5 100644 --- a/messages/plugin-missing.js +++ b/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(); }; From 94178ad5cf4cfa1c8664dd8ac878790e72c90d8c Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 4 Apr 2024 23:26:58 +0200 Subject: [PATCH 060/166] docs: mention about `name` field in flat config (#18252) * docs: mention about `name` field in flat config * Update docs/src/use/configure/configuration-files.md Co-authored-by: Nicholas C. Zakas * Update docs/src/use/configure/configuration-files.md Co-authored-by: Nicholas C. Zakas * Update docs/src/use/configure/configuration-files.md * chore: update examples * Update docs/src/use/configure/configuration-files.md Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * Update configuration-files.md Co-authored-by: Nicholas C. Zakas * Update docs/src/use/configure/configuration-files.md Co-authored-by: Milos Djermanovic * docs: update convention about uniqueness * docs: wording * Apply suggestions from code review Co-authored-by: Nicholas C. Zakas --------- Co-authored-by: Nicholas C. Zakas Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Co-authored-by: Milos Djermanovic --- docs/src/use/configure/configuration-files.md | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/docs/src/use/configure/configuration-files.md b/docs/src/use/configure/configuration-files.md index 5c116f13bcf..2d46f9e208a 100644 --- a/docs/src/use/configure/configuration-files.md +++ b/docs/src/use/configure/configuration-files.md @@ -56,6 +56,7 @@ module.exports = [ Each configuration object contains all of the information ESLint needs to execute on a set of files. Each configuration object is made up of these properties: +* `name` - A name for the configuration object. This is used in error messages and config inspector to help identify which configuration object is being used. ([Naming Convention](#configuration-naming-conventions)) * `files` - An array of glob patterns indicating the files that the configuration object should apply to. If not specified, the configuration object applies to all files matched by any other configuration object. * `ignores` - An array of glob patterns indicating the files that the configuration object should not apply to. If not specified, the configuration object applies to all files matched by `files`. * `languageOptions` - An object containing settings related to how JavaScript is configured for linting. @@ -342,6 +343,57 @@ Here, the `js.configs.recommended` predefined configuration is applied first and For more information on how to combine predefined configs with your preferences, please see [Combine Configs](combine-configs). +### Configuration Naming Conventions + +The `name` property is optional, but it is recommended to provide a name for each configuration object, especially when you are creating shared configurations. The name is used in error messages and the config inspector to help identify which configuration object is being used. + +The name should be descriptive of the configuration object's purpose and scoped with the configuration name or plugin name using `/` as a separator. ESLint does not enforce the names to be unique at runtime, but it is recommended that unique names be set to avoid confusion. + +For example, if you are creating a configuration object for a plugin named `eslint-plugin-example`, you might add `name` to the configuration objects with the `example/` prefix: + +```js +export default { + configs: { + recommended: { + name: "example/recommended", + rules: { + "no-unused-vars": "warn" + } + }, + strict: { + name: "example/strict", + rules: { + "no-unused-vars": "error" + } + } + } +}; +``` + +When exposing arrays of configuration objects, the `name` may have extra scoping levels to help identify the configuration object. For example: + +```js +export default { + configs: { + strict: [ + { + name: "example/strict/language-setup", + languageOptions: { + ecmaVersion: 2024 + } + }, + { + name: "example/strict/sub-config", + file: ["src/**/*.js"], + rules: { + "no-unused-vars": "error" + } + } + ] + } +} +``` + ## Using a Shareable Configuration Package A sharable configuration is an npm package that exports a configuration object or array. This package should be installed as a dependency in your project and then referenced from inside of your `eslint.config.js` file. For example, to use a shareable configuration named `eslint-config-example`, your configuration file would look like this: From 44a81c6151c58a3f4c1f6bb2927b0996f81c2daa Mon Sep 17 00:00:00 2001 From: Lars Kappert Date: Fri, 5 Apr 2024 09:24:39 +0200 Subject: [PATCH 061/166] chore: upgrade knip (#18272) * chore: upgrade knip * chore: refactor default vs named exports/imports --- lib/source-code/token-store/backward-token-cursor.js | 6 +++--- lib/source-code/token-store/cursors.js | 6 ++++-- lib/source-code/token-store/forward-token-comment-cursor.js | 6 +++--- lib/source-code/token-store/forward-token-cursor.js | 6 +++--- package.json | 2 +- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/source-code/token-store/backward-token-cursor.js b/lib/source-code/token-store/backward-token-cursor.js index 454a2449701..d3469c99b14 100644 --- a/lib/source-code/token-store/backward-token-cursor.js +++ b/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/lib/source-code/token-store/cursors.js b/lib/source-code/token-store/cursors.js index 30c72b69b8f..f2676f13da6 100644 --- a/lib/source-code/token-store/cursors.js +++ b/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/lib/source-code/token-store/forward-token-comment-cursor.js b/lib/source-code/token-store/forward-token-comment-cursor.js index 50c7a394f38..8aa46c27b74 100644 --- a/lib/source-code/token-store/forward-token-comment-cursor.js +++ b/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/lib/source-code/token-store/forward-token-cursor.js b/lib/source-code/token-store/forward-token-cursor.js index e8c18609621..9305cbef683 100644 --- a/lib/source-code/token-store/forward-token-cursor.js +++ b/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/package.json b/package.json index f5e600a1f71..a0c03a35a8f 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "got": "^11.8.3", "gray-matter": "^4.0.3", "js-yaml": "^4.1.0", - "knip": "^5.0.1", + "knip": "^5.8.0", "lint-staged": "^11.0.0", "load-perf": "^0.2.0", "markdown-it": "^12.2.0", From 610c1486dc54a095667822113eb08062a1aad2b7 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger <53019676+kirkwaiblinger@users.noreply.github.com> Date: Fri, 5 Apr 2024 01:45:45 -0600 Subject: [PATCH 062/166] fix: Support `using` declarations in no-lone-blocks (#18269) fix: [no-lone-blocks] Support using declarations This commit adds support for explicit resource management keywords inside a block for the `no-lone-blocks` rule. Fixes #18204 --- lib/rules/no-lone-blocks.js | 2 +- .../no-lone-blocks/await-using.js | 330 ++++++++++++++++++ .../no-lone-blocks/using.js | 312 +++++++++++++++++ tests/lib/rules/no-lone-blocks.js | 37 +- 4 files changed, 678 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/parsers/typescript-parsers/no-lone-blocks/await-using.js create mode 100644 tests/fixtures/parsers/typescript-parsers/no-lone-blocks/using.js diff --git a/lib/rules/no-lone-blocks.js b/lib/rules/no-lone-blocks.js index 51f30f351f1..2e27089269b 100644 --- a/lib/rules/no-lone-blocks.js +++ b/lib/rules/no-lone-blocks.js @@ -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/tests/fixtures/parsers/typescript-parsers/no-lone-blocks/await-using.js b/tests/fixtures/parsers/typescript-parsers/no-lone-blocks/await-using.js new file mode 100644 index 00000000000..74f68c51697 --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/no-lone-blocks/await-using.js @@ -0,0 +1,330 @@ +"use strict"; + +// source code: +` +{ + await using x = makeDisposable(); +}` +// obtained from https://typescript-eslint.io/play/#ts=5.4.3&showAST=es&fileType=.tsx&code=FAb2AJwQwdyhLALuArgZ3gOwObgB7gC84AtlANYCmAIvGgA4D2aUARgDaUAUAlANzAAvkA&eslintrc=N4KABGBEBOCuA2BTAzpAXGYBfEWg&tsconfig=N4KABGBEDGD2C2AHAlgGwKYCcDyiAuysAdgM6QBcYoEEkJemy0eAcgK6qoDCAFutAGsylBm3TgwAXxCSgA&tokens=false + +exports.parse = () => ({ + "type": "Program", + "body": [ + { + "type": "BlockStatement", + "body": [ + { + "type": "VariableDeclaration", + "declarations": [ + { + "type": "VariableDeclarator", + "definite": false, + "id": { + "type": "Identifier", + "decorators": [], + "name": "x", + "optional": false, + "range": [ + 17, + 18 + ], + "loc": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 15 + } + } + }, + "init": { + "type": "CallExpression", + "callee": { + "type": "Identifier", + "decorators": [], + "name": "makeDisposable", + "optional": false, + "range": [ + 21, + 35 + ], + "loc": { + "start": { + "line": 3, + "column": 18 + }, + "end": { + "line": 3, + "column": 32 + } + } + }, + "arguments": [], + "optional": false, + "range": [ + 21, + 37 + ], + "loc": { + "start": { + "line": 3, + "column": 18 + }, + "end": { + "line": 3, + "column": 34 + } + } + }, + "range": [ + 17, + 37 + ], + "loc": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 34 + } + } + } + ], + "declare": false, + "kind": "await using", + "range": [ + 5, + 38 + ], + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 35 + } + } + } + ], + "range": [ + 1, + 40 + ], + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 4, + "column": 1 + } + } + } + ], + "comments": [], + "range": [ + 1, + 40 + ], + "sourceType": "script", + "tokens": [ + { + "type": "Punctuator", + "value": "{", + "range": [ + 1, + 2 + ], + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 1 + } + } + }, + { + "type": "Identifier", + "value": "await", + "range": [ + 5, + 10 + ], + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 7 + } + } + }, + { + "type": "Identifier", + "value": "using", + "range": [ + 11, + 16 + ], + "loc": { + "start": { + "line": 3, + "column": 8 + }, + "end": { + "line": 3, + "column": 13 + } + } + }, + { + "type": "Identifier", + "value": "x", + "range": [ + 17, + 18 + ], + "loc": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 15 + } + } + }, + { + "type": "Punctuator", + "value": "=", + "range": [ + 19, + 20 + ], + "loc": { + "start": { + "line": 3, + "column": 16 + }, + "end": { + "line": 3, + "column": 17 + } + } + }, + { + "type": "Identifier", + "value": "makeDisposable", + "range": [ + 21, + 35 + ], + "loc": { + "start": { + "line": 3, + "column": 18 + }, + "end": { + "line": 3, + "column": 32 + } + } + }, + { + "type": "Punctuator", + "value": "(", + "range": [ + 35, + 36 + ], + "loc": { + "start": { + "line": 3, + "column": 32 + }, + "end": { + "line": 3, + "column": 33 + } + } + }, + { + "type": "Punctuator", + "value": ")", + "range": [ + 36, + 37 + ], + "loc": { + "start": { + "line": 3, + "column": 33 + }, + "end": { + "line": 3, + "column": 34 + } + } + }, + { + "type": "Punctuator", + "value": ";", + "range": [ + 37, + 38 + ], + "loc": { + "start": { + "line": 3, + "column": 34 + }, + "end": { + "line": 3, + "column": 35 + } + } + }, + { + "type": "Punctuator", + "value": "}", + "range": [ + 39, + 40 + ], + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 1 + } + } + } + ], + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 4, + "column": 1 + } + }, + "parent": null +}) diff --git a/tests/fixtures/parsers/typescript-parsers/no-lone-blocks/using.js b/tests/fixtures/parsers/typescript-parsers/no-lone-blocks/using.js new file mode 100644 index 00000000000..18d1a86e568 --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/no-lone-blocks/using.js @@ -0,0 +1,312 @@ +"use strict"; + +// source code: +` +{ + using x = makeDisposable(); +}` +// obtained from https://typescript-eslint.io/play/#ts=5.4.3&showAST=es&fileType=.tsx&code=FAb2AJwVwZwSwHYHNwA9wF5wFsCGBrAUwBE4YAHAexlwCMAbQgCgEoBuYAXyA&eslintrc=N4KABGBEBOCuA2BTAzpAXGYBfEWg&tsconfig=N4KABGBEDGD2C2AHAlgGwKYCcDyiAuysAdgM6QBcYoEEkJemy0eAcgK6qoDCAFutAGsylBm3TgwAXxCSgA&tokens=false + +exports.parse = () => ({ + "type": "Program", + "body": [ + { + "type": "BlockStatement", + "body": [ + { + "type": "VariableDeclaration", + "declarations": [ + { + "type": "VariableDeclarator", + "definite": false, + "id": { + "type": "Identifier", + "decorators": [], + "name": "x", + "optional": false, + "range": [ + 11, + 12 + ], + "loc": { + "start": { + "line": 3, + "column": 8 + }, + "end": { + "line": 3, + "column": 9 + } + } + }, + "init": { + "type": "CallExpression", + "callee": { + "type": "Identifier", + "decorators": [], + "name": "makeDisposable", + "optional": false, + "range": [ + 15, + 29 + ], + "loc": { + "start": { + "line": 3, + "column": 12 + }, + "end": { + "line": 3, + "column": 26 + } + } + }, + "arguments": [], + "optional": false, + "range": [ + 15, + 31 + ], + "loc": { + "start": { + "line": 3, + "column": 12 + }, + "end": { + "line": 3, + "column": 28 + } + } + }, + "range": [ + 11, + 31 + ], + "loc": { + "start": { + "line": 3, + "column": 8 + }, + "end": { + "line": 3, + "column": 28 + } + } + } + ], + "declare": false, + "kind": "using", + "range": [ + 5, + 32 + ], + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 29 + } + } + } + ], + "range": [ + 1, + 34 + ], + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 4, + "column": 1 + } + } + } + ], + "comments": [], + "range": [ + 1, + 35 + ], + "sourceType": "script", + "tokens": [ + { + "type": "Punctuator", + "value": "{", + "range": [ + 1, + 2 + ], + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 1 + } + } + }, + { + "type": "Identifier", + "value": "using", + "range": [ + 5, + 10 + ], + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 7 + } + } + }, + { + "type": "Identifier", + "value": "x", + "range": [ + 11, + 12 + ], + "loc": { + "start": { + "line": 3, + "column": 8 + }, + "end": { + "line": 3, + "column": 9 + } + } + }, + { + "type": "Punctuator", + "value": "=", + "range": [ + 13, + 14 + ], + "loc": { + "start": { + "line": 3, + "column": 10 + }, + "end": { + "line": 3, + "column": 11 + } + } + }, + { + "type": "Identifier", + "value": "makeDisposable", + "range": [ + 15, + 29 + ], + "loc": { + "start": { + "line": 3, + "column": 12 + }, + "end": { + "line": 3, + "column": 26 + } + } + }, + { + "type": "Punctuator", + "value": "(", + "range": [ + 29, + 30 + ], + "loc": { + "start": { + "line": 3, + "column": 26 + }, + "end": { + "line": 3, + "column": 27 + } + } + }, + { + "type": "Punctuator", + "value": ")", + "range": [ + 30, + 31 + ], + "loc": { + "start": { + "line": 3, + "column": 27 + }, + "end": { + "line": 3, + "column": 28 + } + } + }, + { + "type": "Punctuator", + "value": ";", + "range": [ + 31, + 32 + ], + "loc": { + "start": { + "line": 3, + "column": 28 + }, + "end": { + "line": 3, + "column": 29 + } + } + }, + { + "type": "Punctuator", + "value": "}", + "range": [ + 33, + 34 + ], + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 1 + } + } + } + ], + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 5, + "column": 0 + } + }, + "parent": null +}) diff --git a/tests/lib/rules/no-lone-blocks.js b/tests/lib/rules/no-lone-blocks.js index 8fb2f82ec55..104ea64f868 100644 --- a/tests/lib/rules/no-lone-blocks.js +++ b/tests/lib/rules/no-lone-blocks.js @@ -10,7 +10,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/no-lone-blocks"), - RuleTester = require("../../../lib/rule-tester/rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"), + parser = require("../../fixtures/fixture-parser"); //------------------------------------------------------------------------------ // Tests @@ -71,7 +72,39 @@ ruleTester.run("no-lone-blocks", rule, { { code: "class C { static { { let block; } something; } }", languageOptions: { ecmaVersion: 2022 } }, { code: "class C { static { something; { const block = 1; } } }", languageOptions: { ecmaVersion: 2022 } }, { code: "class C { static { { function block(){} } something; } }", languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { something; { class block {} } } }", languageOptions: { ecmaVersion: 2022 } } + { code: "class C { static { something; { class block {} } } }", languageOptions: { ecmaVersion: 2022 } }, + + /* + * Test case to support `using` declarations in advance of explicit resource management + * reaching stage 4. + * See https://github.com/eslint/eslint/issues/18204 + */ + { + code: ` +{ + using x = makeDisposable(); +}`, + languageOptions: { + parser: require(parser("typescript-parsers/no-lone-blocks/using")), + ecmaVersion: 2022 + } + }, + + /* + * Test case to support `await using` declarations in advance of explicit resource management + * reaching stage 4. + * See https://github.com/eslint/eslint/issues/18204 + */ + { + code: ` +{ + await using x = makeDisposable(); +}`, + languageOptions: { + parser: require(parser("typescript-parsers/no-lone-blocks/await-using")), + ecmaVersion: 2022 + } + } ], invalid: [ { From e151050e64b57f156c32f6d0d1f20dce08b5a610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Fri, 5 Apr 2024 17:39:17 +0800 Subject: [PATCH 063/166] docs: update get-started to the new `@eslint/create-config` (#18217) * docs: update get-started to the new `@eslint/create-config` * Update README.md * Update getting-started.md --- README.md | 14 +++++--------- docs/src/use/getting-started.md | 18 ++---------------- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 79b7796292e..6c6463aa5b1 100644 --- a/README.md +++ b/README.md @@ -59,15 +59,11 @@ After that, you can run ESLint on any file or directory like this: ## Configuration -After running `npm init @eslint/config`, you'll have an `.eslintrc` file in your directory. In it, you'll see some rules configured like this: - -```json -{ - "rules": { - "semi": ["error", "always"], - "quotes": ["error", "double"] - } -} +After running `npm init @eslint/config`, you'll have an `eslint.config.js` or `eslint.config.mjs` file in your directory. In it, you'll see some rules configured like this: + +```js +import pluginJs from "@eslint/js"; +export default [ pluginJs.configs.recommended, ]; ``` The names `"semi"` and `"quotes"` are the names of [rules](https://eslint.org/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values: diff --git a/docs/src/use/getting-started.md b/docs/src/use/getting-started.md index 3480c64dab0..a1da8053c00 100644 --- a/docs/src/use/getting-started.md +++ b/docs/src/use/getting-started.md @@ -27,25 +27,11 @@ npm init @eslint/config If you want to use a specific shareable config that is hosted on npm, you can use the `--config` option and specify the package name: ```shell -# use `eslint-config-semistandard` shared config +# use `eslint-config-standard` shared config # npm 7+ -npm init @eslint/config -- --config semistandard +npm init @eslint/config -- --config eslint-config-standard -# or (`eslint-config` prefix is optional) -npm init @eslint/config -- --config eslint-config-semistandard - -# ⚠️ npm 6.x no extra double-dash: -npm init @eslint/config --config semistandard - -``` - -The `--config` flag also supports passing in arrays: - -```shell -npm init @eslint/config -- --config semistandard,standard -# or -npm init @eslint/config -- --config semistandard --config standard ``` **Note:** `npm init @eslint/config` assumes you have a `package.json` file already. If you don't, make sure to run `npm init` or `yarn init` beforehand. From d54a41200483b7dd90531841a48a1f3a91f172fe Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 5 Apr 2024 13:21:22 -0700 Subject: [PATCH 064/166] feat: Add --inspect-config CLI flag (#18270) * feat: Add --inspect-config CLI flag fixes #18255 * Update docs to indicate only flat config mode is supported * Update tests/lib/options.js Co-authored-by: Francesco Trotta --------- Co-authored-by: Francesco Trotta --- bin/eslint.js | 12 ++++++ docs/src/use/command-line-interface.md | 41 ++++++++++++------- docs/src/use/configure/configuration-files.md | 4 ++ lib/options.js | 11 +++++ tests/lib/options.js | 8 ++++ 5 files changed, 61 insertions(+), 15 deletions(-) diff --git a/bin/eslint.js b/bin/eslint.js index 46ca98738b2..b6190f66742 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -148,6 +148,18 @@ ${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 cli = require("../lib/cli"); const exitCode = await cli.execute( diff --git a/docs/src/use/command-line-interface.md b/docs/src/use/command-line-interface.md index d4ec452611a..2e52e47283e 100644 --- a/docs/src/use/command-line-interface.md +++ b/docs/src/use/command-line-interface.md @@ -67,27 +67,24 @@ You can view all the CLI options by running `npx eslint -h`. eslint [options] file.js [file.js] [dir] Basic configuration: - --no-eslintrc Disable use of configuration from .eslintrc.* - -c, --config path::String Use this configuration, overriding .eslintrc.* config options if present - --env [String] Specify environments - --ext [String] Specify JavaScript file extensions + --no-config-lookup Disable look up for eslint.config.js + -c, --config path::String Use this configuration instead of eslint.config.js, eslint.config.mjs, or + eslint.config.cjs + --inspect-config Open the config inspector with the current configuration --global [String] Define global variables --parser String Specify the parser to be used --parser-options Object Specify parser options - --resolve-plugins-relative-to path::String A folder where plugins should be resolved from, CWD by default -Specify rules and plugins: +Specify Rules and Plugins: --plugin [String] Specify plugins --rule Object Specify rules - --rulesdir [path::String] Load additional rules from this directory. Deprecated: Use rules from plugins -Fix problems: +Fix Problems: --fix Automatically fix problems --fix-dry-run Automatically fix problems without saving the changes to the file system --fix-type Array Specify the types of fixes to apply (directive, problem, suggestion, layout) -Ignore files: - --ignore-path path::String Specify path of ignore file +Ignore Files: --no-ignore Disable use of ignore files and patterns --ignore-pattern [String] Pattern of files to ignore (in addition to those in .eslintignore) @@ -95,8 +92,8 @@ Use stdin: --stdin Lint code provided on - default: false --stdin-filename String Specify filename to process STDIN as -Handle warnings: - --quiet Report and check errors only - default: false +Handle Warnings: + --quiet Report errors only - default: false --max-warnings Int Number of warnings to trigger nonzero exit code - default: -1 Output: @@ -107,20 +104,22 @@ Output: Inline configuration comments: --no-inline-config Prevent comments from changing config or rules --report-unused-disable-directives Adds reported errors for unused eslint-disable and eslint-enable directives - --report-unused-disable-directives-severity String Chooses severity level for reporting unused eslint-disable and eslint-enable directives - either: off, warn, error, 0, 1, or 2 + --report-unused-disable-directives-severity String Chooses severity level for reporting unused eslint-disable and + eslint-enable directives - either: off, warn, error, 0, 1, or 2 Caching: --cache Only check changed files - default: false --cache-file path::String Path to the cache file. Deprecated: use --cache-location - default: .eslintcache --cache-location path::String Path to the cache file or directory - --cache-strategy String Strategy to use for detecting changed files in the cache - either: metadata or content - default: metadata + --cache-strategy String Strategy to use for detecting changed files in the cache - either: metadata or + content - default: metadata Miscellaneous: --init Run config initialization wizard - default: false --env-info Output execution environment information - default: false --no-error-on-unmatched-pattern Prevent errors when pattern is unmatched --exit-on-fatal-error Exit with exit code 2 in case of fatal error - default: false - --no-warn-ignored Suppress warnings when the file list includes ignored files. *Flat Config Mode Only* + --no-warn-ignored Suppress warnings when the file list includes ignored files --pass-on-no-patterns Exit with exit code 0 in case no file patterns are passed --debug Output debugging information -h, --help Show help @@ -160,6 +159,18 @@ This example uses the configuration file at `~/my-eslint.json`. If `.eslintrc.*` and/or `package.json` files are also used for configuration (i.e., `--no-eslintrc` was not specified), the configurations are merged. Options from this configuration file have precedence over the options from `.eslintrc.*` and `package.json` files. +#### `--inspect-config` + +**Flat Config Mode Only.** This option runs `npx @eslint/config-inspector` to start the config inspector. You can use the config inspector to better understand what your configuration is doing and which files it applies to. When you use this flag, the CLI does not perform linting. + +* **Argument Type**: No argument. + +##### `--inspect-config` example + +```shell +npx eslint --inspect-config +``` + #### `--env` **eslintrc Mode Only.** This option enables specific environments. diff --git a/docs/src/use/configure/configuration-files.md b/docs/src/use/configure/configuration-files.md index 2d46f9e208a..1710937a3cd 100644 --- a/docs/src/use/configure/configuration-files.md +++ b/docs/src/use/configure/configuration-files.md @@ -157,6 +157,10 @@ export default [ This configuration object applies to all files except those ending with `.config.js`. Effectively, this is like having `files` set to `**/*`. In general, it's a good idea to always include `files` if you are specifying `ignores`. +::: tip +Use the [config inspector](https://github.com/eslint/config-inspector) (`--inspect-config` in the CLI) to test which config objects apply to a specific file. +::: + #### Globally ignoring files with `ignores` If `ignores` is used without any other keys in the configuration object, then the patterns act as global ignores. Here's an example: diff --git a/lib/options.js b/lib/options.js index 135e3f2d760..c8c3f8e4f70 100644 --- a/lib/options.js +++ b/lib/options.js @@ -104,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) { @@ -185,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, { diff --git a/tests/lib/options.js b/tests/lib/options.js index a75381f7241..3e6aed60e13 100644 --- a/tests/lib/options.js +++ b/tests/lib/options.js @@ -445,4 +445,12 @@ describe("options", () => { }); }); + describe("--inspect-config", () => { + it("should return true when --inspect-config is passed", () => { + const currentOptions = flatOptions.parse("--inspect-config"); + + assert.isTrue(currentOptions.inspectConfig); + }); + }); + }); From d73a33caddc34ab1eb62039f0f661a338836147c Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 5 Apr 2024 22:22:37 +0200 Subject: [PATCH 065/166] chore: ignore `/docs/v8.x` in link checker (#18274) --- docs/tools/validate-links.js | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/tools/validate-links.js b/docs/tools/validate-links.js index b1e7036c730..091dd2fe640 100644 --- a/docs/tools/validate-links.js +++ b/docs/tools/validate-links.js @@ -19,6 +19,7 @@ const skipPatterns = [ "/donate", "/docs/latest", "/docs/next", + "/docs/v8.x", 'src="null"' ]; From 7c957f295dcd97286016cfb3c121dbae72f26a91 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 5 Apr 2024 20:31:48 +0000 Subject: [PATCH 066/166] chore: package.json update for @eslint/js release --- packages/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/package.json b/packages/js/package.json index 8fd90b5961c..88772efbe84 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "9.0.0-rc.0", + "version": "9.0.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 19f9a8926bd7888ab4a813ae323ad3c332fd5d5c Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 5 Apr 2024 13:47:11 -0700 Subject: [PATCH 067/166] chore: Update dependencies for v9.0.0 (#18275) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a0c03a35a8f..e2c38c02da1 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^3.0.2", - "@eslint/js": "9.0.0-rc.0", - "@humanwhocodes/config-array": "^0.12.2", + "@eslint/js": "9.0.0", + "@humanwhocodes/config-array": "^0.12.3", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", From 75cb5f40478e8d158c5034e362a07c53d3a9ac1e Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 5 Apr 2024 20:52:06 +0000 Subject: [PATCH 068/166] Build: changelog update for 9.0.0 --- CHANGELOG.md | 230 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a9484074cf..d0a8e666538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,233 @@ +v9.0.0 - April 5, 2024 + +* [`19f9a89`](https://github.com/eslint/eslint/commit/19f9a8926bd7888ab4a813ae323ad3c332fd5d5c) chore: Update dependencies for v9.0.0 (#18275) (Nicholas C. Zakas) +* [`7c957f2`](https://github.com/eslint/eslint/commit/7c957f295dcd97286016cfb3c121dbae72f26a91) chore: package.json update for @eslint/js release (Jenkins) +* [`d73a33c`](https://github.com/eslint/eslint/commit/d73a33caddc34ab1eb62039f0f661a338836147c) chore: ignore `/docs/v8.x` in link checker (#18274) (Milos Djermanovic) +* [`d54a412`](https://github.com/eslint/eslint/commit/d54a41200483b7dd90531841a48a1f3a91f172fe) feat: Add --inspect-config CLI flag (#18270) (Nicholas C. Zakas) +* [`e151050`](https://github.com/eslint/eslint/commit/e151050e64b57f156c32f6d0d1f20dce08b5a610) docs: update get-started to the new `@eslint/create-config` (#18217) (唯然) +* [`610c148`](https://github.com/eslint/eslint/commit/610c1486dc54a095667822113eb08062a1aad2b7) fix: Support `using` declarations in no-lone-blocks (#18269) (Kirk Waiblinger) +* [`44a81c6`](https://github.com/eslint/eslint/commit/44a81c6151c58a3f4c1f6bb2927b0996f81c2daa) chore: upgrade knip (#18272) (Lars Kappert) +* [`94178ad`](https://github.com/eslint/eslint/commit/94178ad5cf4cfa1c8664dd8ac878790e72c90d8c) docs: mention about `name` field in flat config (#18252) (Anthony Fu) +* [`1765c24`](https://github.com/eslint/eslint/commit/1765c24df2f48ab1c1565177b8c6dbef63acf977) docs: add Troubleshooting page (#18181) (Josh Goldberg ✨) +* [`e80b60c`](https://github.com/eslint/eslint/commit/e80b60c342f59db998afefd856b31159a527886a) chore: remove code for testing version selectors (#18266) (Milos Djermanovic) +* [`96607d0`](https://github.com/eslint/eslint/commit/96607d0581845fab19f832cd435547f9da960733) docs: version selectors synchronization (#18260) (Milos Djermanovic) +* [`e508800`](https://github.com/eslint/eslint/commit/e508800658d0a71356ccc8b94a30e06140fc8858) fix: rule tester ignore irrelevant test case properties (#18235) (fnx) +* [`a129acb`](https://github.com/eslint/eslint/commit/a129acba0bd2d44480b56fd96c3d5444e850ba5b) fix: flat config name on ignores object (#18258) (Nicholas C. Zakas) +* [`97ce45b`](https://github.com/eslint/eslint/commit/97ce45bcdaf2320efd59bb7974e0c8e073aab672) feat: Add `reportUsedIgnorePattern` option to `no-unused-vars` rule (#17662) (Pearce Ropion) +* [`651ec91`](https://github.com/eslint/eslint/commit/651ec9122d0bd8dd08082098bd1e1a24892983f2) docs: remove `/* eslint-env */` comments from rule examples (#18249) (Milos Djermanovic) +* [`950c4f1`](https://github.com/eslint/eslint/commit/950c4f11c6797de56a5b056affd0c74211840957) docs: Update README (GitHub Actions Bot) +* [`3e9fcea`](https://github.com/eslint/eslint/commit/3e9fcea3808af83bda1e610aa2d33fb92135b5de) feat: Show config names in error messages (#18256) (Nicholas C. Zakas) +* [`b7cf3bd`](https://github.com/eslint/eslint/commit/b7cf3bd29f25a0bab4102a51029bf47c50f406b5) fix!: correct `camelcase` rule schema for `allow` option (#18232) (eMerzh) +* [`12f5746`](https://github.com/eslint/eslint/commit/12f574628f2adbe1bfed07aafecf5152b5fc3f4d) docs: add info about dot files and dir in flat config (#18239) (Tanuj Kanti) +* [`b93f408`](https://github.com/eslint/eslint/commit/b93f4085c105117a1081b249bd50c0831127fab3) docs: update shared settings example (#18251) (Tanuj Kanti) +* [`26384d3`](https://github.com/eslint/eslint/commit/26384d3367e11bd4909a3330b72741742897fa1f) docs: fix `ecmaVersion` in one example, add checks (#18241) (Milos Djermanovic) +* [`7747097`](https://github.com/eslint/eslint/commit/77470973a0c2cae8ce07a456f2ad95896bc8d1d3) docs: Update PR review process (#18233) (Nicholas C. Zakas) +* [`b07d427`](https://github.com/eslint/eslint/commit/b07d427826f81c2bdb683d04879093c687479edf) docs: fix typo (#18246) (Kirill Gavrilov) +* [`a98babc`](https://github.com/eslint/eslint/commit/a98babcda227649b2299d10e3f887241099406f7) chore: add npm script to run WebdriverIO test (#18238) (Francesco Trotta) +* [`9b7bd3b`](https://github.com/eslint/eslint/commit/9b7bd3be066ac1f72fa35c4d31a1b178c7e2b683) chore: update dependency markdownlint to ^0.34.0 (#18237) (renovate[bot]) +* [`778082d`](https://github.com/eslint/eslint/commit/778082d4fa5e2fc97549c9e5acaecc488ef928f5) docs: add Glossary page (#18187) (Josh Goldberg ✨) +* [`dadc5bf`](https://github.com/eslint/eslint/commit/dadc5bf843a7181b9724a261c7ac0486091207aa) fix: `constructor-super` false positives with loops (#18226) (Milos Djermanovic) +* [`de40874`](https://github.com/eslint/eslint/commit/de408743b5c3fc25ebd7ef5fb11ab49ab4d06c36) feat: Rule Performance Statistics for flat ESLint (#17850) (Mara Kiefer) +* [`d85c436`](https://github.com/eslint/eslint/commit/d85c436353d566d261798c51dadb8ed50def1a7d) feat: use-isnan report NaN in `indexOf` and `lastIndexOf` with fromIndex (#18225) (Tanuj Kanti) +* [`b185eb9`](https://github.com/eslint/eslint/commit/b185eb97ec60319cc39023e8615959dd598919ae) 9.0.0-rc.0 (Jenkins) +* [`26010c2`](https://github.com/eslint/eslint/commit/26010c209d2657cd401bf2550ba4f276cb318f7d) Build: changelog update for 9.0.0-rc.0 (Jenkins) +* [`297416d`](https://github.com/eslint/eslint/commit/297416d2b41f5880554d052328aa36cd79ceb051) chore: package.json update for eslint-9.0.0-rc.0 (#18223) (Francesco Trotta) +* [`d363c51`](https://github.com/eslint/eslint/commit/d363c51b177e085b011c7fde1c5a5a09b3db9cdb) chore: package.json update for @eslint/js release (Jenkins) +* [`239a7e2`](https://github.com/eslint/eslint/commit/239a7e27209a6b861d634b3ef245ebbb805793a3) docs: Clarify the description of `sort-imports` options (#18198) (gyeongwoo park) +* [`09bd7fe`](https://github.com/eslint/eslint/commit/09bd7fe09ad255a263286e90accafbe2bf04ccfc) feat!: move AST traversal into SourceCode (#18167) (Nicholas C. Zakas) +* [`b91f9dc`](https://github.com/eslint/eslint/commit/b91f9dc072f17f5ea79803deb86cf002d031b4cf) build: fix TypeError in prism-eslint-hooks.js (#18209) (Francesco Trotta) +* [`4769c86`](https://github.com/eslint/eslint/commit/4769c86cc16e0b54294c0a394a1ec7ed88fc334f) docs: fix incorrect example in `no-lone-blocks` (#18215) (Tanuj Kanti) +* [`1b841bb`](https://github.com/eslint/eslint/commit/1b841bb04ac642c5ee84d1e44be3e53317579526) chore: fix some comments (#18213) (avoidaway) +* [`b8fb572`](https://github.com/eslint/eslint/commit/b8fb57256103b908712302ccd508f464eff1c9dc) feat: add `reportUnusedFallthroughComment` option to no-fallthrough rule (#18188) (Kirk Waiblinger) +* [`ae8103d`](https://github.com/eslint/eslint/commit/ae8103de69c12c6e71644a1de9589644e6767d15) fix: load plugins in the CLI in flat config mode (#18185) (Francesco Trotta) +* [`5251327`](https://github.com/eslint/eslint/commit/5251327711a2d7083e3c629cb8e48d9d1e809add) docs: Update README (GitHub Actions Bot) +* [`29c3595`](https://github.com/eslint/eslint/commit/29c359599c2ddd168084a2c8cbca626c51d0dc13) chore: remove repetitive words (#18193) (cuithon) +* [`1dc8618`](https://github.com/eslint/eslint/commit/1dc861897e8b47280e878d609c13c9e41892f427) docs: Update README (GitHub Actions Bot) +* [`acc2e06`](https://github.com/eslint/eslint/commit/acc2e06edd55eaab58530d891c0a572c1f0ec453) chore: Introduce Knip (#18005) (Lars Kappert) +* [`ba89c73`](https://github.com/eslint/eslint/commit/ba89c73261f7fd1b6cdd50cfaeb8f4ce36101757) 9.0.0-beta.2 (Jenkins) +* [`d7ec0d1`](https://github.com/eslint/eslint/commit/d7ec0d1fbdbafa139d090ffd8b42d33bd4aa46f8) Build: changelog update for 9.0.0-beta.2 (Jenkins) +* [`7509276`](https://github.com/eslint/eslint/commit/75092764db117252067558bd3fbbf0c66ac081b7) chore: upgrade @eslint/js@9.0.0-beta.2 (#18180) (Milos Djermanovic) +* [`96087b3`](https://github.com/eslint/eslint/commit/96087b33dc10311bba83e22cc968919c358a0188) chore: package.json update for @eslint/js release (Jenkins) +* [`ba1c1bb`](https://github.com/eslint/eslint/commit/ba1c1bbc6ba9d57a83d04f450566337d3c3b0448) docs: Update README (GitHub Actions Bot) +* [`337cdf9`](https://github.com/eslint/eslint/commit/337cdf9f7ad939df7bc55c23d953e12d847b6ecc) docs: Explain limitations of RuleTester fix testing (#18175) (Nicholas C. Zakas) +* [`c7abd89`](https://github.com/eslint/eslint/commit/c7abd8936193a87be274174c47d6775e6220e354) docs: Explain Node.js version support (#18176) (Nicholas C. Zakas) +* [`925afa2`](https://github.com/eslint/eslint/commit/925afa2b0c882f77f6b4411bdca3cb8ad6934b56) chore: Remove some uses of `lodash.merge` (#18179) (Milos Djermanovic) +* [`1c173dc`](https://github.com/eslint/eslint/commit/1c173dc1f3d36a28cb2543e93675c2fbdb6fa9f1) feat: add `ignoreClassWithStaticInitBlock` option to `no-unused-vars` (#18170) (Tanuj Kanti) +* [`d961eeb`](https://github.com/eslint/eslint/commit/d961eeb855b6dd9118a78165e358e454eb1d090d) docs: show red underlines in examples in rules docs (#18041) (Yosuke Ota) +* [`558274a`](https://github.com/eslint/eslint/commit/558274abbd25ef269f4994cf258b2e44afbad548) docs: Update README (GitHub Actions Bot) +* [`2908b9b`](https://github.com/eslint/eslint/commit/2908b9b96ab7a25fe8044a1755030b18186a75b0) docs: Update release documentation (#18174) (Nicholas C. Zakas) +* [`a451b32`](https://github.com/eslint/eslint/commit/a451b32b33535a57b4b7e24291f30760f65460ba) feat: make `no-misleading-character-class` report more granular errors (#18082) (Francesco Trotta) +* [`972ef15`](https://github.com/eslint/eslint/commit/972ef155a94ad2cc85db7d209ad869869222c14c) chore: remove invalid type in @eslint/js (#18164) (Nitin Kumar) +* [`1f1260e`](https://github.com/eslint/eslint/commit/1f1260e863f53e2a5891163485a67c55d41993aa) docs: replace HackerOne link with GitHub advisory (#18165) (Francesco Trotta) +* [`79a95eb`](https://github.com/eslint/eslint/commit/79a95eb7da7fe657b6448c225d4f8ac31117456a) feat!: disallow multiple configuration comments for same rule (#18157) (Milos Djermanovic) +* [`e37153f`](https://github.com/eslint/eslint/commit/e37153f71f173e8667273d6298bef81e0d33f9ba) fix: improve error message for invalid rule config (#18147) (Nitin Kumar) +* [`c49ed63`](https://github.com/eslint/eslint/commit/c49ed63265fc8e0cccea404810a4c5075d396a15) feat: update complexity rule for optional chaining & default values (#18152) (Mathias Schreck) +* [`e5ef3cd`](https://github.com/eslint/eslint/commit/e5ef3cd6953bb40108556e0465653898ffed8420) docs: add inline cases condition in `no-fallthrough` (#18158) (Tanuj Kanti) +* [`af6e170`](https://github.com/eslint/eslint/commit/af6e17081fa6c343474959712e7a4a20f8b304e2) fix: stop linting files after an error (#18155) (Francesco Trotta) +* [`450d0f0`](https://github.com/eslint/eslint/commit/450d0f044023843b1790bd497dfca45dcbdb41e4) docs: fix `ignore` option docs (#18154) (Francesco Trotta) +* [`11144a2`](https://github.com/eslint/eslint/commit/11144a2671b2404b293f656be111221557f3390f) feat: `no-restricted-imports` option added `allowImportNames` (#16196) (M Pater) +* [`491a1d1`](https://github.com/eslint/eslint/commit/491a1d16a8dbcbe2f0cc82ce7bef580229d09b86) 9.0.0-beta.1 (Jenkins) +* [`fd9c0a9`](https://github.com/eslint/eslint/commit/fd9c0a9f0e50da617fe1f2e60ba3df0276a7f06b) Build: changelog update for 9.0.0-beta.1 (Jenkins) +* [`32ffdd1`](https://github.com/eslint/eslint/commit/32ffdd181aa673ccc596f714d10a2f879ec622a7) chore: upgrade @eslint/js@9.0.0-beta.1 (#18146) (Milos Djermanovic) +* [`e41425b`](https://github.com/eslint/eslint/commit/e41425b5c3b4c885f2679a3663bd081911a8b570) chore: package.json update for @eslint/js release (Jenkins) +* [`bb3b9c6`](https://github.com/eslint/eslint/commit/bb3b9c68fe714bb8aa305be5f019a7a42f4374ee) chore: upgrade @eslint/eslintrc@3.0.2 (#18145) (Milos Djermanovic) +* [`c9f2f33`](https://github.com/eslint/eslint/commit/c9f2f3343e7c197e5e962c68ef202d6a1646866e) build: changelog update for 8.57.0 (#18144) (Milos Djermanovic) +* [`5fe095c`](https://github.com/eslint/eslint/commit/5fe095cf718b063dc5e58089b0a6cbcd53da7925) docs: show v8.57.0 as latest version in dropdown (#18142) (Milos Djermanovic) +* [`0cb4914`](https://github.com/eslint/eslint/commit/0cb4914ef93cd572ba368d390b1cf0b93f578a9d) fix: validate options when comment with just severity enables rule (#18133) (Milos Djermanovic) +* [`7db5bb2`](https://github.com/eslint/eslint/commit/7db5bb270f95d1472de0bfed0e33ed5ab294942e) docs: Show prerelease version in dropdown (#18135) (Nicholas C. Zakas) +* [`e462524`](https://github.com/eslint/eslint/commit/e462524cc318ffacecd266e6fe1038945a0b02e9) chore: upgrade eslint-release@3.2.2 (#18138) (Milos Djermanovic) +* [`8e13a6b`](https://github.com/eslint/eslint/commit/8e13a6beb587e624cc95ae16eefe503ad024b11b) chore: fix spelling mistake in README.md (#18128) (Will Eastcott) +* [`66f52e2`](https://github.com/eslint/eslint/commit/66f52e276c31487424bcf54e490c4ac7ef70f77f) chore: remove unused tools rule-types.json, update-rule-types.js (#18125) (Josh Goldberg ✨) +* [`bf0c7ef`](https://github.com/eslint/eslint/commit/bf0c7effdba51c48b929d06ce1965408a912dc77) ci: fix sync-labels value of pr-labeler (#18124) (Tanuj Kanti) +* [`cace6d0`](https://github.com/eslint/eslint/commit/cace6d0a3afa5c84b18abee4ef8c598125143461) ci: add PR labeler action (#18109) (Nitin Kumar) +* [`73a5f06`](https://github.com/eslint/eslint/commit/73a5f0641b43e169247b0000f44a366ee6bbc4f2) docs: Update README (GitHub Actions Bot) +* [`74124c2`](https://github.com/eslint/eslint/commit/74124c20287fac1995c3f4e553f0723c066f311d) feat: add suggestions to `use-isnan` in `indexOf` & `lastIndexOf` calls (#18063) (StyleShit) +* [`1a65d3e`](https://github.com/eslint/eslint/commit/1a65d3e4a6ee16e3f607d69b998a08c3fed505ca) chore: export `base` config from `eslint-config-eslint` (#18119) (Milos Djermanovic) +* [`f95cd27`](https://github.com/eslint/eslint/commit/f95cd27679eef228173e27e170429c9710c939b3) docs: Disallow multiple rule configuration comments in the same example (#18116) (Milos Djermanovic) +* [`9aa4df3`](https://github.com/eslint/eslint/commit/9aa4df3f4d85960eee72923f3b9bfc88e62f04fb) refactor: remove `globals` dependency (#18115) (Milos Djermanovic) +* [`d8068ec`](https://github.com/eslint/eslint/commit/d8068ec70fac050e900dc400510a4ad673e17633) docs: Update link for schema examples (#18112) (Svetlana) +* [`428dbdb`](https://github.com/eslint/eslint/commit/428dbdbef367e17edef7ba648fba0d37c860be9c) 9.0.0-beta.0 (Jenkins) +* [`1bbc495`](https://github.com/eslint/eslint/commit/1bbc495aecbd3e4a4aaf54d7c489191809c1b65b) Build: changelog update for 9.0.0-beta.0 (Jenkins) +* [`e40d1d7`](https://github.com/eslint/eslint/commit/e40d1d74a5b9788cbec195f4e602b50249f26659) chore: upgrade @eslint/js@9.0.0-beta.0 (#18108) (Milos Djermanovic) +* [`9870f93`](https://github.com/eslint/eslint/commit/9870f93e714edefb410fccae1e9924a3c1972a2e) chore: package.json update for @eslint/js release (Jenkins) +* [`2c62e79`](https://github.com/eslint/eslint/commit/2c62e797a433e5fc298b976872a89c594f88bb19) chore: upgrade @eslint/eslintrc@3.0.1 (#18107) (Milos Djermanovic) +* [`81f0294`](https://github.com/eslint/eslint/commit/81f0294e651928b49eb49495b90b54376073a790) chore: upgrade espree@10.0.1 (#18106) (Milos Djermanovic) +* [`5e2b292`](https://github.com/eslint/eslint/commit/5e2b2922aa65bda54b0966d1bf71acda82b3047c) chore: upgrade eslint-visitor-keys@4.0.0 (#18105) (Milos Djermanovic) +* [`9163646`](https://github.com/eslint/eslint/commit/916364692bae6a93c10b5d48fc1e9de1677d0d09) feat!: Rule Tester checks for missing placeholder data in the message (#18073) (fnx) +* [`53f0f47`](https://github.com/eslint/eslint/commit/53f0f47badffa1b04ec2836f2ae599f4fc464da2) feat: Add loadESLint() API method for v9 (#18097) (Nicholas C. Zakas) +* [`f1c7e6f`](https://github.com/eslint/eslint/commit/f1c7e6fc8ea77fcdae4ad1f8fe1cd104a281d2e9) docs: Switch to Ethical Ads (#18090) (Strek) +* [`15c143f`](https://github.com/eslint/eslint/commit/15c143f96ef164943fd3d39b5ad79d9a4a40de8f) docs: JS Foundation -> OpenJS Foundation in PR template (#18092) (Nicholas C. Zakas) +* [`c4d26fd`](https://github.com/eslint/eslint/commit/c4d26fd3d1f59c1c0f2266664887ad18692039f3) fix: `use-isnan` doesn't report on `SequenceExpression`s (#18059) (StyleShit) +* [`6ea339e`](https://github.com/eslint/eslint/commit/6ea339e658d29791528ab26aabd86f1683cab6c3) docs: add stricter rule test validations to v9 migration guide (#18085) (Milos Djermanovic) +* [`ce838ad`](https://github.com/eslint/eslint/commit/ce838adc3b673e52a151f36da0eedf5876977514) chore: replace dependency npm-run-all with npm-run-all2 ^5.0.0 (#18045) (renovate[bot]) +* [`3c816f1`](https://github.com/eslint/eslint/commit/3c816f193eecace5efc6166efa2852a829175ef8) docs: use relative link from CLI to core concepts (#18083) (Milos Djermanovic) +* [`54df731`](https://github.com/eslint/eslint/commit/54df731174d2528170560d1f765e1336eca0a8bd) chore: update dependency markdownlint-cli to ^0.39.0 (#18084) (renovate[bot]) +* [`9458735`](https://github.com/eslint/eslint/commit/9458735381269d12b24f76e1b2b6fda1bc5a509b) docs: fix malformed `eslint` config comments in rule examples (#18078) (Francesco Trotta) +* [`07a1ada`](https://github.com/eslint/eslint/commit/07a1ada7166b76c7af6186f4c5e5de8b8532edba) docs: link from `--fix` CLI doc to the relevant core concept (#18080) (Bryan Mishkin) +* [`8f06a60`](https://github.com/eslint/eslint/commit/8f06a606845f40aaf0fea1fd83d5930747c5acec) chore: update dependency shelljs to ^0.8.5 (#18079) (Francesco Trotta) +* [`b844324`](https://github.com/eslint/eslint/commit/b844324e4e8f511c9985a96c7aca063269df9570) docs: Update team responsibilities (#18048) (Nicholas C. Zakas) +* [`aadfb60`](https://github.com/eslint/eslint/commit/aadfb609f1b847e492fc3b28ced62f830fe7f294) docs: document languageOptions and other v9 changes for context (#18074) (fnx) +* [`3c4d51d`](https://github.com/eslint/eslint/commit/3c4d51d55fa5435ab18b6bf46f6b97df0f480ae7) feat!: default for `enforceForClassMembers` in `no-useless-computed-key` (#18054) (Francesco Trotta) +* [`47e60f8`](https://github.com/eslint/eslint/commit/47e60f85e0c3f275207bb4be9b5947166a190477) feat!: Stricter rule test validations (#17654) (fnx) +* [`1a94589`](https://github.com/eslint/eslint/commit/1a945890105d307541dcbff15f6438c19b476ade) feat!: `no-unused-vars` default caughtErrors to 'all' (#18043) (Josh Goldberg ✨) +* [`857e242`](https://github.com/eslint/eslint/commit/857e242584227181ecb8af79fc6bc236b9975228) docs: tweak explanation for meta.docs rule properties (#18057) (Bryan Mishkin) +* [`10485e8`](https://github.com/eslint/eslint/commit/10485e8b961d045514bc1e34227cf09867a6c4b7) docs: recommend messageId over message for reporting rule violations (#18050) (Bryan Mishkin) +* [`98b5ab4`](https://github.com/eslint/eslint/commit/98b5ab406bac6279eadd84e8a5fd5a01fc586ff1) docs: Update README (GitHub Actions Bot) +* [`93ffe30`](https://github.com/eslint/eslint/commit/93ffe30da5e2127e336c1c22e69e09ec0558a8e6) chore: update dependency file-entry-cache to v8 (#17903) (renovate[bot]) +* [`505fbf4`](https://github.com/eslint/eslint/commit/505fbf4b35c14332bffb0c838cce4843a00fad68) docs: update `no-restricted-imports` rule (#18015) (Tanuj Kanti) +* [`2d11d46`](https://github.com/eslint/eslint/commit/2d11d46e890a9f1b5f639b8ee034ffa9bd453e42) feat: add suggestions to `use-isnan` in binary expressions (#17996) (StyleShit) +* [`c25b4af`](https://github.com/eslint/eslint/commit/c25b4aff1fe35e5bd9d4fcdbb45b739b6d253828) docs: Update README (GitHub Actions Bot) +* [`fd1e2f3`](https://github.com/eslint/eslint/commit/fd1e2f346307f7711bf0f206b4d09656d15a7e1a) 9.0.0-alpha.2 (Jenkins) +* [`96f8877`](https://github.com/eslint/eslint/commit/96f8877de7dd3d92ac5afb77c92d821002d24929) Build: changelog update for 9.0.0-alpha.2 (Jenkins) +* [`6ffdcbb`](https://github.com/eslint/eslint/commit/6ffdcbb8c51956054d3f81c5ce446c15dcd51a6f) chore: upgrade @eslint/js@9.0.0-alpha.2 (#18038) (Milos Djermanovic) +* [`2c12715`](https://github.com/eslint/eslint/commit/2c1271528e88d0c3c6a92eeee902001f1703d5c9) chore: package.json update for @eslint/js release (Jenkins) +* [`cc74c4d`](https://github.com/eslint/eslint/commit/cc74c4da99368b97494b924dbea1cb6e87adec53) chore: upgrade espree@10.0.0 (#18037) (Milos Djermanovic) +* [`26093c7`](https://github.com/eslint/eslint/commit/26093c76903310d12f21e24e73d97c0d2ac1f359) feat: fix false negatives in `no-this-before-super` (#17762) (Yosuke Ota) +* [`57089cb`](https://github.com/eslint/eslint/commit/57089cb5166acf8b8bdba8a8dbeb0a129f841478) feat!: no-restricted-imports allow multiple config entries for same path (#18021) (Milos Djermanovic) +* [`33d1ab0`](https://github.com/eslint/eslint/commit/33d1ab0b6ea5fcebca7284026d2396df41b06566) docs: add more examples to flat config ignores docs (#18020) (Milos Djermanovic) +* [`e6eebca`](https://github.com/eslint/eslint/commit/e6eebca90750ef5c7c99d4fe3658553cf737dab8) docs: Update sort-keys options properties count (#18025) (LB (Ben Johnston)) +* [`dfb68b6`](https://github.com/eslint/eslint/commit/dfb68b63ce6e8df6ffe81bd843e650c5b017dce9) chore: use Node.js 20 for docs sites (#18026) (Milos Djermanovic) +* [`8c1b8dd`](https://github.com/eslint/eslint/commit/8c1b8dda169920c4e3b99f6548f9c872d65ee426) test: add more tests for ignoring files and directories (#18018) (Milos Djermanovic) +* [`60b966b`](https://github.com/eslint/eslint/commit/60b966b6861da11617ddc15487bd7a51c584c596) chore: update dependency @eslint/js to v9.0.0-alpha.1 (#18014) (renovate[bot]) +* [`5471e43`](https://github.com/eslint/eslint/commit/5471e435d12bf5add9869d81534b147e445a2368) feat: convert unsafe autofixes to suggestions in `no-implicit-coercion` (#17985) (Gürgün Dayıoğlu) +* [`2e1d549`](https://github.com/eslint/eslint/commit/2e1d54960051b59e1c731fa44c2ef843290b1335) feat!: detect duplicate test cases (#17955) (Bryan Mishkin) +* [`1fedfd2`](https://github.com/eslint/eslint/commit/1fedfd28a46d86b2fbcf06a2328befafd6535a88) docs: Improve flat config ignores docs (#17997) (Nicholas C. Zakas) +* [`e3051be`](https://github.com/eslint/eslint/commit/e3051be6366b00e1571e702023a351177d24e443) feat: emit warning when `.eslintignore` file is detected (#17952) (Nitin Kumar) +* [`38b9b06`](https://github.com/eslint/eslint/commit/38b9b06695f88c70441dd15ae5d97ffd8088be23) docs: update valid-typeof rule (#18001) (Tanuj Kanti) +* [`39076fb`](https://github.com/eslint/eslint/commit/39076fb5e4c7fa10b305d510f489aff34a5f5d99) fix: handle absolute file paths in `RuleTester` (#17989) (Nitin Kumar) +* [`b4abfea`](https://github.com/eslint/eslint/commit/b4abfea4c1703a50f1ce639e3207ad342a56f79d) docs: Update note about ECMAScript support (#17991) (Francesco Trotta) +* [`c893bc0`](https://github.com/eslint/eslint/commit/c893bc0bdf1bca256fbab6190358e5f922683249) chore: update `markdownlint` to `v0.33.0` (#17995) (Nitin Kumar) +* [`6788873`](https://github.com/eslint/eslint/commit/6788873328a7f974d5e45c0be06ca0c7dd409acd) docs: Update release blog post template (#17994) (Nicholas C. Zakas) +* [`1f37442`](https://github.com/eslint/eslint/commit/1f3744278433006042b8d5f4e9e1e488b2bbb011) docs: Add sections on non-npm plugin configuration (#17984) (Nicholas C. Zakas) +* [`bbf2b21`](https://github.com/eslint/eslint/commit/bbf2b214473606329a5dbcbe022079f4048923a8) 9.0.0-alpha.1 (Jenkins) +* [`52d5e7a`](https://github.com/eslint/eslint/commit/52d5e7a41d37a1a6d9aa1dffba3b688573800536) Build: changelog update for 9.0.0-alpha.1 (Jenkins) +* [`c5e50ee`](https://github.com/eslint/eslint/commit/c5e50ee65cf22871770b1d4d438b9056c577f646) chore: package.json update for @eslint/js release (Jenkins) +* [`1bf2520`](https://github.com/eslint/eslint/commit/1bf2520c4166aa55596417bf44c567555bc65fba) chore: Split Docs CI from core CI (#17897) (Nicholas C. Zakas) +* [`6d11f3d`](https://github.com/eslint/eslint/commit/6d11f3dac1b76188d7fda6e772e89b5c3945ac4d) fix: Ensure config keys are printed for config errors (#17980) (Nicholas C. Zakas) +* [`320787e`](https://github.com/eslint/eslint/commit/320787e661beb979cf063d0f8333654f94ef9efd) chore: delete relative-module-resolver.js (#17981) (Francesco Trotta) +* [`96307da`](https://github.com/eslint/eslint/commit/96307da837c407c9a1275124b65ca29c07ffd5e4) docs: migration guide entry for `no-inner-declarations` (#17977) (Tanuj Kanti) +* [`40be60e`](https://github.com/eslint/eslint/commit/40be60e0186cdde76219df4e8e628125df2912d8) docs: Update README (GitHub Actions Bot) +* [`a630edd`](https://github.com/eslint/eslint/commit/a630edd809894dc38752705bb5954d847987f031) feat: maintain latest ecma version in ESLint (#17958) (Milos Djermanovic) +* [`701f1af`](https://github.com/eslint/eslint/commit/701f1afbee34e458b56d2dfa36d9153d6aebea3a) feat!: no-inner-declaration new default behaviour and option (#17885) (Tanuj Kanti) +* [`b4e0503`](https://github.com/eslint/eslint/commit/b4e0503a56beea1222be266cc6b186d89410d1f2) feat: add `no-useless-assignment` rule (#17625) (Yosuke Ota) +* [`806f708`](https://github.com/eslint/eslint/commit/806f70878e787f2c56aaa42a3e7adb61bc015278) fix: `no-misleading-character-class` edge cases with granular errors (#17970) (Milos Djermanovic) +* [`287c4b7`](https://github.com/eslint/eslint/commit/287c4b7d498746b43392ee4fecd6904a9cd4b30b) feat: `no-misleading-character-class` granular errors (#17515) (Josh Goldberg ✨) +* [`d31c180`](https://github.com/eslint/eslint/commit/d31c180312260d1a286cc8162907b6a33368edc9) docs: fix number of code-path events on custom rules page (#17969) (Richard Hunter) +* [`1529ab2`](https://github.com/eslint/eslint/commit/1529ab288ec815b2690864e04dd6d0a1f0b537c6) docs: reorder entries in v9 migration guide (#17967) (Milos Djermanovic) +* [`bde5105`](https://github.com/eslint/eslint/commit/bde51055530d4a71bd9f48c90ed7de9c0b767d86) fix!: handle `--output-file` for empty output when saving to disk (#17957) (Nitin Kumar) +* [`9507525`](https://github.com/eslint/eslint/commit/95075251fb3ce35aaf7eadbd1d0a737106c13ec6) docs: Explain how to combine configs (#17947) (Nicholas C. Zakas) +* [`7c78576`](https://github.com/eslint/eslint/commit/7c785769fd177176966de7f6c1153480f7405000) docs: Add more removed `context` methods to migrate to v9 guide (#17951) (Milos Djermanovic) +* [`07107a5`](https://github.com/eslint/eslint/commit/07107a5904c2580243971c8ad7f26a04738b712e) fix!: upgrade eslint-scope@8.0.0 (#17942) (Milos Djermanovic) +* [`3ee0f6c`](https://github.com/eslint/eslint/commit/3ee0f6ca5d756da647e4e76bf3daa82a5905a792) fix!: no-unused-vars `varsIgnorePattern` behavior with catch arguments (#17932) (Tanuj Kanti) +* [`4926f33`](https://github.com/eslint/eslint/commit/4926f33b96faf07a64aceec5f1f4882f4faaf4b5) refactor: use `Object.hasOwn()` (#17948) (Milos Djermanovic) +* [`df200e1`](https://github.com/eslint/eslint/commit/df200e147705eb62f94b99c170554327259c65d4) refactor: use `Array.prototype.at()` to get last elements (#17949) (Milos Djermanovic) +* [`51f8bc8`](https://github.com/eslint/eslint/commit/51f8bc836bf0b13dad3a897ae84259bcdaed2431) fix!: configuration comments with just severity should retain options (#17945) (Milos Djermanovic) +* [`3a877d6`](https://github.com/eslint/eslint/commit/3a877d68d0151679f8bf1cabc39746778754b3dd) docs: Update removed CLI flags migration (#17939) (Nicholas C. Zakas) +* [`750b8df`](https://github.com/eslint/eslint/commit/750b8dff6df02a500e12cb78390fd14814c82e5b) chore: update dependency glob to v10 (#17917) (renovate[bot]) +* [`c2bf27d`](https://github.com/eslint/eslint/commit/c2bf27def29ef1ca7f5bfe20c1306bf78087ea29) build: update docs files when publishing prereleases (#17940) (Milos Djermanovic) +* [`d191bdd`](https://github.com/eslint/eslint/commit/d191bdd67214c33e65bd605e616ca7cc947fd045) feat!: Remove CodePath#currentSegments (#17936) (Milos Djermanovic) +* [`4a9cd1e`](https://github.com/eslint/eslint/commit/4a9cd1ea1cd0c115b98d07d1b6018ca918a9c73f) docs: Update Linter API for v9 (#17937) (Milos Djermanovic) +* [`74794f5`](https://github.com/eslint/eslint/commit/74794f53a6bc88b67653c737f858cfdf35b1c73d) chore: removed unused eslintrc modules (#17938) (Milos Djermanovic) +* [`10ed29c`](https://github.com/eslint/eslint/commit/10ed29c0c4505dbac3bb05b0e3d61f329b99f747) chore: remove unused dependency rimraf (#17934) (Francesco Trotta) +* [`2a8eea8`](https://github.com/eslint/eslint/commit/2a8eea8e5847f4103d90d667a2b08edf9795545f) docs: update docs for v9.0.0-alpha.0 (#17929) (Milos Djermanovic) +* [`903ee60`](https://github.com/eslint/eslint/commit/903ee60ea910aee344df7edb66874f80e4b6ed31) ci: use `--force` flag when installing eslint (#17921) (Milos Djermanovic) +* [`73a841a`](https://github.com/eslint/eslint/commit/73a841a7dff809e6cf7bb9a37f073d168eabd45f) 9.0.0-alpha.0 (Jenkins) +* [`e91d85d`](https://github.com/eslint/eslint/commit/e91d85db76c7bd8a5998f7ff52d2cc844d0e953e) Build: changelog update for 9.0.0-alpha.0 (Jenkins) +* [`7f0ba51`](https://github.com/eslint/eslint/commit/7f0ba51bcef3e6fbf972ceb20403238f0e1f0ea9) docs: show `NEXT` in version selectors (#17911) (Milos Djermanovic) +* [`17fedc1`](https://github.com/eslint/eslint/commit/17fedc17e9e6e39ad986d917fb4e9e4835c50482) chore: upgrade @eslint/js@9.0.0-alpha.0 (#17928) (Milos Djermanovic) +* [`cb89ef3`](https://github.com/eslint/eslint/commit/cb89ef373fffbed991f4e099cb255a7c116889f9) chore: package.json update for @eslint/js release (Jenkins) +* [`0a7911e`](https://github.com/eslint/eslint/commit/0a7911e09adf2aca4d93c81f4be1cd80db7dd735) docs: add flat config default to v9 migration guide (#17927) (Milos Djermanovic) +* [`94f8065`](https://github.com/eslint/eslint/commit/94f80652aca302e2715ea51c10c3a1010786b751) docs: Add CLI updates to migrate to v9 guide (#17924) (Nicholas C. Zakas) +* [`16187f2`](https://github.com/eslint/eslint/commit/16187f23c6e5aaed3b50ff551a66f758893d5422) docs: Add exported and string config notes to migrate to v9 guide (#17926) (Nicholas C. Zakas) +* [`3ae50cc`](https://github.com/eslint/eslint/commit/3ae50cc788c3cdd209e642573e3c831dd86fa0cd) docs: Add RuleTester changes to migrate to v9 guide (#17923) (Nicholas C. Zakas) +* [`0831b58`](https://github.com/eslint/eslint/commit/0831b58fe6fb5778c92aeb4cefa9ecedbbfbf48b) docs: add rule changes to v9 migration guide (#17925) (Milos Djermanovic) +* [`946ae00`](https://github.com/eslint/eslint/commit/946ae00457265eb298eb169d6d48ca7ec71b9eef) feat!: FlatRuleTester -> RuleTester (#17922) (Nicholas C. Zakas) +* [`f182114`](https://github.com/eslint/eslint/commit/f182114144ae0bb7187de34a1661f31fb70f1357) fix: deep merge behavior in flat config (#17906) (Francesco Trotta) +* [`037abfc`](https://github.com/eslint/eslint/commit/037abfc21f264fca3a910c4a5cd23d1bf6826c3d) docs: update API docs (#17919) (Milos Djermanovic) +* [`baff28c`](https://github.com/eslint/eslint/commit/baff28ce8f167f564471f1d70d6e9c4b0cb1a508) feat!: remove `no-inner-declarations` from `eslint:recommended` (#17920) (Milos Djermanovic) +* [`f6f4a45`](https://github.com/eslint/eslint/commit/f6f4a45680039f720a2fccd5f445deaf45babb3d) chore: drop structuredClone polyfill for v9 (#17915) (Kevin Gibbons) +* [`afc3c03`](https://github.com/eslint/eslint/commit/afc3c038ed3132a99659604624cc24e702eec45a) docs: add function-style and `meta.schema` changes to v9 migration guide (#17912) (Milos Djermanovic) +* [`cadfbcd`](https://github.com/eslint/eslint/commit/cadfbcd468737fc9447243edd1d15058efb6d3d8) feat!: Rename FlatESLint to ESLint (#17914) (Nicholas C. Zakas) +* [`412dcbb`](https://github.com/eslint/eslint/commit/412dcbb672b5a5859f96753afa7cb87291135a1b) chore: upgrade eslint-plugin-n@16.6.0 (#17916) (Milos Djermanovic) +* [`8792464`](https://github.com/eslint/eslint/commit/8792464ee7956af82dab582ca9ee59da596a608e) feat: Enable eslint.config.mjs and eslint.config.cjs (#17909) (Nicholas C. Zakas) +* [`24ce927`](https://github.com/eslint/eslint/commit/24ce9276d472b85541c4b01db488c789f33fd234) feat: warn by default for unused disable directives (#17879) (Bryan Mishkin) +* [`02a8baf`](https://github.com/eslint/eslint/commit/02a8baf9f2ef7b309c7d45564a79ed5d2153057f) chore: Rename files with underscores (#17910) (Nicholas C. Zakas) +* [`d1018fc`](https://github.com/eslint/eslint/commit/d1018fc5e59db0495aa4a7f501c9d3f831981f35) feat!: skip running warnings in --quiet mode (#17274) (Maddy Miller) +* [`fb81b1c`](https://github.com/eslint/eslint/commit/fb81b1cb78d2692a87fd3591fdc0f96b0c95e760) feat!: Set default `schema: []`, drop support for function-style rules (#17792) (Milos Djermanovic) +* [`1da0723`](https://github.com/eslint/eslint/commit/1da0723695d080008b22f30c8b5c86fe386c6242) docs: update `eslint:recommended` section in Migrate to v9.x (#17908) (Milos Djermanovic) +* [`f55881f`](https://github.com/eslint/eslint/commit/f55881f492d10e9c759e459ba6bade1be3dad84b) docs: remove configuration-files-new.md (#17907) (Milos Djermanovic) +* [`63ae191`](https://github.com/eslint/eslint/commit/63ae191070569a9118b5972c90a98633b0a336e1) docs: Migrate to v9.0.0 (#17905) (Nicholas C. Zakas) +* [`e708496`](https://github.com/eslint/eslint/commit/e7084963c73f3cbaae5d569b4a2bee1509dd8cef) docs: Switch to flat config by default (#17840) (Nicholas C. Zakas) +* [`fdf0424`](https://github.com/eslint/eslint/commit/fdf0424c5c08c058479a6cd7676be6985e0f400f) docs: Update Create a Plugin for flat config (#17826) (Nicholas C. Zakas) +* [`0b21e1f`](https://github.com/eslint/eslint/commit/0b21e1fd67d94f907d007a7a9707a3ae1cc08575) feat!: add two more cases to `no-implicit-coercion` (#17832) (Gürgün Dayıoğlu) +* [`e6a91bd`](https://github.com/eslint/eslint/commit/e6a91bdf401e3b765f2b712e447154e4a2419fbc) docs: Switch shareable config docs to use flat config (#17827) (Nicholas C. Zakas) +* [`c0f5d91`](https://github.com/eslint/eslint/commit/c0f5d913b0f07de332dfcecf6052f1e64bf3d2fb) chore: remove creating an unused instance of Linter in tests (#17902) (Milos Djermanovic) +* [`2916c63`](https://github.com/eslint/eslint/commit/2916c63046603e0cdc578d3c2eef8fca5b2e8847) feat!: Switch Linter to flat config by default (#17851) (Nicholas C. Zakas) +* [`3826cdf`](https://github.com/eslint/eslint/commit/3826cdf89294d079be037a9ab30b7506077b26ac) chore: use jsdoc/no-multi-asterisks with allowWhitespace: true (#17900) (Percy Ma) +* [`a9a17b3`](https://github.com/eslint/eslint/commit/a9a17b3f1cb6b6c609bda86a618ac5ff631285d2) chore: fix getting scope in tests (#17899) (Milos Djermanovic) +* [`200518e`](https://github.com/eslint/eslint/commit/200518eb6d42de4c3b0c6ef190fc09a95718297e) fix!: Parsing 'exported' comment using parseListConfig (#17675) (amondev) +* [`3831fb7`](https://github.com/eslint/eslint/commit/3831fb78daa3da296b71823f61f8e3a4556ff7d3) docs: updated examples of `max-lines` rule (#17898) (Tanuj Kanti) +* [`bdd6ba1`](https://github.com/eslint/eslint/commit/bdd6ba138645dba0442bb0ed2ee73049df56f38d) feat!: Remove valid-jsdoc and require-jsdoc (#17694) (Nicholas C. Zakas) +* [`595a1f6`](https://github.com/eslint/eslint/commit/595a1f689edb5250d8398af13c3e4bd19d284d92) test: ensure that CLI tests run with FlatESLint (#17884) (Francesco Trotta) +* [`12be307`](https://github.com/eslint/eslint/commit/12be3071d014814149e8e6d602f5c192178ca771) fix!: Behavior of CLI when no arguments are passed (#17644) (Nicholas C. Zakas) +* [`cd1ac20`](https://github.com/eslint/eslint/commit/cd1ac2041f48f2b6d743ebf671d0279a70de6eea) docs: Update README (GitHub Actions Bot) +* [`8fe8c56`](https://github.com/eslint/eslint/commit/8fe8c5626b98840d6a8580004f6ceffeff56264f) feat!: Update shouldUseFlatConfig and CLI so flat config is default (#17748) (Nicholas C. Zakas) +* [`c7eca43`](https://github.com/eslint/eslint/commit/c7eca43202be98f6ff253b46c9a38602eeb92ea0) chore: update dependency markdownlint-cli to ^0.38.0 (#17865) (renovate[bot]) +* [`60dea3e`](https://github.com/eslint/eslint/commit/60dea3e3abd6c0b6aab25437b2d0501b0d30b70c) feat!: deprecate no-new-symbol, recommend no-new-native-nonconstructor (#17710) (Francesco Trotta) +* [`5aa9c49`](https://github.com/eslint/eslint/commit/5aa9c499da48b2d3187270d5d8ece71ad7521f56) feat!: check for parsing errors in suggestion fixes (#16639) (Bryan Mishkin) +* [`b3e0bb0`](https://github.com/eslint/eslint/commit/b3e0bb03cc814e78b06a1acc4e5347b4c90d72bf) feat!: assert suggestion messages are unique in rule testers (#17532) (Josh Goldberg ✨) +* [`e563c52`](https://github.com/eslint/eslint/commit/e563c52e35d25f726d423cc3b1dffcd80027fd99) feat!: `no-invalid-regexp` make allowConstructorFlags case-sensitive (#17533) (Josh Goldberg ✨) +* [`e5f02c7`](https://github.com/eslint/eslint/commit/e5f02c70084c4f80900c0875b08f665e1f030af2) fix!: no-sequences rule schema correction (#17878) (MHO) +* [`6ee3e9e`](https://github.com/eslint/eslint/commit/6ee3e9eb5df7bdfdaa1746214793ed511112be76) feat!: Update `eslint:recommended` configuration (#17716) (Milos Djermanovic) +* [`c2cf85a`](https://github.com/eslint/eslint/commit/c2cf85a7447777e6b499cbb5c49de919bb5c817f) feat!: drop support for string configurations in flat config array (#17717) (Milos Djermanovic) +* [`c314fd6`](https://github.com/eslint/eslint/commit/c314fd612587c42cfbe6acbe286629c4178be3f7) feat!: Remove `SourceCode#getComments()` (#17715) (Milos Djermanovic) +* [`ae78ff1`](https://github.com/eslint/eslint/commit/ae78ff16558a1a2ca07b2b9cd294157d1bdcce2e) feat!: Remove deprecated context methods (#17698) (Nicholas C. Zakas) +* [`f71c328`](https://github.com/eslint/eslint/commit/f71c328e2786e2d73f168e43c7f96de172484a49) feat!: Swap FlatESLint-ESLint, FlatRuleTester-RuleTester in API (#17823) (Nicholas C. Zakas) +* [`5304da0`](https://github.com/eslint/eslint/commit/5304da03d94dc8cb19060e2efc9206784c4cec0e) feat!: remove formatters except html, json(-with-metadata), and stylish (#17531) (Josh Goldberg ✨) +* [`e1e827f`](https://github.com/eslint/eslint/commit/e1e827ffcbd73faa40dbac3b97529452e9c67108) feat!: Require Node.js `^18.18.0 || ^20.9.0 || >=21.1.0` (#17725) (Milos Djermanovic) +* [`b577e8a`](https://github.com/eslint/eslint/commit/b577e8a55750c5e842074f62f1babb1836c4571c) fix: allow circular references in config (#17752) (Francesco Trotta) +* [`cc0c9f7`](https://github.com/eslint/eslint/commit/cc0c9f707aa9da7965b98151868b3c249c7f8f30) ci: bump github/codeql-action from 2 to 3 (#17873) (dependabot[bot]) + v9.0.0-rc.0 - March 22, 2024 * [`297416d`](https://github.com/eslint/eslint/commit/297416d2b41f5880554d052328aa36cd79ceb051) chore: package.json update for eslint-9.0.0-rc.0 (#18223) (Francesco Trotta) From e0cbc50179adac1670f4e0bd9093387a51f4f42a Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 5 Apr 2024 20:52:07 +0000 Subject: [PATCH 069/166] 9.0.0 --- docs/package.json | 2 +- docs/src/_data/versions.json | 10 +++++----- docs/src/use/formatters/html-formatter-example.html | 2 +- package.json | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/package.json b/docs/package.json index 3c90ccfa41a..0bcceba76ac 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "9.0.0-rc.0", + "version": "9.0.0", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/_data/versions.json b/docs/src/_data/versions.json index c5642ae9ad7..09d0c1d79c4 100644 --- a/docs/src/_data/versions.json +++ b/docs/src/_data/versions.json @@ -6,14 +6,14 @@ "path": "/docs/head/" }, { - "version": "9.0.0-rc.0", - "branch": "next", - "path": "/docs/next/" + "version": "9.0.0", + "branch": "latest", + "path": "/docs/latest/" }, { "version": "8.57.0", - "branch": "latest", - "path": "/docs/latest/" + "branch": "v8.x", + "path": "/docs/v8.x/" } ] } diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index d9a8be4e1a3..570830c9204 100644 --- a/docs/src/use/formatters/html-formatter-example.html +++ b/docs/src/use/formatters/html-formatter-example.html @@ -118,7 +118,7 @@

ESLint Report

- 8 problems (4 errors, 4 warnings) - Generated on Fri Mar 22 2024 20:54:14 GMT+0000 (Coordinated Universal Time) + 8 problems (4 errors, 4 warnings) - Generated on Fri Apr 05 2024 20:52:07 GMT+0000 (Coordinated Universal Time)
diff --git a/package.json b/package.json index e2c38c02da1..356bfbbc8c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "9.0.0-rc.0", + "version": "9.0.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 5c353215e05818e17e83192acbb4d3730c716afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Lui=20=E5=88=98=E5=B1=95=E9=B9=8F?= Date: Sun, 7 Apr 2024 03:38:04 -0700 Subject: [PATCH 070/166] docs: add eslintrc-only note to `--rulesdir` (#18281) Added eslintrc-only note to `--rulesdir` --- docs/src/use/command-line-interface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/use/command-line-interface.md b/docs/src/use/command-line-interface.md index 2e52e47283e..65b504646c8 100644 --- a/docs/src/use/command-line-interface.md +++ b/docs/src/use/command-line-interface.md @@ -321,7 +321,7 @@ npx eslint --rule 'quotes: [error, double]' --no-eslintrc **Deprecated**: Use rules from plugins instead. -This option allows you to specify another directory from which to load rules files. This allows you to dynamically load new rules at run time. This is useful when you have custom rules that aren't suitable for being bundled with ESLint. +**eslintrc Mode Only.** This option allows you to specify another directory from which to load rules files. This allows you to dynamically load new rules at run time. This is useful when you have custom rules that aren't suitable for being bundled with ESLint. * **Argument Type**: String. Path to directory. The rules in your custom rules directory must follow the same format as bundled rules to work properly. * **Multiple Arguments**: Yes. From 1fa66220ad130eeb69cfa0207d3896b7bb09c576 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Mon, 8 Apr 2024 09:10:36 +0200 Subject: [PATCH 071/166] build: do not use `--force` flag to install dependencies (#18284) --- .github/workflows/ci.yml | 6 +++--- .github/workflows/docs-ci.yml | 2 +- .github/workflows/update-readme.yml | 2 +- docs/netlify.toml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 555b73a3b44..1555a688fa3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: with: node-version: "lts/*" - name: Install Packages - run: npm install --force + run: npm install - name: Install Docs Packages working-directory: docs @@ -60,7 +60,7 @@ jobs: with: node-version: ${{ matrix.node }} - name: Install Packages - run: npm install --force + run: npm install - name: Test run: node Makefile mocha - name: Fuzz Test @@ -75,7 +75,7 @@ jobs: with: node-version: "20" # Should be the same as the version used on Netlify to build the ESLint Playground - name: Install Packages - run: npm install --force + run: npm install - name: Test run: node Makefile wdio - name: Fuzz Test diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml index 09fbf643c3f..de07aa1819b 100644 --- a/.github/workflows/docs-ci.yml +++ b/.github/workflows/docs-ci.yml @@ -28,7 +28,7 @@ jobs: run: npm install - name: Install Packages - run: npm install --force + run: npm install - name: Stylelint Docs working-directory: docs diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml index b919dc8c60a..f1071502b1a 100644 --- a/.github/workflows/update-readme.yml +++ b/.github/workflows/update-readme.yml @@ -17,7 +17,7 @@ jobs: uses: actions/setup-node@v4 - name: Install npm packages - run: npm install --force + run: npm install - name: Update README with latest team and sponsor data run: npm run build:readme diff --git a/docs/netlify.toml b/docs/netlify.toml index 57a235140ec..a21fb4047ac 100644 --- a/docs/netlify.toml +++ b/docs/netlify.toml @@ -1,2 +1,2 @@ [build] - command = "cd .. && npm install -f && cd ./docs && npm run build" + command = "cd .. && npm install && cd ./docs && npm run build" From 113f51ec4e52d3082a74b9682239a6e28d1a70ee Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 9 Apr 2024 20:18:45 -0700 Subject: [PATCH 072/166] docs: Mention package.json config support dropped (#18305) fixes #18296 --- docs/src/use/configure/migration-guide.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/src/use/configure/migration-guide.md b/docs/src/use/configure/migration-guide.md index ee1ece3a2a1..c99080f4da2 100644 --- a/docs/src/use/configure/migration-guide.md +++ b/docs/src/use/configure/migration-guide.md @@ -646,6 +646,12 @@ The `--resolve-plugins-relative-to` flag was used to indicate which directory pl With flat config, shareable configs can specify their dependencies directly, so this flag is no longer needed. +### `package.json` Configuration No Longer Supported + +With eslintrc, it was possible to use a `package.json` file to configure ESLint using the `eslintConfig` key. + +With flat config, it's no longer possible to use a `package.json` file to configure ESLint. You'll need to move your configuration into a separate file. + ### Additional Changes The following changes have been made from the eslintrc to the flat config file format: From e1e305defaab98605d79c81d67ee5a48558c458a Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Wed, 10 Apr 2024 20:11:32 +0200 Subject: [PATCH 073/166] docs: fix `linebreak-style` examples (#18262) fix `linebreak-style` examples --- docs/src/rules/linebreak-style.md | 7 +++++-- docs/tools/code-block-utils.js | 11 +++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/src/rules/linebreak-style.md b/docs/src/rules/linebreak-style.md index d4dd0f5cf27..38f1fbc3f67 100644 --- a/docs/src/rules/linebreak-style.md +++ b/docs/src/rules/linebreak-style.md @@ -52,6 +52,7 @@ var a = 'a', // \n function foo(params) { // \n // do stuff \n }// \n + ``` ::: @@ -66,6 +67,7 @@ Examples of **incorrect** code for this rule with the `"windows"` option: /*eslint linebreak-style: ["error", "windows"]*/ var a = 'a'; // \n + ``` ::: @@ -75,14 +77,15 @@ Examples of **correct** code for this rule with the `"windows"` option: ::: correct ```js -/*eslint linebreak-style: ["error", "windows"]*/ - +/*eslint linebreak-style: ["error", "windows"]*/ // \r\n +// \r\n var a = 'a', // \r\n b = 'b'; // \r\n // \r\n function foo(params) { // \r\n // do stuff \r\n } // \r\n + ``` ::: diff --git a/docs/tools/code-block-utils.js b/docs/tools/code-block-utils.js index faaadba70e1..6c398eea8bd 100644 --- a/docs/tools/code-block-utils.js +++ b/docs/tools/code-block-utils.js @@ -14,11 +14,14 @@ function docsExampleCodeToParsableCode(code) { return code - // Remove trailing newline and presentational `⏎` characters - .replace(/⏎(?=\n)/gu, "") + // Code blocks always contain an extra line break at the end, so remove it. + .replace(/\n$/u, "") - // Code blocks always contain extra line breaks, so remove them. - .replace(/\n$/u, ""); + // Replace LF line breaks with CRLF after `\r\n` sequences. + .replace(/(?<=\\r\\n)\n/gu, "\r\n") + + // Remove presentational `⏎` characters at the end of lines. + .replace(/⏎(?=\n)/gu, ""); } module.exports = { From 36103a52432fffa20b90f2c6960757e6b9dc778f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Thu, 11 Apr 2024 17:06:56 +0800 Subject: [PATCH 074/166] chore: eslint-plugin-n v17.0.0 (#18315) * chore: eslint-plugin-n v17.0.0 * Update package.json --- packages/eslint-config-eslint/package.json | 2 +- tests/lib/eslint/eslint.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-config-eslint/package.json b/packages/eslint-config-eslint/package.json index f3632fad9f3..b267841cb62 100644 --- a/packages/eslint-config-eslint/package.json +++ b/packages/eslint-config-eslint/package.json @@ -32,7 +32,7 @@ "@eslint/js": "^8.42.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-jsdoc": "^46.5.1", - "eslint-plugin-n": "^16.6.0", + "eslint-plugin-n": "^17.2.0", "eslint-plugin-unicorn": "^49.0.0" }, "keywords": [ diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 9eb1913c7d1..5f1d40acaaa 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -4475,6 +4475,7 @@ describe("ESLint", () => { await assert.rejects(eslint.lintFiles("*.js")); // Wait until all files have been closed. + // eslint-disable-next-line n/no-unsupported-features/node-builtins -- it's still an experimental feature. while (process.getActiveResourcesInfo().includes("CloseReq")) { await timers.setImmediate(); } From 78e45b1d8d6b673ced233ca82b9ff1dddcdd1fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Thu, 11 Apr 2024 18:12:02 +0800 Subject: [PATCH 075/166] chore: eslint-plugin-eslint-plugin v6.0.0 (#18316) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 356bfbbc8c1..acd53912281 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "eslint": "file:.", "eslint-config-eslint": "file:packages/eslint-config-eslint", "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-eslint-plugin": "^5.2.1", + "eslint-plugin-eslint-plugin": "^6.0.0", "eslint-plugin-internal-rules": "file:tools/internal-rules", "eslint-plugin-jsdoc": "^46.9.0", "eslint-plugin-n": "^16.6.0", From c537d76327586616b7ca5d00e76eaf6c76e6bcd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Thu, 11 Apr 2024 18:53:37 +0800 Subject: [PATCH 076/166] docs: update `npm init @eslint/config` generated file names (#18298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: update `eslint init @eslint/config` generated file names Signed-off-by: 唯然 * Update getting-started.md --------- Signed-off-by: 唯然 --- README.md | 2 +- docs/src/use/getting-started.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6c6463aa5b1..866d7fbcec1 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ After that, you can run ESLint on any file or directory like this: ## Configuration -After running `npm init @eslint/config`, you'll have an `eslint.config.js` or `eslint.config.mjs` file in your directory. In it, you'll see some rules configured like this: +After running `npm init @eslint/config`, you'll have an `eslint.config.js` (or `eslint.config.mjs`) file in your directory. In it, you'll see some rules configured like this: ```js import pluginJs from "@eslint/js"; diff --git a/docs/src/use/getting-started.md b/docs/src/use/getting-started.md index a1da8053c00..449e3575885 100644 --- a/docs/src/use/getting-started.md +++ b/docs/src/use/getting-started.md @@ -50,7 +50,7 @@ yarn run eslint yourfile.js **Note:** If you are coming from a version before 9.0.0 please see the [migration guide](configure/migration-guide). -After running `npm init @eslint/config`, you'll have an `eslint.config.js` file in your directory. In it, you'll see some rules configured like this: +After running `npm init @eslint/config`, you'll have an `eslint.config.js` (or `eslint.config.mjs`) file in your directory. In it, you'll see some rules configured like this: ```js // eslint.config.js From df5f8a9bc1042c13f1969c9fbd8c72eee0662daa Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Thu, 11 Apr 2024 23:45:53 +0530 Subject: [PATCH 077/166] docs: `paths` and `patterns` difference in `no-restricted-imports` (#18273) * docs: mention paths and pattern difference * docs: add examples for clarification * fix typo * update examples --- docs/src/rules/no-restricted-imports.md | 29 +++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/docs/src/rules/no-restricted-imports.md b/docs/src/rules/no-restricted-imports.md index 2211fd21c2f..f7357c0580c 100644 --- a/docs/src/rules/no-restricted-imports.md +++ b/docs/src/rules/no-restricted-imports.md @@ -286,6 +286,31 @@ import { AllowedObject } from "foo"; This is also an object option whose value is an array. This option allows you to specify multiple modules to restrict using `gitignore`-style patterns. +Where `paths` option takes exact import paths, `patterns` option can be used to specify the import paths with more flexibility, allowing for the restriction of multiple modules within the same directory. For example: + +```json +"no-restricted-imports": ["error", { + "paths": [{ + "name": "import-foo", + }] +}] +``` + +This configuration restricts import of the `import-foo` module but wouldn't restrict the import of `import-foo/bar` or `import-foo/baz`. You can use `patterns` to restrict both: + +```json +"no-restricted-imports": ["error", { + "paths": [{ + "name": "import-foo", + }], + "patterns": [{ + "group": ["import-foo/ba*"], + }] +}] +``` + +This configuration restricts imports not just from `import-foo` using `path`, but also `import-foo/bar` and `import-foo/baz` using `patterns`. + Because the patterns follow the `gitignore`-style, if you want to reinclude any particular module this can be done by prefixing a negation (`!`) mark in front of the pattern. (Negated patterns should come last in the array because order is important.) ```json @@ -294,7 +319,7 @@ Because the patterns follow the `gitignore`-style, if you want to reinclude any }] ``` -Examples of **incorrect** code for `pattern` option: +Examples of **incorrect** code for `patterns` option: ::: incorrect { "sourceType": "module" } @@ -328,7 +353,7 @@ import pick from 'import1/private/someModule'; In this example, `"!import1/private/*"` is not reincluding the modules inside `private` because the negation mark (`!`) does not reinclude the files if it's parent directory is excluded by a pattern. In this case, `import1/private` directory is already excluded by the `import1/*` pattern. (The excluded directory can be reincluded using `"!import1/private"`.) -Examples of **correct** code for `pattern` option: +Examples of **correct** code for `patterns` option: ::: correct { "sourceType": "module" } From a76fb55004ea095c68dde134ca7db0212c93c86e Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Thu, 11 Apr 2024 21:21:04 +0200 Subject: [PATCH 078/166] chore: @eslint-community/eslint-plugin-eslint-comments v4.3.0 (#18319) * chore: @eslint-community/eslint-plugin-eslint-comments v4.3.0 * update comment and better sort deps --- packages/eslint-config-eslint/base.js | 18 ++++++------------ packages/eslint-config-eslint/package.json | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/eslint-config-eslint/base.js b/packages/eslint-config-eslint/base.js index 571a18ed55c..65205d8e6b8 100644 --- a/packages/eslint-config-eslint/base.js +++ b/packages/eslint-config-eslint/base.js @@ -2,15 +2,9 @@ const js = require("@eslint/js"); const jsdoc = require("eslint-plugin-jsdoc"); -const eslintComments = require("eslint-plugin-eslint-comments"); +const eslintCommentsPluginConfigs = require("@eslint-community/eslint-plugin-eslint-comments/configs"); const unicorn = require("eslint-plugin-unicorn"); -/* - * the plugins' configs are not updated to support the flat config, - * need to manually update the `plugins` property - */ -eslintComments.configs.recommended.plugins = { "eslint-comments": eslintComments }; - // extends eslint recommended config const jsConfigs = [js.configs.recommended, { rules: { @@ -373,12 +367,12 @@ const unicornConfigs = [{ } }]; -// extends eslint-plugin-eslint-comments's recommended config -const eslintCommentsConfigs = [eslintComments.configs.recommended, { +// extends @eslint-community/eslint-plugin-eslint-comments's recommended config +const eslintCommentsConfigs = [eslintCommentsPluginConfigs.recommended, { rules: { - "eslint-comments/disable-enable-pair": ["error"], - "eslint-comments/no-unused-disable": "error", - "eslint-comments/require-description": "error" + "@eslint-community/eslint-comments/disable-enable-pair": ["error"], + "@eslint-community/eslint-comments/no-unused-disable": "error", + "@eslint-community/eslint-comments/require-description": "error" } }]; diff --git a/packages/eslint-config-eslint/package.json b/packages/eslint-config-eslint/package.json index b267841cb62..a579774481d 100644 --- a/packages/eslint-config-eslint/package.json +++ b/packages/eslint-config-eslint/package.json @@ -29,8 +29,8 @@ "homepage": "https://eslint.org", "bugs": "https://github.com/eslint/eslint/issues/", "dependencies": { + "@eslint-community/eslint-plugin-eslint-comments": "^4.3.0", "@eslint/js": "^8.42.0", - "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-jsdoc": "^46.5.1", "eslint-plugin-n": "^17.2.0", "eslint-plugin-unicorn": "^49.0.0" From 16b6a8b469d2e0ba6d904b9e858711590568b246 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Fri, 12 Apr 2024 08:06:48 +0000 Subject: [PATCH 079/166] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 866d7fbcec1..7f97a76cd9f 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

Automattic

Gold Sponsors

-

Bitwarden Salesforce Airbnb

Silver Sponsors

+

Salesforce Airbnb

Silver Sponsors

JetBrains Liftoff American Express Workleap

Bronze Sponsors

notion Anagram Solver Icons8 Discord Transloadit Ignition Nx HeroCoders Nextbase Starter Kit

From 32c08cf66536e595e93284500b0b8d702e30cfd8 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 12 Apr 2024 12:56:14 +0200 Subject: [PATCH 080/166] chore: drop Node < 18 and use @eslint/js v9 in eslint-config-eslint (#18323) * chore: drop Node < 18 and use @eslint/js v9 in eslint-config-eslint * replace no-new-object with no-object-constructor --- packages/eslint-config-eslint/base.js | 3 +-- packages/eslint-config-eslint/package.json | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/eslint-config-eslint/base.js b/packages/eslint-config-eslint/base.js index 65205d8e6b8..21d5260860b 100644 --- a/packages/eslint-config-eslint/base.js +++ b/packages/eslint-config-eslint/base.js @@ -68,7 +68,6 @@ const jsConfigs = [js.configs.recommended, { "no-caller": "error", "no-confusing-arrow": "error", "no-console": "error", - "no-constant-binary-expression": "error", "no-constructor-return": "error", "no-else-return": ["error", { allowElseIf: false } ], @@ -99,8 +98,8 @@ const jsConfigs = [js.configs.recommended, { "no-nested-ternary": "error", "no-new": "error", "no-new-func": "error", - "no-new-object": "error", "no-new-wrappers": "error", + "no-object-constructor": "error", "no-octal-escape": "error", "no-param-reassign": "error", "no-proto": "error", diff --git a/packages/eslint-config-eslint/package.json b/packages/eslint-config-eslint/package.json index a579774481d..33999006e04 100644 --- a/packages/eslint-config-eslint/package.json +++ b/packages/eslint-config-eslint/package.json @@ -30,7 +30,7 @@ "bugs": "https://github.com/eslint/eslint/issues/", "dependencies": { "@eslint-community/eslint-plugin-eslint-comments": "^4.3.0", - "@eslint/js": "^8.42.0", + "@eslint/js": "^9.0.0", "eslint-plugin-jsdoc": "^46.5.1", "eslint-plugin-n": "^17.2.0", "eslint-plugin-unicorn": "^49.0.0" @@ -42,6 +42,6 @@ ], "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } } From 200fd4e3223d1ad22dca3dc79aa6eaa860fefe32 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 12 Apr 2024 23:09:18 +0200 Subject: [PATCH 081/166] docs: indicate eslintrc mode for `.eslintignore` (#18285) * docs: remove obsolete references to `.eslintignore` * add mention to eslintrc mode * do not print .eslintignore notice in flat config * Match default CLI output in the docs Co-authored-by: Nicholas C. Zakas * Remove trailing dot --------- Co-authored-by: Amaresh S M Co-authored-by: Nicholas C. Zakas --- docs/src/use/command-line-interface.md | 4 ++-- lib/options.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/use/command-line-interface.md b/docs/src/use/command-line-interface.md index 65b504646c8..66bceade810 100644 --- a/docs/src/use/command-line-interface.md +++ b/docs/src/use/command-line-interface.md @@ -86,7 +86,7 @@ Fix Problems: Ignore Files: --no-ignore Disable use of ignore files and patterns - --ignore-pattern [String] Pattern of files to ignore (in addition to those in .eslintignore) + --ignore-pattern [String] Patterns of files to ignore Use stdin: --stdin Lint code provided on - default: false @@ -426,7 +426,7 @@ npx eslint --no-ignore file.js #### `--ignore-pattern` -This option allows you to specify patterns of files to ignore (in addition to those in `.eslintignore`). +This option allows you to specify patterns of files to ignore. In eslintrc mode, these are in addition to `.eslintignore`. * **Argument Type**: String. The supported syntax is the same as for [`.eslintignore` files](configure/ignore-deprecated#the-eslintignore-file), which use the same patterns as the [`.gitignore` specification](https://git-scm.com/docs/gitignore). You should quote your patterns in order to avoid shell interpretation of glob patterns. * **Multiple Arguments**: Yes diff --git a/lib/options.js b/lib/options.js index c8c3f8e4f70..5515ec60837 100644 --- a/lib/options.js +++ b/lib/options.js @@ -38,7 +38,7 @@ const optionator = require("optionator"); * @property {boolean} [help] Show help * @property {boolean} ignore Disable use of ignore files and patterns * @property {string} [ignorePath] Specify path of ignore file - * @property {string[]} [ignorePattern] Pattern of files to ignore (in addition to those in .eslintignore) + * @property {string[]} [ignorePattern] Patterns of files to ignore. In eslintrc mode, these are in addition to `.eslintignore` * @property {boolean} init Run config initialization wizard * @property {boolean} inlineConfig Prevent comments from changing config or rules * @property {number} maxWarnings Number of warnings to trigger nonzero exit code @@ -261,7 +261,7 @@ module.exports = function(usingFlatConfig) { { option: "ignore-pattern", type: "[String]", - description: "Pattern of files to ignore (in addition to those in .eslintignore)", + description: `Patterns of files to ignore${usingFlatConfig ? "" : " (in addition to those in .eslintignore)"}`, concatRepeatedArrays: [true, { oneValuePerFlag: true }] From 0db676f9c64d2622ada86b653136d2bda4f0eee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Sat, 13 Apr 2024 22:09:33 +0800 Subject: [PATCH 082/166] feat: add `Intl` in es6 globals (#18318) fix: add `Intl` in es6 globals see the discussion: https://github.com/sindresorhus/globals/pull/213#issue-2142294092 --- conf/globals.js | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/globals.js b/conf/globals.js index 58710e05bc6..c356adb1714 100644 --- a/conf/globals.js +++ b/conf/globals.js @@ -70,6 +70,7 @@ const es2015 = { Int16Array: false, Int32Array: false, Int8Array: false, + Intl: false, Map: false, Promise: false, Proxy: false, From 71c771fb390cf178220d06fd7316033a385128a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Freixin=C3=B3s?= <14282156+germanfrelo@users.noreply.github.com> Date: Sat, 13 Apr 2024 16:51:43 +0200 Subject: [PATCH 083/166] docs: Fix missing accessible name for scroll-to-top link (#18329) More info: https://www.sarasoueidan.com/blog/accessible-icon-buttons/#technique-%233%3A-using-aria-label --- docs/src/_includes/layouts/doc.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/_includes/layouts/doc.html b/docs/src/_includes/layouts/doc.html index 92db212806e..f17bd15873e 100644 --- a/docs/src/_includes/layouts/doc.html +++ b/docs/src/_includes/layouts/doc.html @@ -119,7 +119,7 @@

{{ title }}

- + From 698d9ff2c9c4e24836d69358b93d42c356eb853b Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sat, 13 Apr 2024 19:16:50 +0200 Subject: [PATCH 084/166] chore: upgrade jsdoc & unicorn plugins in eslint-config-eslint (#18333) --- packages/eslint-config-eslint/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-config-eslint/package.json b/packages/eslint-config-eslint/package.json index 33999006e04..d5c7d970879 100644 --- a/packages/eslint-config-eslint/package.json +++ b/packages/eslint-config-eslint/package.json @@ -31,9 +31,9 @@ "dependencies": { "@eslint-community/eslint-plugin-eslint-comments": "^4.3.0", "@eslint/js": "^9.0.0", - "eslint-plugin-jsdoc": "^46.5.1", + "eslint-plugin-jsdoc": "^48.2.3", "eslint-plugin-n": "^17.2.0", - "eslint-plugin-unicorn": "^49.0.0" + "eslint-plugin-unicorn": "^52.0.0" }, "keywords": [ "eslintconfig", From 48207908a8291916a124af60e02d0327276f8957 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sat, 13 Apr 2024 22:06:55 +0200 Subject: [PATCH 085/166] chore: upgrade globals@15.0.0 dev dependency (#18332) * chore: upgrade globals@15.0.0 dev dependency * remove global directive for Intl from no-obj-calls docs --- docs/src/rules/no-obj-calls.md | 2 -- package.json | 2 +- tests/lib/rules/no-obj-calls.js | 22 +++++++++++----------- tests/lib/rules/no-undef.js | 3 ++- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/src/rules/no-obj-calls.md b/docs/src/rules/no-obj-calls.md index 56e9f914153..2ab8d27f8b7 100644 --- a/docs/src/rules/no-obj-calls.md +++ b/docs/src/rules/no-obj-calls.md @@ -38,7 +38,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-obj-calls: "error"*/ -/*global Intl*/ var math = Math(); @@ -69,7 +68,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-obj-calls: "error"*/ -/*global Intl*/ function area(r) { return Math.PI * r * r; diff --git a/package.json b/package.json index acd53912281..ba0f59f0c28 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "fast-glob": "^3.2.11", "fs-teardown": "^0.1.3", "glob": "^10.0.0", - "globals": "^14.0.0", + "globals": "^15.0.0", "got": "^11.8.3", "gray-matter": "^4.0.3", "js-yaml": "^4.1.0", diff --git a/tests/lib/rules/no-obj-calls.js b/tests/lib/rules/no-obj-calls.js index a69417f98d3..d263daded09 100644 --- a/tests/lib/rules/no-obj-calls.js +++ b/tests/lib/rules/no-obj-calls.js @@ -53,11 +53,11 @@ ruleTester.run("no-obj-calls", rule, { }, { code: "new Intl.Segmenter()", - languageOptions: { globals: globals.browser } + languageOptions: { ecmaVersion: 2015 } }, { code: "Intl.foo()", - languageOptions: { globals: globals.browser } + languageOptions: { ecmaVersion: 2015 } }, { code: "globalThis.Math();", languageOptions: { ecmaVersion: 6 } }, @@ -72,7 +72,7 @@ ruleTester.run("no-obj-calls", rule, { { code: "/*globals Reflect: true*/ globalThis.Reflect();", languageOptions: { ecmaVersion: 2017 } }, { code: "var x = globalThis.Atomics();", languageOptions: { ecmaVersion: 2017 } }, { code: "var x = globalThis.Atomics();", languageOptions: { ecmaVersion: 2017, globals: { Atomics: false } } }, - { code: "var x = globalThis.Intl();", languageOptions: { globals: globals.browser } }, + { code: "var x = globalThis.Intl();", languageOptions: { ecmaVersion: 2015 } }, // non-existing variables "/*globals Math: off*/ Math();", @@ -140,15 +140,15 @@ ruleTester.run("no-obj-calls", rule, { }, { code: "function foo(Intl) { Intl(); }", - languageOptions: { globals: globals.browser } + languageOptions: { ecmaVersion: 2015 } }, { code: "if (foo) { const Intl = 1; Intl(); }", - languageOptions: { ecmaVersion: 2015, globals: globals.browser } + languageOptions: { ecmaVersion: 2015 } }, { code: "if (foo) { const Intl = 1; new Intl(); }", - languageOptions: { ecmaVersion: 2015, globals: globals.browser } + languageOptions: { ecmaVersion: 2015 } } ], invalid: [ @@ -257,12 +257,12 @@ ruleTester.run("no-obj-calls", rule, { }, { code: "var x = Intl();", - languageOptions: { globals: globals.browser }, + languageOptions: { ecmaVersion: 2015 }, errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "CallExpression" }] }, { code: "var x = new Intl();", - languageOptions: { globals: globals.browser }, + languageOptions: { ecmaVersion: 2015 }, errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "NewExpression" }] }, { @@ -338,17 +338,17 @@ ruleTester.run("no-obj-calls", rule, { }, { code: "var x = globalThis.Intl();", - languageOptions: { globals: globals.browser, ecmaVersion: 2020 }, + languageOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "CallExpression" }] }, { code: "var x = new globalThis.Intl;", - languageOptions: { globals: globals.browser, ecmaVersion: 2020 }, + languageOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "NewExpression" }] }, { code: "/*globals Intl: true*/ Intl();", - languageOptions: { globals: globals.browser, ecmaVersion: 2020 }, + languageOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "unexpectedCall", data: { name: "Intl" }, type: "CallExpression" }] }, { diff --git a/tests/lib/rules/no-undef.js b/tests/lib/rules/no-undef.js index 6f5b90d06b6..158dfa894ac 100644 --- a/tests/lib/rules/no-undef.js +++ b/tests/lib/rules/no-undef.js @@ -65,7 +65,7 @@ ruleTester.run("no-undef", rule, { { code: "var a; ({b: a} = {});", languageOptions: { ecmaVersion: 6 } }, { code: "var obj; [obj.a, obj.b] = [0, 1];", languageOptions: { ecmaVersion: 6 } }, { code: "URLSearchParams;", languageOptions: { globals: globals.browser } }, - { code: "Intl;", languageOptions: { globals: globals.browser } }, + { code: "Intl;", languageOptions: { ecmaVersion: 2015 } }, { code: "IntersectionObserver;", languageOptions: { globals: globals.browser } }, { code: "Credential;", languageOptions: { globals: globals.browser } }, { code: "requestIdleCallback;", languageOptions: { globals: globals.browser } }, @@ -173,6 +173,7 @@ ruleTester.run("no-undef", rule, { { code: "var a = b;", errors: [{ messageId: "undef", data: { name: "b" }, type: "Identifier" }] }, { code: "function f() { b; }", errors: [{ messageId: "undef", data: { name: "b" }, type: "Identifier" }] }, { code: "window;", errors: [{ messageId: "undef", data: { name: "window" }, type: "Identifier" }] }, + { code: "Intl;", errors: [{ messageId: "undef", data: { name: "Intl" }, type: "Identifier" }] }, { code: "require(\"a\");", errors: [{ messageId: "undef", data: { name: "require" }, type: "Identifier" }] }, { code: "var React; React.render();", languageOptions: { ecmaVersion: 6, parserOptions: { ecmaFeatures: { jsx: true } } }, errors: [{ messageId: "undef", data: { name: "a" } }] }, { code: "var React, App; React.render();", languageOptions: { ecmaVersion: 6, parserOptions: { ecmaFeatures: { jsx: true } } }, errors: [{ messageId: "undef", data: { name: "a" } }] }, From 9048e2184c19799bb9b8a5908345d4ce05020c41 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sun, 14 Apr 2024 18:32:29 +0200 Subject: [PATCH 086/166] chore: lint `docs/src/_data` js files (#18335) --- docs/src/_data/eslintVersions.js | 10 ++++++---- docs/src/_data/helpers.js | 23 ++++++++++++++--------- docs/src/_data/layout.js | 2 ++ docs/src/_data/rules_categories.js | 4 +++- docs/src/_data/site.js | 6 ++++-- eslint.config.js | 5 ++--- 6 files changed, 31 insertions(+), 19 deletions(-) diff --git a/docs/src/_data/eslintVersions.js b/docs/src/_data/eslintVersions.js index 2fc2b981828..16e6235df0e 100644 --- a/docs/src/_data/eslintVersions.js +++ b/docs/src/_data/eslintVersions.js @@ -3,11 +3,13 @@ * @author Milos Djermanovic */ +"use strict"; + //----------------------------------------------------------------------------- // Requirements //----------------------------------------------------------------------------- -const eleventyFetch = require("@11ty/eleventy-fetch"); +const eleventyFetch = require("@11ty/eleventy-fetch"); //----------------------------------------------------------------------------- // Exports @@ -35,7 +37,7 @@ module.exports = async function() { foundItemForThisBranch ||= isItemForThisBranch; - const isNumberVersion = /^\d/.test(item.version); // `false` for HEAD + const isNumberVersion = /^\d/u.test(item.version); // `false` for HEAD if (isNumberVersion) { @@ -62,8 +64,8 @@ module.exports = async function() { display: "", path: "", selected: true - }) + }); } return data; -} +}; diff --git a/docs/src/_data/helpers.js b/docs/src/_data/helpers.js index a7c4ef6fab7..b4307e841ea 100644 --- a/docs/src/_data/helpers.js +++ b/docs/src/_data/helpers.js @@ -1,14 +1,16 @@ +"use strict"; + module.exports = { + /** * Returns some attributes based on whether the link is active or * a parent of an active item - * - * @param {String} itemUrl is the link in question - * @param {String} pageUrl is the page context - * @returns {String} is the attributes or empty + * @param {string} itemUrl is the link in question + * @param {string} pageUrl is the page context + * @returns {string} is the attributes or empty */ - getLinkActiveState: function(itemUrl, pageUrl) { - let response = ''; + getLinkActiveState(itemUrl, pageUrl) { + let response = ""; if (itemUrl === pageUrl) { response = ' aria-current="page" '; @@ -20,10 +22,13 @@ module.exports = { return response; }, - excludeThis: function(arr, pageUrl) { - var newArray = []; + excludeThis(arr, pageUrl) { + const newArray = []; + arr.forEach(item => { - if(item.url !== pageUrl) newArray.push(item); + if (item.url !== pageUrl) { + newArray.push(item); + } }); return newArray; } diff --git a/docs/src/_data/layout.js b/docs/src/_data/layout.js index 2665a708914..9114836a936 100644 --- a/docs/src/_data/layout.js +++ b/docs/src/_data/layout.js @@ -1 +1,3 @@ +"use strict"; + module.exports = "doc.html"; diff --git a/docs/src/_data/rules_categories.js b/docs/src/_data/rules_categories.js index 46856958f22..ebd4883d0b6 100644 --- a/docs/src/_data/rules_categories.js +++ b/docs/src/_data/rules_categories.js @@ -1,3 +1,5 @@ +"use strict"; + module.exports = eleventy => { const PATH_PREFIX = eleventy.PATH_PREFIX; @@ -22,5 +24,5 @@ module.exports = eleventy => { displayName: "Removed", description: `These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:` } - } + }; }; diff --git a/docs/src/_data/site.js b/docs/src/_data/site.js index d0792694f48..62f17da93dd 100644 --- a/docs/src/_data/site.js +++ b/docs/src/_data/site.js @@ -3,6 +3,8 @@ * @author Nicholas C. Zakas */ +"use strict"; + //----------------------------------------------------------------------------- // Requirements //----------------------------------------------------------------------------- @@ -16,11 +18,11 @@ const yaml = require("js-yaml"); //----------------------------------------------------------------------------- module.exports = function(eleventy) { - + const siteName = eleventy.site_name; const siteDataFile = path.resolve(__dirname, `sites/${siteName}.yml`); fs.statSync(siteDataFile); return yaml.load(fs.readFileSync(siteDataFile)); -} +}; diff --git a/eslint.config.js b/eslint.config.js index 5d332e6edfc..e51829b602b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -79,9 +79,8 @@ module.exports = [ ignores: [ "build/**", "coverage/**", - "docs/*", - "!docs/*.js", - "!docs/tools/", + "docs/!(src|tools)/", + "docs/src/!(_data)", "jsdoc/**", "templates/**", "tests/bench/**", From 09675e153169d4d0f4a85a95007dcd17d34d70c7 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 15 Apr 2024 15:11:08 +0200 Subject: [PATCH 087/166] fix: `--no-ignore` should not apply to non-global ignores (#18334) Fixes #18261 --- lib/config/flat-config-array.js | 13 +++-- tests/fixtures/eslint.config-with-ignores3.js | 9 +++ tests/lib/eslint/eslint.js | 57 +++++++++++++++++++ 3 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/eslint.config-with-ignores3.js diff --git a/lib/config/flat-config-array.js b/lib/config/flat-config-array.js index 6103374dcd6..e09fdcef0e5 100644 --- a/lib/config/flat-config-array.js +++ b/lib/config/flat-config-array.js @@ -18,6 +18,11 @@ const { defaultConfig } = require("./default-config"); // Helpers //----------------------------------------------------------------------------- +/** + * Fields that are considered metadata and not part of the config object. + */ +const META_FIELDS = new Set(["name"]); + const ruleValidator = new RuleValidator(); /** @@ -135,15 +140,15 @@ class FlatConfigArray extends ConfigArray { [ConfigArraySymbol.preprocessConfig](config) { /* - * If `shouldIgnore` is false, we remove any ignore patterns specified - * in the config so long as it's not a default config and it doesn't - * have a `files` entry. + * If a config object has `ignores` and no other non-meta fields, then it's an object + * for global ignores. If `shouldIgnore` is false, that object shouldn't apply, + * so we'll remove its `ignores`. */ if ( !this.shouldIgnore && !this[originalBaseConfig].includes(config) && config.ignores && - !config.files + Object.keys(config).filter(key => !META_FIELDS.has(key)).length === 1 ) { /* eslint-disable-next-line no-unused-vars -- need to strip off other keys */ const { ignores, ...otherKeys } = config; diff --git a/tests/fixtures/eslint.config-with-ignores3.js b/tests/fixtures/eslint.config-with-ignores3.js new file mode 100644 index 00000000000..ed82eee5cd5 --- /dev/null +++ b/tests/fixtures/eslint.config-with-ignores3.js @@ -0,0 +1,9 @@ +const eslintConfig = require("./eslint.config.js"); + +module.exports = [ + eslintConfig, + { + name: "Global ignores", + ignores: ["**/*.json", "**/*.js"] + } +]; diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 5f1d40acaaa..66ea4d90c62 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -1630,6 +1630,16 @@ describe("ESLint", () => { }, /All files matched by 'tests\/fixtures\/cli-engine\/' are ignored\./u); }); + it("should throw an error when all given files are ignored by a config object that has `name`", async () => { + eslint = new ESLint({ + overrideConfigFile: getFixturePath("eslint.config-with-ignores3.js") + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["tests/fixtures/cli-engine/"]); + }, /All files matched by 'tests\/fixtures\/cli-engine\/' are ignored\./u); + }); + it("should throw an error when all given files are ignored even with a `./` prefix", async () => { eslint = new ESLint({ overrideConfigFile: getFixturePath("eslint.config-with-ignores.js") @@ -1775,6 +1785,29 @@ describe("ESLint", () => { assert.strictEqual(results[0].suppressedMessages.length, 0); }); + it("should return two messages when given a file in excluded files list by a config object that has `name` while ignore is off", async () => { + eslint = new ESLint({ + cwd: getFixturePath(), + ignore: false, + overrideConfigFile: getFixturePath("eslint.config-with-ignores3.js"), + overrideConfig: { + rules: { + "no-undef": 2 + } + } + }); + const filePath = fs.realpathSync(getFixturePath("undef.js")); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[1].severity, 2); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + // https://github.com/eslint/eslint/issues/16300 it("should process ignore patterns relative to basePath not cwd", async () => { eslint = new ESLint({ @@ -5698,6 +5731,30 @@ describe("ESLint", () => { assert.strictEqual(warnResult.messages[0].ruleId, "no-unused-vars"); assert.strictEqual(warnResult.messages[0].severity, 1); }); + + // https://github.com/eslint/eslint/issues/18261 + it("should apply to all files except for 'error.js' even with `ignore: false` option", async () => { + const engine = new ESLint({ + cwd, + ignore: false + }); + + const results = await engine.lintFiles("{error,warn}.js"); + + assert.strictEqual(results.length, 2); + + const [errorResult, warnResult] = results; + + assert.strictEqual(errorResult.filePath, path.join(getPath(), "error.js")); + assert.strictEqual(errorResult.messages.length, 1); + assert.strictEqual(errorResult.messages[0].ruleId, "no-unused-vars"); + assert.strictEqual(errorResult.messages[0].severity, 2); + + assert.strictEqual(warnResult.filePath, path.join(getPath(), "warn.js")); + assert.strictEqual(warnResult.messages.length, 1); + assert.strictEqual(warnResult.messages[0].ruleId, "no-unused-vars"); + assert.strictEqual(warnResult.messages[0].severity, 1); + }); }); describe("config with ignores: ['**/*.json']", () => { From e1ac0b5c035bfdff7be08b69e89e1470a7becac3 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 15 Apr 2024 19:11:31 -0700 Subject: [PATCH 088/166] fix: --inspect-config only for flat config and respect -c (#18306) * fix: --inspect-config only for flat config and respect -c * Remove unnecessary flat config check --- bin/eslint.js | 12 - lib/cli.js | 41 +- lib/eslint/eslint.js | 3 +- tests/lib/cli.js | 2612 +++++++++++++++++++++--------------------- 4 files changed, 1373 insertions(+), 1295 deletions(-) diff --git a/bin/eslint.js b/bin/eslint.js index b6190f66742..46ca98738b2 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -148,18 +148,6 @@ ${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 cli = require("../lib/cli"); const exitCode = await cli.execute( diff --git a/lib/cli.js b/lib/cli.js index 24d72d6e21a..4a60d457a72 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -19,7 +19,7 @@ const fs = require("fs"), path = require("path"), { promisify } = require("util"), { LegacyESLint } = require("./eslint"), - { ESLint, shouldUseFlatConfig } = require("./eslint/eslint"), + { ESLint, shouldUseFlatConfig, locateConfigFileToUse } = require("./eslint/eslint"), createCLIOptions = require("./options"), log = require("./shared/logging"), RuntimeInfo = require("./shared/runtime-info"), @@ -336,6 +336,27 @@ async function printResults(engine, results, format, outputFile, resultsMeta) { */ const cli = { + /** + * Calculates the command string for the --inspect-config operation. + * @param {string} configFile The path to the config file to inspect. + * @returns {Promise} The command string to execute. + */ + async calculateInspectConfigFlags(configFile) { + + // find the config file + const { + configFilePath, + basePath, + error + } = await locateConfigFileToUse({ cwd: process.cwd(), configFile }); + + if (error) { + throw error; + } + + return ["--config", configFilePath, "--basePath", basePath]; + }, + /** * Executes the CLI based on an array of arguments that is passed in. * @param {string|Array|Object} args The arguments to process. @@ -425,6 +446,24 @@ const cli = { return 0; } + if (options.inspectConfig) { + + log.info("You can also run this command directly using 'npx @eslint/config-inspector' in the same directory as your configuration file."); + + try { + const flatOptions = await translateOptions(options, "flat"); + const spawn = require("cross-spawn"); + const flags = await cli.calculateInspectConfigFlags(flatOptions.overrideConfigFile); + + spawn.sync("npx", ["@eslint/config-inspector", ...flags], { encoding: "utf8", stdio: "inherit" }); + } catch (error) { + log.error(error); + return 2; + } + + return 0; + } + debug(`Running on ${useStdin ? "text" : "files"}`); if (options.fix && options.fixDryRun) { diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index b4c38503a6e..4f2c1f3a86c 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -1214,5 +1214,6 @@ async function shouldUseFlatConfig() { module.exports = { ESLint, - shouldUseFlatConfig + shouldUseFlatConfig, + locateConfigFileToUse }; diff --git a/tests/lib/cli.js b/tests/lib/cli.js index ce127d542e6..01aaebcec7e 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -31,322 +31,226 @@ const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); //------------------------------------------------------------------------------ describe("cli", () => { - let fixtureDir; - const log = { - info: sinon.spy(), - error: sinon.spy() - }; - const RuntimeInfo = { - environment: sinon.stub(), - version: sinon.stub() - }; - const cli = proxyquire("../../lib/cli", { - "./shared/logging": log, - "./shared/runtime-info": RuntimeInfo - }); - - /** - * Verify that ESLint class receives correct opts via await cli.execute(). - * @param {string} cmd CLI command. - * @param {Object} opts Options hash that should match that received by ESLint class. - * @param {string} configType The config type to work with. - * @returns {void} - */ - async function verifyESLintOpts(cmd, opts, configType) { - const ActiveESLint = configType === "flat" ? ESLint : LegacyESLint; + describe("calculateInspectConfigFlags()", () => { - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match(opts)); + const cli = require("../../lib/cli"); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: sinon.spy() }); - - const localCLI = proxyquire("../../lib/cli", { - "./eslint": { LegacyESLint: fakeESLint }, - "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(configType === "flat") }, - "./shared/logging": log - }); - - await localCLI.execute(cmd, null, configType === "flat"); - sinon.verifyAndRestore(); - } - - // verifyESLintOpts - - /** - * Returns the path inside of the fixture directory. - * @param {...string} args file path segments. - * @returns {string} The path inside the fixture directory. - * @private - */ - function getFixturePath(...args) { - return path.join(fixtureDir, ...args); - } - - // copy into clean area so as not to get "infected" by this project's .eslintrc files - before(function() { - - /* - * GitHub Actions Windows and macOS runners occasionally exhibit - * extremely slow filesystem operations, during which copying fixtures - * exceeds the default test timeout, so raise it just for this hook. - * Mocha uses `this` to set timeouts on an individual hook level. - */ - this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API - fixtureDir = `${os.tmpdir()}/eslint/fixtures`; - sh.mkdir("-p", fixtureDir); - sh.cp("-r", "./tests/fixtures/.", fixtureDir); - }); - - beforeEach(() => { - sinon.stub(process, "emitWarning").withArgs(sinon.match.any, "ESLintIgnoreWarning").returns(); - process.emitWarning.callThrough(); - }); - - afterEach(() => { - sinon.restore(); - log.info.resetHistory(); - log.error.resetHistory(); - }); - - after(() => { - sh.rm("-r", fixtureDir); - }); + it("should return the config file in the project root when no argument is passed", async () => { - ["eslintrc", "flat"].forEach(configType => { + const flags = await cli.calculateInspectConfigFlags(); - const useFlatConfig = configType === "flat"; - const ActiveESLint = configType === "flat" ? ESLint : LegacyESLint; + assert.deepStrictEqual(flags, [ + "--config", + path.resolve(process.cwd(), "eslint.config.js"), + "--basePath", + process.cwd() + ]); - describe("execute()", () => { - - it(`should return error when text with incorrect quotes is passed as argument with configType:${configType}`, async () => { - const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; - const configFile = getFixturePath("configurations", "quotes-error.js"); - const result = await cli.execute(`${flag} -c ${configFile} --stdin --stdin-filename foo.js`, "var foo = 'bar';", useFlatConfig); - - assert.strictEqual(result, 1); - }); - - it(`should not print debug info when passed the empty string as text with configType:${configType}`, async () => { - const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; - const result = await cli.execute(["argv0", "argv1", "--stdin", flag, "--stdin-filename", "foo.js"], "", useFlatConfig); + }); - assert.strictEqual(result, 0); - assert.isTrue(log.info.notCalled); - }); + it("should return the override config file when an argument is passed", async () => { - it(`should exit with console error when passed unsupported arguments with configType:${configType}`, async () => { - const filePath = getFixturePath("files"); - const result = await cli.execute(`--blah --another ${filePath}`, null, useFlatConfig); + const flags = await cli.calculateInspectConfigFlags("foo.js"); - assert.strictEqual(result, 2); - }); + assert.deepStrictEqual(flags, [ + "--config", + path.resolve(process.cwd(), "foo.js"), + "--basePath", + process.cwd() + ]); }); - describe("flat config", () => { - const originalEnv = process.env; - const originalCwd = process.cwd; + it("should return the override config file when an argument is passed with a path", async () => { - let processStub; + const flags = await cli.calculateInspectConfigFlags("bar/foo.js"); - beforeEach(() => { - sinon.restore(); - processStub = sinon.stub(process, "emitWarning"); - process.env = { ...originalEnv }; - }); - - afterEach(() => { - processStub.restore(); - process.env = originalEnv; - process.cwd = originalCwd; - }); + assert.deepStrictEqual(flags, [ + "--config", + path.resolve(process.cwd(), "bar/foo.js"), + "--basePath", + process.cwd() + ]); - it(`should use it when an eslint.config.js is present and useFlatConfig is true:${configType}`, async () => { - process.cwd = getFixturePath; + }); - const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); + }); - // When flat config is used, we get an exit code of 2 because the --ext option is unrecognized. - assert.strictEqual(exitCode, useFlatConfig ? 2 : 0); - }); + describe("execute()", () => { + + let fixtureDir; + const log = { + info: sinon.spy(), + error: sinon.spy() + }; + const RuntimeInfo = { + environment: sinon.stub(), + version: sinon.stub() + }; + const cli = proxyquire("../../lib/cli", { + "./shared/logging": log, + "./shared/runtime-info": RuntimeInfo + }); - it(`should not use it when ESLINT_USE_FLAT_CONFIG=false even if an eslint.config.js is present:${configType}`, async () => { - process.env.ESLINT_USE_FLAT_CONFIG = "false"; - process.cwd = getFixturePath; + /** + * Verify that ESLint class receives correct opts via await cli.execute(). + * @param {string} cmd CLI command. + * @param {Object} opts Options hash that should match that received by ESLint class. + * @param {string} configType The config type to work with. + * @returns {void} + */ + async function verifyESLintOpts(cmd, opts, configType) { - const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); + const ActiveESLint = configType === "flat" ? ESLint : LegacyESLint; - assert.strictEqual(exitCode, 0); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match(opts)); + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: sinon.spy() }); - if (useFlatConfig) { - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.strictEqual(processStub.getCall(0).args[1], "ESLintRCWarning"); - } + const localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(configType === "flat") }, + "./shared/logging": log }); - it(`should use it when ESLINT_USE_FLAT_CONFIG=true and useFlatConfig is true even if an eslint.config.js is not present:${configType}`, async () => { - process.env.ESLINT_USE_FLAT_CONFIG = "true"; + await localCLI.execute(cmd, null, configType === "flat"); + sinon.verifyAndRestore(); + } - // Set the CWD to outside the fixtures/ directory so that no eslint.config.js is found - process.cwd = () => getFixturePath(".."); + // verifyESLintOpts - const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); - - // When flat config is used, we get an exit code of 2 because the --ext option is unrecognized. - assert.strictEqual(exitCode, useFlatConfig ? 2 : 0); - }); + /** + * Returns the path inside of the fixture directory. + * @param {...string} args file path segments. + * @returns {string} The path inside the fixture directory. + * @private + */ + function getFixturePath(...args) { + return path.join(fixtureDir, ...args); + } + + // copy into clean area so as not to get "infected" by this project's .eslintrc files + before(function() { + + /* + * GitHub Actions Windows and macOS runners occasionally exhibit + * extremely slow filesystem operations, during which copying fixtures + * exceeds the default test timeout, so raise it just for this hook. + * Mocha uses `this` to set timeouts on an individual hook level. + */ + this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API + fixtureDir = `${os.tmpdir()}/eslint/fixtures`; + sh.mkdir("-p", fixtureDir); + sh.cp("-r", "./tests/fixtures/.", fixtureDir); }); - describe("when given a config with rules with options and severity level set to error", () => { - - const originalCwd = process.cwd; - - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); - - afterEach(() => { - process.cwd = originalCwd; - }); - - it(`should exit with an error status (1) with configType:${configType}`, async () => { - const configPath = getFixturePath("configurations", "quotes-error.js"); - const filePath = getFixturePath("single-quoted.js"); - const code = `--no-ignore --config ${configPath} ${filePath}`; - - const exitStatus = await cli.execute(code, null, useFlatConfig); - - assert.strictEqual(exitStatus, 1); - }); + beforeEach(() => { + sinon.stub(process, "emitWarning").withArgs(sinon.match.any, "ESLintIgnoreWarning").returns(); + process.emitWarning.callThrough(); }); - describe("when there is a local config file", () => { + afterEach(() => { + sinon.restore(); + log.info.resetHistory(); + log.error.resetHistory(); + }); - const originalCwd = process.cwd; + after(() => { + sh.rm("-r", fixtureDir); + }); - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); + ["eslintrc", "flat"].forEach(configType => { - afterEach(() => { - process.cwd = originalCwd; - }); + const useFlatConfig = configType === "flat"; + const ActiveESLint = configType === "flat" ? ESLint : LegacyESLint; - it(`should load the local config file with configType:${configType}`, async () => { - await cli.execute("cli/passing.js --no-ignore", null, useFlatConfig); - }); + describe("execute()", () => { - if (useFlatConfig) { - it(`should load the local config file with glob pattern and configType:${configType}`, async () => { - await cli.execute("cli/pass*.js --no-ignore", null, useFlatConfig); - }); - } + it(`should return error when text with incorrect quotes is passed as argument with configType:${configType}`, async () => { + const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; + const configFile = getFixturePath("configurations", "quotes-error.js"); + const result = await cli.execute(`${flag} -c ${configFile} --stdin --stdin-filename foo.js`, "var foo = 'bar';", useFlatConfig); - // only works on Windows - if (os.platform() === "win32") { - it(`should load the local config file with Windows slashes glob pattern and configType:${configType}`, async () => { - await cli.execute("cli\\pass*.js --no-ignore", null, useFlatConfig); + assert.strictEqual(result, 1); }); - } - }); - describe("Formatters", () => { + it(`should not print debug info when passed the empty string as text with configType:${configType}`, async () => { + const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; + const result = await cli.execute(["argv0", "argv1", "--stdin", flag, "--stdin-filename", "foo.js"], "", useFlatConfig); - const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; + assert.strictEqual(result, 0); + assert.isTrue(log.info.notCalled); + }); - describe("when given a valid built-in formatter name", () => { - it(`should execute without any errors with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`${flag} -f json ${filePath}`, null, useFlatConfig); + it(`should exit with console error when passed unsupported arguments with configType:${configType}`, async () => { + const filePath = getFixturePath("files"); + const result = await cli.execute(`--blah --another ${filePath}`, null, useFlatConfig); - assert.strictEqual(exit, 0); + assert.strictEqual(result, 2); }); - }); - describe("when given a valid built-in formatter name that uses rules meta.", () => { + }); + describe("flat config", () => { + const originalEnv = process.env; const originalCwd = process.cwd; + let processStub; + beforeEach(() => { - process.cwd = () => getFixturePath(); + sinon.restore(); + processStub = sinon.stub(process, "emitWarning"); + process.env = { ...originalEnv }; }); afterEach(() => { + processStub.restore(); + process.env = originalEnv; process.cwd = originalCwd; }); - it(`should execute without any errors with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`--no-ignore -f json-with-metadata ${filePath} ${flag}`, null, useFlatConfig); - - assert.strictEqual(exit, 0); + it(`should use it when an eslint.config.js is present and useFlatConfig is true:${configType}`, async () => { + process.cwd = getFixturePath; - /* - * Note: There is a behavior difference between eslintrc and flat config - * when using formatters. For eslintrc, rulesMeta always contains every - * rule that was loaded during the last run; for flat config, rulesMeta - * only contains meta data for the rules that triggered messages in the - * results. (Flat config uses ESLint#getRulesMetaForResults().) - */ - - // Check metadata. - const { metadata } = JSON.parse(log.info.args[0][0]); - const expectedMetadata = { - cwd: process.cwd(), - rulesMeta: useFlatConfig ? {} : Array.from(BuiltinRules).reduce((obj, [ruleId, rule]) => { - obj[ruleId] = rule.meta; - return obj; - }, {}) - }; + const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); - assert.deepStrictEqual(metadata, expectedMetadata); + // When flat config is used, we get an exit code of 2 because the --ext option is unrecognized. + assert.strictEqual(exitCode, useFlatConfig ? 2 : 0); }); - }); - describe("when the --max-warnings option is passed", () => { + it(`should not use it when ESLINT_USE_FLAT_CONFIG=false even if an eslint.config.js is present:${configType}`, async () => { + process.env.ESLINT_USE_FLAT_CONFIG = "false"; + process.cwd = getFixturePath; - describe("and there are too many warnings", () => { - it(`should provide \`maxWarningsExceeded\` metadata to the formatter with configType:${configType}`, async () => { - const exit = await cli.execute( - `--no-ignore -f json-with-metadata --max-warnings 1 --rule 'quotes: warn' ${flag}`, - "'hello' + 'world';", - useFlatConfig - ); + const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); - assert.strictEqual(exit, 1); + assert.strictEqual(exitCode, 0); - const { metadata } = JSON.parse(log.info.args[0][0]); - assert.deepStrictEqual( - metadata.maxWarningsExceeded, - { maxWarnings: 1, foundWarnings: 2 } - ); - }); + if (useFlatConfig) { + assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); + assert.strictEqual(processStub.getCall(0).args[1], "ESLintRCWarning"); + } }); - describe("and warnings do not exceed the limit", () => { - it(`should omit \`maxWarningsExceeded\` metadata from the formatter with configType:${configType}`, async () => { - const exit = await cli.execute( - `--no-ignore -f json-with-metadata --max-warnings 1 --rule 'quotes: warn' ${flag}`, - "'hello world';", - useFlatConfig - ); + it(`should use it when ESLINT_USE_FLAT_CONFIG=true and useFlatConfig is true even if an eslint.config.js is not present:${configType}`, async () => { + process.env.ESLINT_USE_FLAT_CONFIG = "true"; - assert.strictEqual(exit, 0); + // Set the CWD to outside the fixtures/ directory so that no eslint.config.js is found + process.cwd = () => getFixturePath(".."); - const { metadata } = JSON.parse(log.info.args[0][0]); + const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); - assert.notProperty(metadata, "maxWarningsExceeded"); - }); + // When flat config is used, we get an exit code of 2 because the --ext option is unrecognized. + assert.strictEqual(exitCode, useFlatConfig ? 2 : 0); }); }); - describe("when given an invalid built-in formatter name", () => { + describe("when given a config with rules with options and severity level set to error", () => { const originalCwd = process.cwd; @@ -358,15 +262,18 @@ describe("cli", () => { process.cwd = originalCwd; }); - it(`should execute with error: with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`-f fakeformatter ${filePath} ${flag}`, null, useFlatConfig); + it(`should exit with an error status (1) with configType:${configType}`, async () => { + const configPath = getFixturePath("configurations", "quotes-error.js"); + const filePath = getFixturePath("single-quoted.js"); + const code = `--no-ignore --config ${configPath} ${filePath}`; - assert.strictEqual(exit, 2); + const exitStatus = await cli.execute(code, null, useFlatConfig); + + assert.strictEqual(exitStatus, 1); }); }); - describe("when given a valid formatter path", () => { + describe("when there is a local config file", () => { const originalCwd = process.cwd; @@ -378,1482 +285,1625 @@ describe("cli", () => { process.cwd = originalCwd; }); - it(`should execute without any errors with configType:${configType}`, async () => { - const formatterPath = getFixturePath("formatters", "simple.js"); - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`-f ${formatterPath} ${filePath} ${flag}`, null, useFlatConfig); - - assert.strictEqual(exit, 0); + it(`should load the local config file with configType:${configType}`, async () => { + await cli.execute("cli/passing.js --no-ignore", null, useFlatConfig); }); - }); - describe("when given an invalid formatter path", () => { + if (useFlatConfig) { + it(`should load the local config file with glob pattern and configType:${configType}`, async () => { + await cli.execute("cli/pass*.js --no-ignore", null, useFlatConfig); + }); + } - const originalCwd = process.cwd; + // only works on Windows + if (os.platform() === "win32") { + it(`should load the local config file with Windows slashes glob pattern and configType:${configType}`, async () => { + await cli.execute("cli\\pass*.js --no-ignore", null, useFlatConfig); + }); + } + }); - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); + describe("Formatters", () => { - afterEach(() => { - process.cwd = originalCwd; - }); + const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; - it(`should execute with error with configType:${configType}`, async () => { - const formatterPath = getFixturePath("formatters", "file-does-not-exist.js"); - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`--no-ignore -f ${formatterPath} ${filePath}`, null, useFlatConfig); + describe("when given a valid built-in formatter name", () => { + it(`should execute without any errors with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`${flag} -f json ${filePath}`, null, useFlatConfig); - assert.strictEqual(exit, 2); + assert.strictEqual(exit, 0); + }); }); - }); - describe("when given an async formatter path", () => { + describe("when given a valid built-in formatter name that uses rules meta.", () => { - const originalCwd = process.cwd; + const originalCwd = process.cwd; - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); - afterEach(() => { - process.cwd = originalCwd; - }); + afterEach(() => { + process.cwd = originalCwd; + }); - it(`should execute without any errors with configType:${configType}`, async () => { - const formatterPath = getFixturePath("formatters", "async.js"); - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`-f ${formatterPath} ${filePath} ${flag}`, null, useFlatConfig); + it(`should execute without any errors with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`--no-ignore -f json-with-metadata ${filePath} ${flag}`, null, useFlatConfig); - assert.strictEqual(log.info.getCall(0).args[0], "from async formatter"); - assert.strictEqual(exit, 0); - }); - }); - }); + assert.strictEqual(exit, 0); - describe("Exit Codes", () => { + /* + * Note: There is a behavior difference between eslintrc and flat config + * when using formatters. For eslintrc, rulesMeta always contains every + * rule that was loaded during the last run; for flat config, rulesMeta + * only contains meta data for the rules that triggered messages in the + * results. (Flat config uses ESLint#getRulesMetaForResults().) + */ - const originalCwd = process.cwd; + // Check metadata. + const { metadata } = JSON.parse(log.info.args[0][0]); + const expectedMetadata = { + cwd: process.cwd(), + rulesMeta: useFlatConfig ? {} : Array.from(BuiltinRules).reduce((obj, [ruleId, rule]) => { + obj[ruleId] = rule.meta; + return obj; + }, {}) + }; + + assert.deepStrictEqual(metadata, expectedMetadata); + }); + }); - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); + describe("when the --max-warnings option is passed", () => { - afterEach(() => { - process.cwd = originalCwd; - }); + describe("and there are too many warnings", () => { + it(`should provide \`maxWarningsExceeded\` metadata to the formatter with configType:${configType}`, async () => { + const exit = await cli.execute( + `--no-ignore -f json-with-metadata --max-warnings 1 --rule 'quotes: warn' ${flag}`, + "'hello' + 'world';", + useFlatConfig + ); - describe("when executing a file with a lint error", () => { + assert.strictEqual(exit, 1); - it(`should exit with error with configType:${configType}`, async () => { - const filePath = getFixturePath("undef.js"); - const code = `--no-ignore --rule no-undef:2 ${filePath}`; + const { metadata } = JSON.parse(log.info.args[0][0]); - const exit = await cli.execute(code, null, useFlatConfig); + assert.deepStrictEqual( + metadata.maxWarningsExceeded, + { maxWarnings: 1, foundWarnings: 2 } + ); + }); + }); - assert.strictEqual(exit, 1); - }); - }); + describe("and warnings do not exceed the limit", () => { + it(`should omit \`maxWarningsExceeded\` metadata from the formatter with configType:${configType}`, async () => { + const exit = await cli.execute( + `--no-ignore -f json-with-metadata --max-warnings 1 --rule 'quotes: warn' ${flag}`, + "'hello world';", + useFlatConfig + ); - describe("when using --fix-type without --fix or --fix-dry-run", () => { - it(`should exit with error with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); - const code = `--fix-type suggestion ${filePath}`; + assert.strictEqual(exit, 0); - const exit = await cli.execute(code, null, useFlatConfig); + const { metadata } = JSON.parse(log.info.args[0][0]); - assert.strictEqual(exit, 2); + assert.notProperty(metadata, "maxWarningsExceeded"); + }); + }); }); - }); - describe("when executing a file with a syntax error", () => { - it(`should exit with error with configType:${configType}`, async () => { - const filePath = getFixturePath("syntax-error.js"); - const exit = await cli.execute(`--no-ignore ${filePath}`, null, useFlatConfig); + describe("when given an invalid built-in formatter name", () => { - assert.strictEqual(exit, 1); - }); - }); + const originalCwd = process.cwd; - }); + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); - describe("when calling execute more than once", () => { + afterEach(() => { + process.cwd = originalCwd; + }); - const originalCwd = process.cwd; + it(`should execute with error: with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`-f fakeformatter ${filePath} ${flag}`, null, useFlatConfig); - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); + assert.strictEqual(exit, 2); + }); + }); - afterEach(() => { - process.cwd = originalCwd; - }); + describe("when given a valid formatter path", () => { - it(`should not print the results from previous execution with configType:${configType}`, async () => { - const filePath = getFixturePath("missing-semicolon.js"); - const passingPath = getFixturePath("passing.js"); + const originalCwd = process.cwd; - await cli.execute(`--no-ignore --rule semi:2 ${filePath}`, null, useFlatConfig); + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); - assert.isTrue(log.info.called, "Log should have been called."); + afterEach(() => { + process.cwd = originalCwd; + }); - log.info.resetHistory(); + it(`should execute without any errors with configType:${configType}`, async () => { + const formatterPath = getFixturePath("formatters", "simple.js"); + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`-f ${formatterPath} ${filePath} ${flag}`, null, useFlatConfig); - await cli.execute(`--no-ignore --rule semi:2 ${passingPath}`, null, useFlatConfig); - assert.isTrue(log.info.notCalled); + assert.strictEqual(exit, 0); + }); + }); - }); - }); + describe("when given an invalid formatter path", () => { - describe("when executing with version flag", () => { - it(`should print out current version with configType:${configType}`, async () => { - assert.strictEqual(await cli.execute("-v", null, useFlatConfig), 0); - assert.strictEqual(log.info.callCount, 1); - }); - }); + const originalCwd = process.cwd; - describe("when executing with env-info flag", () => { + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); - it(`should print out environment information with configType:${configType}`, async () => { - assert.strictEqual(await cli.execute("--env-info", null, useFlatConfig), 0); - assert.strictEqual(log.info.callCount, 1); - }); + afterEach(() => { + process.cwd = originalCwd; + }); - describe("With error condition", () => { + it(`should execute with error with configType:${configType}`, async () => { + const formatterPath = getFixturePath("formatters", "file-does-not-exist.js"); + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`--no-ignore -f ${formatterPath} ${filePath}`, null, useFlatConfig); - beforeEach(() => { - RuntimeInfo.environment = sinon.stub().throws("There was an error!"); + assert.strictEqual(exit, 2); + }); }); - afterEach(() => { - RuntimeInfo.environment = sinon.stub(); - }); + describe("when given an async formatter path", () => { - it(`should print error message and return error code with configType:${configType}`, async () => { + const originalCwd = process.cwd; - assert.strictEqual(await cli.execute("--env-info", null, useFlatConfig), 2); - assert.strictEqual(log.error.callCount, 1); - }); - }); + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); - }); + afterEach(() => { + process.cwd = originalCwd; + }); + + it(`should execute without any errors with configType:${configType}`, async () => { + const formatterPath = getFixturePath("formatters", "async.js"); + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`-f ${formatterPath} ${filePath} ${flag}`, null, useFlatConfig); - describe("when executing with help flag", () => { - it(`should print out help with configType:${configType}`, async () => { - assert.strictEqual(await cli.execute("-h", null, useFlatConfig), 0); - assert.strictEqual(log.info.callCount, 1); + assert.strictEqual(log.info.getCall(0).args[0], "from async formatter"); + assert.strictEqual(exit, 0); + }); + }); }); - }); - describe("when executing a file with a shebang", () => { - it(`should execute without error with configType:${configType}`, async () => { - const filePath = getFixturePath("shebang.js"); - const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; - const exit = await cli.execute(`${flag} --no-ignore ${filePath}`, null, useFlatConfig); + describe("Exit Codes", () => { - assert.strictEqual(exit, 0); - }); - }); + const originalCwd = process.cwd; - describe("FixtureDir Dependent Tests", () => { + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); - const originalCwd = process.cwd; + afterEach(() => { + process.cwd = originalCwd; + }); - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); + describe("when executing a file with a lint error", () => { - afterEach(() => { - process.cwd = originalCwd; - }); + it(`should exit with error with configType:${configType}`, async () => { + const filePath = getFixturePath("undef.js"); + const code = `--no-ignore --rule no-undef:2 ${filePath}`; - describe("when given a config file and a directory of files", () => { - it(`should load and execute without error with configType:${configType}`, async () => { - const configPath = getFixturePath("configurations", "semi-error.js"); - const filePath = getFixturePath("formatters"); - const code = `--no-ignore --config ${configPath} ${filePath}`; - const exitStatus = await cli.execute(code, null, useFlatConfig); + const exit = await cli.execute(code, null, useFlatConfig); - assert.strictEqual(exitStatus, 0); + assert.strictEqual(exit, 1); + }); }); - }); - describe("when executing with global flag", () => { + describe("when using --fix-type without --fix or --fix-dry-run", () => { + it(`should exit with error with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const code = `--fix-type suggestion ${filePath}`; - it(`should default defined variables to read-only with configType:${configType}`, async () => { - const filePath = getFixturePath("undef.js"); - const exit = await cli.execute(`--global baz,bat --no-ignore --rule no-global-assign:2 ${filePath}`, null, useFlatConfig); + const exit = await cli.execute(code, null, useFlatConfig); - assert.isTrue(log.info.calledOnce); - assert.strictEqual(exit, 1); + assert.strictEqual(exit, 2); + }); }); - it(`should allow defining writable global variables with configType:${configType}`, async () => { - const filePath = getFixturePath("undef.js"); - const exit = await cli.execute(`--global baz:false,bat:true --no-ignore ${filePath}`, null, useFlatConfig); + describe("when executing a file with a syntax error", () => { + it(`should exit with error with configType:${configType}`, async () => { + const filePath = getFixturePath("syntax-error.js"); + const exit = await cli.execute(`--no-ignore ${filePath}`, null, useFlatConfig); - assert.isTrue(log.info.notCalled); - assert.strictEqual(exit, 0); + assert.strictEqual(exit, 1); + }); }); - it(`should allow defining variables with multiple flags with configType:${configType}`, async () => { - const filePath = getFixturePath("undef.js"); - const exit = await cli.execute(`--global baz --global bat:true --no-ignore ${filePath}`, null, useFlatConfig); - - assert.isTrue(log.info.notCalled); - assert.strictEqual(exit, 0); - }); }); + describe("when calling execute more than once", () => { - describe("when supplied with rule flag and severity level set to error", () => { - + const originalCwd = process.cwd; - it(`should exit with an error status (2) with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const code = `--no-ignore --rule 'quotes: [2, double]' ${filePath}`; - const exitStatus = await cli.execute(code, null, useFlatConfig); + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); - assert.strictEqual(exitStatus, 1); + afterEach(() => { + process.cwd = originalCwd; }); - }); - describe("when the quiet option is enabled", () => { + it(`should not print the results from previous execution with configType:${configType}`, async () => { + const filePath = getFixturePath("missing-semicolon.js"); + const passingPath = getFixturePath("passing.js"); - it(`should only print error with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const cliArgs = `--no-ignore --quiet -f stylish --rule 'quotes: [2, double]' --rule 'no-undef: 1' ${filePath}`; + await cli.execute(`--no-ignore --rule semi:2 ${filePath}`, null, useFlatConfig); - await cli.execute(cliArgs, null, useFlatConfig); + assert.isTrue(log.info.called, "Log should have been called."); - sinon.assert.calledOnce(log.info); + log.info.resetHistory(); - const formattedOutput = log.info.firstCall.args[0]; + await cli.execute(`--no-ignore --rule semi:2 ${passingPath}`, null, useFlatConfig); + assert.isTrue(log.info.notCalled); - assert.include(formattedOutput, "(1 error, 0 warnings)"); }); + }); - it(`should print nothing if there are no errors with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const cliArgs = `--no-ignore --quiet -f stylish --rule 'quotes: [1, double]' --rule 'no-undef: 1' ${filePath}`; + describe("when executing with version flag", () => { + it(`should print out current version with configType:${configType}`, async () => { + assert.strictEqual(await cli.execute("-v", null, useFlatConfig), 0); + assert.strictEqual(log.info.callCount, 1); + }); + }); - await cli.execute(cliArgs, null, useFlatConfig); + describe("when executing with env-info flag", () => { - sinon.assert.notCalled(log.info); + it(`should print out environment information with configType:${configType}`, async () => { + assert.strictEqual(await cli.execute("--env-info", null, useFlatConfig), 0); + assert.strictEqual(log.info.callCount, 1); }); - if (useFlatConfig) { - it(`should not run rules set to 'warn' with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const configPath = getFixturePath("eslint.config-rule-throws.js"); - const cliArgs = `--quiet --config ${configPath}' ${filePath}`; + describe("With error condition", () => { - const exit = await cli.execute(cliArgs, null, useFlatConfig); + beforeEach(() => { + RuntimeInfo.environment = sinon.stub().throws("There was an error!"); + }); - assert.strictEqual(exit, 0); + afterEach(() => { + RuntimeInfo.environment = sinon.stub(); }); - it(`should run rules set to 'warn' while maxWarnings is set with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const configPath = getFixturePath("eslint.config-rule-throws.js"); - const cliArgs = `--quiet --max-warnings=1 --config ${configPath}' ${filePath}`; + it(`should print error message and return error code with configType:${configType}`, async () => { - await stdAssert.rejects(async () => { - await cli.execute(cliArgs, null, useFlatConfig); - }); + assert.strictEqual(await cli.execute("--env-info", null, useFlatConfig), 2); + assert.strictEqual(log.error.callCount, 1); }); - } + }); + }); + describe("when executing with help flag", () => { + it(`should print out help with configType:${configType}`, async () => { + assert.strictEqual(await cli.execute("-h", null, useFlatConfig), 0); + assert.strictEqual(log.info.callCount, 1); + }); + }); - describe("no-error-on-unmatched-pattern flag", () => { + describe("when executing a file with a shebang", () => { + it(`should execute without error with configType:${configType}`, async () => { + const filePath = getFixturePath("shebang.js"); + const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; + const exit = await cli.execute(`${flag} --no-ignore ${filePath}`, null, useFlatConfig); - describe("when executing without no-error-on-unmatched-pattern flag", () => { - it(`should throw an error on unmatched glob pattern with configType:${configType}`, async () => { - let filePath = getFixturePath("unmatched-patterns"); - const globPattern = "unmatched*.js"; + assert.strictEqual(exit, 0); + }); + }); - if (useFlatConfig) { - filePath = filePath.replace(/\\/gu, "/"); - } + describe("FixtureDir Dependent Tests", () => { - await stdAssert.rejects(async () => { - await cli.execute(`"${filePath}/${globPattern}"`, null, useFlatConfig); - }, new Error(`No files matching '${filePath}/${globPattern}' were found.`)); - }); + const originalCwd = process.cwd; + beforeEach(() => { + process.cwd = () => getFixturePath(); }); - describe("when executing with no-error-on-unmatched-pattern flag", () => { - it(`should not throw an error on unmatched node glob syntax patterns with configType:${configType}`, async () => { - const filePath = getFixturePath("unmatched-patterns"); - const exit = await cli.execute(`--no-error-on-unmatched-pattern "${filePath}/unmatched*.js"`, null, useFlatConfig); + afterEach(() => { + process.cwd = originalCwd; + }); - assert.strictEqual(exit, 0); + describe("when given a config file and a directory of files", () => { + it(`should load and execute without error with configType:${configType}`, async () => { + const configPath = getFixturePath("configurations", "semi-error.js"); + const filePath = getFixturePath("formatters"); + const code = `--no-ignore --config ${configPath} ${filePath}`; + const exitStatus = await cli.execute(code, null, useFlatConfig); + + assert.strictEqual(exitStatus, 0); }); }); - describe("when executing with no-error-on-unmatched-pattern flag and multiple patterns", () => { - it(`should not throw an error on multiple unmatched node glob syntax patterns with configType:${configType}`, async () => { - const filePath = getFixturePath("unmatched-patterns/js3"); - const exit = await cli.execute(`--no-error-on-unmatched-pattern ${filePath}/unmatched1*.js ${filePath}/unmatched2*.js`, null, useFlatConfig); + describe("when executing with global flag", () => { + + it(`should default defined variables to read-only with configType:${configType}`, async () => { + const filePath = getFixturePath("undef.js"); + const exit = await cli.execute(`--global baz,bat --no-ignore --rule no-global-assign:2 ${filePath}`, null, useFlatConfig); + assert.isTrue(log.info.calledOnce); + assert.strictEqual(exit, 1); + }); + + it(`should allow defining writable global variables with configType:${configType}`, async () => { + const filePath = getFixturePath("undef.js"); + const exit = await cli.execute(`--global baz:false,bat:true --no-ignore ${filePath}`, null, useFlatConfig); + + assert.isTrue(log.info.notCalled); assert.strictEqual(exit, 0); }); - it(`should still throw an error on when a matched pattern has lint errors with configType:${configType}`, async () => { - const filePath = getFixturePath("unmatched-patterns"); - const exit = await cli.execute(`--no-ignore --no-error-on-unmatched-pattern ${filePath}/unmatched1*.js ${filePath}/failing.js`, null, useFlatConfig); + it(`should allow defining variables with multiple flags with configType:${configType}`, async () => { + const filePath = getFixturePath("undef.js"); + const exit = await cli.execute(`--global baz --global bat:true --no-ignore ${filePath}`, null, useFlatConfig); - assert.strictEqual(exit, 1); + assert.isTrue(log.info.notCalled); + assert.strictEqual(exit, 0); }); }); - }); - describe("Parser Options", () => { + describe("when supplied with rule flag and severity level set to error", () => { - describe("when given parser options", () => { - it(`should exit with error if parser options are invalid with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`--no-ignore --parser-options test111 ${filePath}`, null, useFlatConfig); - assert.strictEqual(exit, 2); + it(`should exit with an error status (2) with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const code = `--no-ignore --rule 'quotes: [2, double]' ${filePath}`; + const exitStatus = await cli.execute(code, null, useFlatConfig); + + assert.strictEqual(exitStatus, 1); }); + }); - it(`should exit with no error if parser is valid with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:6 ${filePath}`, null, useFlatConfig); + describe("when the quiet option is enabled", () => { - assert.strictEqual(exit, 0); - }); + it(`should only print error with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const cliArgs = `--no-ignore --quiet -f stylish --rule 'quotes: [2, double]' --rule 'no-undef: 1' ${filePath}`; - it(`should exit with an error on ecmaVersion 7 feature in ecmaVersion 6 with configType:${configType}`, async () => { - const filePath = getFixturePath("passing-es7.js"); - const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:6 ${filePath}`, null, useFlatConfig); + await cli.execute(cliArgs, null, useFlatConfig); - assert.strictEqual(exit, 1); - }); + sinon.assert.calledOnce(log.info); - it(`should exit with no error on ecmaVersion 7 feature in ecmaVersion 7 with configType:${configType}`, async () => { - const filePath = getFixturePath("passing-es7.js"); - const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:7 ${filePath}`, null, useFlatConfig); + const formattedOutput = log.info.firstCall.args[0]; - assert.strictEqual(exit, 0); + assert.include(formattedOutput, "(1 error, 0 warnings)"); }); - it(`should exit with no error on ecmaVersion 7 feature with config ecmaVersion 6 and command line ecmaVersion 7 with configType:${configType}`, async () => { - const configPath = useFlatConfig - ? getFixturePath("configurations", "es6.js") - : getFixturePath("configurations", "es6.json"); - const filePath = getFixturePath("passing-es7.js"); - const exit = await cli.execute(`--no-ignore --config ${configPath} --parser-options=ecmaVersion:7 ${filePath}`, null, useFlatConfig); + it(`should print nothing if there are no errors with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const cliArgs = `--no-ignore --quiet -f stylish --rule 'quotes: [1, double]' --rule 'no-undef: 1' ${filePath}`; - assert.strictEqual(exit, 0); + await cli.execute(cliArgs, null, useFlatConfig); + + sinon.assert.notCalled(log.info); }); - }); - }); - describe("when given the max-warnings flag", () => { + if (useFlatConfig) { + it(`should not run rules set to 'warn' with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const configPath = getFixturePath("eslint.config-rule-throws.js"); + const cliArgs = `--quiet --config ${configPath}' ${filePath}`; - let filePath, configFilePath; + const exit = await cli.execute(cliArgs, null, useFlatConfig); - before(() => { - filePath = getFixturePath("max-warnings/six-warnings.js"); - configFilePath = getFixturePath(useFlatConfig ? "max-warnings/eslint.config.js" : "max-warnings/.eslintrc"); - }); + assert.strictEqual(exit, 0); + }); - it(`should not change exit code if warning count under threshold with configType:${configType}`, async () => { - const exitCode = await cli.execute(`--no-ignore --max-warnings 10 ${filePath} -c ${configFilePath}`, null, useFlatConfig); + it(`should run rules set to 'warn' while maxWarnings is set with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const configPath = getFixturePath("eslint.config-rule-throws.js"); + const cliArgs = `--quiet --max-warnings=1 --config ${configPath}' ${filePath}`; - assert.strictEqual(exitCode, 0); + await stdAssert.rejects(async () => { + await cli.execute(cliArgs, null, useFlatConfig); + }); + }); + } }); - it(`should exit with exit code 1 if warning count exceeds threshold with configType:${configType}`, async () => { - const exitCode = await cli.execute(`--no-ignore --max-warnings 5 ${filePath} -c ${configFilePath}`, null, useFlatConfig); - assert.strictEqual(exitCode, 1); - assert.ok(log.error.calledOnce); - assert.include(log.error.getCall(0).args[0], "ESLint found too many warnings"); - }); + describe("no-error-on-unmatched-pattern flag", () => { - it(`should exit with exit code 1 without printing warnings if the quiet option is enabled and warning count exceeds threshold with configType:${configType}`, async () => { - const exitCode = await cli.execute(`--no-ignore --quiet --max-warnings 5 ${filePath} -c ${configFilePath}`, null, useFlatConfig); + describe("when executing without no-error-on-unmatched-pattern flag", () => { + it(`should throw an error on unmatched glob pattern with configType:${configType}`, async () => { + let filePath = getFixturePath("unmatched-patterns"); + const globPattern = "unmatched*.js"; - assert.strictEqual(exitCode, 1); - assert.ok(log.error.calledOnce); - assert.include(log.error.getCall(0).args[0], "ESLint found too many warnings"); - assert.ok(log.info.notCalled); // didn't print warnings - }); + if (useFlatConfig) { + filePath = filePath.replace(/\\/gu, "/"); + } - it(`should not change exit code if warning count equals threshold with configType:${configType}`, async () => { - const exitCode = await cli.execute(`--no-ignore --max-warnings 6 ${filePath} -c ${configFilePath}`, null, useFlatConfig); + await stdAssert.rejects(async () => { + await cli.execute(`"${filePath}/${globPattern}"`, null, useFlatConfig); + }, new Error(`No files matching '${filePath}/${globPattern}' were found.`)); + }); - assert.strictEqual(exitCode, 0); - }); + }); - it(`should not change exit code if flag is not specified and there are warnings with configType:${configType}`, async () => { - const exitCode = await cli.execute(`-c ${configFilePath} ${filePath}`, null, useFlatConfig); + describe("when executing with no-error-on-unmatched-pattern flag", () => { + it(`should not throw an error on unmatched node glob syntax patterns with configType:${configType}`, async () => { + const filePath = getFixturePath("unmatched-patterns"); + const exit = await cli.execute(`--no-error-on-unmatched-pattern "${filePath}/unmatched*.js"`, null, useFlatConfig); - assert.strictEqual(exitCode, 0); - }); - }); + assert.strictEqual(exit, 0); + }); + }); - describe("when given the exit-on-fatal-error flag", () => { - it(`should not change exit code if no fatal errors are reported with configType:${configType}`, async () => { - const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error.js"); - const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); + describe("when executing with no-error-on-unmatched-pattern flag and multiple patterns", () => { + it(`should not throw an error on multiple unmatched node glob syntax patterns with configType:${configType}`, async () => { + const filePath = getFixturePath("unmatched-patterns/js3"); + const exit = await cli.execute(`--no-error-on-unmatched-pattern ${filePath}/unmatched1*.js ${filePath}/unmatched2*.js`, null, useFlatConfig); - assert.strictEqual(exitCode, 0); - }); + assert.strictEqual(exit, 0); + }); - it(`should exit with exit code 1 if no fatal errors are found, but rule violations are found with configType:${configType}`, async () => { - const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error-rule-violation.js"); - const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); + it(`should still throw an error on when a matched pattern has lint errors with configType:${configType}`, async () => { + const filePath = getFixturePath("unmatched-patterns"); + const exit = await cli.execute(`--no-ignore --no-error-on-unmatched-pattern ${filePath}/unmatched1*.js ${filePath}/failing.js`, null, useFlatConfig); + + assert.strictEqual(exit, 1); + }); + }); - assert.strictEqual(exitCode, 1); }); - it(`should exit with exit code 2 if fatal error is found with configType:${configType}`, async () => { - const filePath = getFixturePath("exit-on-fatal-error", "fatal-error.js"); - const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); + describe("Parser Options", () => { - assert.strictEqual(exitCode, 2); - }); + describe("when given parser options", () => { + it(`should exit with error if parser options are invalid with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`--no-ignore --parser-options test111 ${filePath}`, null, useFlatConfig); - it(`should exit with exit code 2 if fatal error is found in any file with configType:${configType}`, async () => { - const filePath = getFixturePath("exit-on-fatal-error"); - const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); + assert.strictEqual(exit, 2); + }); - assert.strictEqual(exitCode, 2); - }); + it(`should exit with no error if parser is valid with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:6 ${filePath}`, null, useFlatConfig); + + assert.strictEqual(exit, 0); + }); + it(`should exit with an error on ecmaVersion 7 feature in ecmaVersion 6 with configType:${configType}`, async () => { + const filePath = getFixturePath("passing-es7.js"); + const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:6 ${filePath}`, null, useFlatConfig); - }); + assert.strictEqual(exit, 1); + }); + it(`should exit with no error on ecmaVersion 7 feature in ecmaVersion 7 with configType:${configType}`, async () => { + const filePath = getFixturePath("passing-es7.js"); + const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:7 ${filePath}`, null, useFlatConfig); - describe("Ignores", () => { + assert.strictEqual(exit, 0); + }); - describe("when given a directory with eslint excluded files in the directory", () => { - it(`should throw an error and not process any files with configType:${configType}`, async () => { - const options = useFlatConfig - ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` - : `--ignore-path ${getFixturePath(".eslintignore")}`; - const filePath = getFixturePath("cli"); - const expectedMessage = useFlatConfig - ? `All files matched by '${filePath.replace(/\\/gu, "/")}' are ignored.` - : `All files matched by '${filePath}' are ignored.`; + it(`should exit with no error on ecmaVersion 7 feature with config ecmaVersion 6 and command line ecmaVersion 7 with configType:${configType}`, async () => { + const configPath = useFlatConfig + ? getFixturePath("configurations", "es6.js") + : getFixturePath("configurations", "es6.json"); + const filePath = getFixturePath("passing-es7.js"); + const exit = await cli.execute(`--no-ignore --config ${configPath} --parser-options=ecmaVersion:7 ${filePath}`, null, useFlatConfig); - await stdAssert.rejects(async () => { - await cli.execute(`${options} ${filePath}`, null, useFlatConfig); - }, new Error(expectedMessage)); + assert.strictEqual(exit, 0); + }); }); }); - describe("when given a file in excluded files list", () => { - it(`should not process the file with configType:${configType}`, async () => { - const options = useFlatConfig - ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` - : `--ignore-path ${getFixturePath(".eslintignore")}`; - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`${options} ${filePath}`, null, useFlatConfig); + describe("when given the max-warnings flag", () => { - // a warning about the ignored file - assert.isTrue(log.info.called); - assert.strictEqual(exit, 0); + let filePath, configFilePath; + + before(() => { + filePath = getFixturePath("max-warnings/six-warnings.js"); + configFilePath = getFixturePath(useFlatConfig ? "max-warnings/eslint.config.js" : "max-warnings/.eslintrc"); }); - it(`should process the file when forced with configType:${configType}`, async () => { - const options = useFlatConfig - ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` - : `--ignore-path ${getFixturePath(".eslintignore")}`; - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`${options} --no-ignore ${filePath}`, null, useFlatConfig); + it(`should not change exit code if warning count under threshold with configType:${configType}`, async () => { + const exitCode = await cli.execute(`--no-ignore --max-warnings 10 ${filePath} -c ${configFilePath}`, null, useFlatConfig); - // no warnings - assert.isFalse(log.info.called); - assert.strictEqual(exit, 0); + assert.strictEqual(exitCode, 0); }); - it(`should suppress the warning if --no-warn-ignored is passed with configType:${configType}`, async () => { - const options = useFlatConfig - ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` - : `--ignore-path ${getFixturePath(".eslintignore")}`; - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`${options} --no-warn-ignored ${filePath}`, null, useFlatConfig); - - assert.isFalse(log.info.called); + it(`should exit with exit code 1 if warning count exceeds threshold with configType:${configType}`, async () => { + const exitCode = await cli.execute(`--no-ignore --max-warnings 5 ${filePath} -c ${configFilePath}`, null, useFlatConfig); - // When eslintrc is used, we get an exit code of 2 because the --no-warn-ignored option is unrecognized. - assert.strictEqual(exit, useFlatConfig ? 0 : 2); + assert.strictEqual(exitCode, 1); + assert.ok(log.error.calledOnce); + assert.include(log.error.getCall(0).args[0], "ESLint found too many warnings"); }); - it(`should not lint anything when no files are passed if --pass-on-no-patterns is passed with configType:${configType}`, async () => { - const exit = await cli.execute("--pass-on-no-patterns", null, useFlatConfig); + it(`should exit with exit code 1 without printing warnings if the quiet option is enabled and warning count exceeds threshold with configType:${configType}`, async () => { + const exitCode = await cli.execute(`--no-ignore --quiet --max-warnings 5 ${filePath} -c ${configFilePath}`, null, useFlatConfig); - assert.isFalse(log.info.called); - assert.strictEqual(exit, 0); + assert.strictEqual(exitCode, 1); + assert.ok(log.error.calledOnce); + assert.include(log.error.getCall(0).args[0], "ESLint found too many warnings"); + assert.ok(log.info.notCalled); // didn't print warnings }); - it(`should suppress the warning if --no-warn-ignored is passed and an ignored file is passed via stdin with configType:${configType}`, async () => { - const options = useFlatConfig - ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` - : `--ignore-path ${getFixturePath(".eslintignore")}`; - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`${options} --no-warn-ignored --stdin --stdin-filename ${filePath}`, "foo", useFlatConfig); + it(`should not change exit code if warning count equals threshold with configType:${configType}`, async () => { + const exitCode = await cli.execute(`--no-ignore --max-warnings 6 ${filePath} -c ${configFilePath}`, null, useFlatConfig); + + assert.strictEqual(exitCode, 0); + }); - assert.isFalse(log.info.called); + it(`should not change exit code if flag is not specified and there are warnings with configType:${configType}`, async () => { + const exitCode = await cli.execute(`-c ${configFilePath} ${filePath}`, null, useFlatConfig); - // When eslintrc is used, we get an exit code of 2 because the --no-warn-ignored option is unrecognized. - assert.strictEqual(exit, useFlatConfig ? 0 : 2); + assert.strictEqual(exitCode, 0); }); }); - describe("when given a pattern to ignore", () => { - it(`should not process any files with configType:${configType}`, async () => { - const ignoredFile = getFixturePath("cli/syntax-error.js"); - const ignorePathOption = useFlatConfig - ? "" - : "--ignore-path .eslintignore_empty"; - const filePath = getFixturePath("cli/passing.js"); - const ignorePattern = useFlatConfig ? "cli/**" : "cli/"; - const exit = await cli.execute( - `--ignore-pattern ${ignorePattern} ${ignorePathOption} ${ignoredFile} ${filePath}`, null, useFlatConfig - ); + describe("when given the exit-on-fatal-error flag", () => { + it(`should not change exit code if no fatal errors are reported with configType:${configType}`, async () => { + const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error.js"); + const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); - // warnings about the ignored files - assert.isTrue(log.info.called); - assert.strictEqual(exit, 0); + assert.strictEqual(exitCode, 0); }); - it(`should interpret pattern that contains a slash as relative to cwd with configType:${configType}`, async () => { - process.cwd = () => getFixturePath("cli/ignore-pattern-relative/subdir"); + it(`should exit with exit code 1 if no fatal errors are found, but rule violations are found with configType:${configType}`, async () => { + const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error-rule-violation.js"); + const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); - /* - * The config file is in `cli/ignore-pattern-relative`, so this would fail - * if `subdir/**` ignore pattern is interpreted as relative to the config base path. - */ - const exit = await cli.execute("**/*.js --ignore-pattern subdir/**", null, useFlatConfig); + assert.strictEqual(exitCode, 1); + }); - assert.strictEqual(exit, 0); + it(`should exit with exit code 2 if fatal error is found with configType:${configType}`, async () => { + const filePath = getFixturePath("exit-on-fatal-error", "fatal-error.js"); + const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); - await stdAssert.rejects( - async () => await cli.execute("**/*.js --ignore-pattern subsubdir/*.js", null, useFlatConfig), - /All files matched by '\*\*\/\*\.js' are ignored/u - ); + assert.strictEqual(exitCode, 2); }); - it(`should interpret pattern that doesn't contain a slash as relative to cwd with configType:${configType}`, async () => { - process.cwd = () => getFixturePath("cli/ignore-pattern-relative/subdir/subsubdir"); + it(`should exit with exit code 2 if fatal error is found in any file with configType:${configType}`, async () => { + const filePath = getFixturePath("exit-on-fatal-error"); + const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); - await stdAssert.rejects( - async () => await cli.execute("**/*.js --ignore-pattern *.js", null, useFlatConfig), - /All files matched by '\*\*\/\*\.js' are ignored/u - ); + assert.strictEqual(exitCode, 2); }); - if (useFlatConfig) { - it("should ignore files if the pattern is a path to a directory (with trailing slash)", async () => { - const filePath = getFixturePath("cli/syntax-error.js"); - const exit = await cli.execute(`--ignore-pattern cli/ ${filePath}`, null, true); - // parsing error causes exit code 1 + }); + + + describe("Ignores", () => { + + describe("when given a directory with eslint excluded files in the directory", () => { + it(`should throw an error and not process any files with configType:${configType}`, async () => { + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; + const filePath = getFixturePath("cli"); + const expectedMessage = useFlatConfig + ? `All files matched by '${filePath.replace(/\\/gu, "/")}' are ignored.` + : `All files matched by '${filePath}' are ignored.`; + + await stdAssert.rejects(async () => { + await cli.execute(`${options} ${filePath}`, null, useFlatConfig); + }, new Error(expectedMessage)); + }); + }); + + describe("when given a file in excluded files list", () => { + it(`should not process the file with configType:${configType}`, async () => { + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`${options} ${filePath}`, null, useFlatConfig); + + // a warning about the ignored file assert.isTrue(log.info.called); assert.strictEqual(exit, 0); }); - it("should ignore files if the pattern is a path to a directory (without trailing slash)", async () => { - const filePath = getFixturePath("cli/syntax-error.js"); - const exit = await cli.execute(`--ignore-pattern cli ${filePath}`, null, true); + it(`should process the file when forced with configType:${configType}`, async () => { + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`${options} --no-ignore ${filePath}`, null, useFlatConfig); - // parsing error causes exit code 1 + // no warnings + assert.isFalse(log.info.called); + assert.strictEqual(exit, 0); + }); + + it(`should suppress the warning if --no-warn-ignored is passed with configType:${configType}`, async () => { + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`${options} --no-warn-ignored ${filePath}`, null, useFlatConfig); + + assert.isFalse(log.info.called); + + // When eslintrc is used, we get an exit code of 2 because the --no-warn-ignored option is unrecognized. + assert.strictEqual(exit, useFlatConfig ? 0 : 2); + }); + + it(`should not lint anything when no files are passed if --pass-on-no-patterns is passed with configType:${configType}`, async () => { + const exit = await cli.execute("--pass-on-no-patterns", null, useFlatConfig); + + assert.isFalse(log.info.called); + assert.strictEqual(exit, 0); + }); + + it(`should suppress the warning if --no-warn-ignored is passed and an ignored file is passed via stdin with configType:${configType}`, async () => { + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute(`${options} --no-warn-ignored --stdin --stdin-filename ${filePath}`, "foo", useFlatConfig); + + assert.isFalse(log.info.called); + + // When eslintrc is used, we get an exit code of 2 because the --no-warn-ignored option is unrecognized. + assert.strictEqual(exit, useFlatConfig ? 0 : 2); + }); + }); + + describe("when given a pattern to ignore", () => { + it(`should not process any files with configType:${configType}`, async () => { + const ignoredFile = getFixturePath("cli/syntax-error.js"); + const ignorePathOption = useFlatConfig + ? "" + : "--ignore-path .eslintignore_empty"; + const filePath = getFixturePath("cli/passing.js"); + const ignorePattern = useFlatConfig ? "cli/**" : "cli/"; + const exit = await cli.execute( + `--ignore-pattern ${ignorePattern} ${ignorePathOption} ${ignoredFile} ${filePath}`, null, useFlatConfig + ); + + // warnings about the ignored files assert.isTrue(log.info.called); assert.strictEqual(exit, 0); }); - } + + it(`should interpret pattern that contains a slash as relative to cwd with configType:${configType}`, async () => { + process.cwd = () => getFixturePath("cli/ignore-pattern-relative/subdir"); + + /* + * The config file is in `cli/ignore-pattern-relative`, so this would fail + * if `subdir/**` ignore pattern is interpreted as relative to the config base path. + */ + const exit = await cli.execute("**/*.js --ignore-pattern subdir/**", null, useFlatConfig); + + assert.strictEqual(exit, 0); + + await stdAssert.rejects( + async () => await cli.execute("**/*.js --ignore-pattern subsubdir/*.js", null, useFlatConfig), + /All files matched by '\*\*\/\*\.js' are ignored/u + ); + }); + + it(`should interpret pattern that doesn't contain a slash as relative to cwd with configType:${configType}`, async () => { + process.cwd = () => getFixturePath("cli/ignore-pattern-relative/subdir/subsubdir"); + + await stdAssert.rejects( + async () => await cli.execute("**/*.js --ignore-pattern *.js", null, useFlatConfig), + /All files matched by '\*\*\/\*\.js' are ignored/u + ); + }); + + if (useFlatConfig) { + it("should ignore files if the pattern is a path to a directory (with trailing slash)", async () => { + const filePath = getFixturePath("cli/syntax-error.js"); + const exit = await cli.execute(`--ignore-pattern cli/ ${filePath}`, null, true); + + // parsing error causes exit code 1 + assert.isTrue(log.info.called); + assert.strictEqual(exit, 0); + }); + + it("should ignore files if the pattern is a path to a directory (without trailing slash)", async () => { + const filePath = getFixturePath("cli/syntax-error.js"); + const exit = await cli.execute(`--ignore-pattern cli ${filePath}`, null, true); + + // parsing error causes exit code 1 + assert.isTrue(log.info.called); + assert.strictEqual(exit, 0); + }); + } + }); + }); }); - }); + describe("when given a parser name", () => { - describe("when given a parser name", () => { + it(`should exit with a fatal error if parser is invalid with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); - it(`should exit with a fatal error if parser is invalid with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); + await stdAssert.rejects(async () => await cli.execute(`--no-ignore --parser test111 ${filePath}`, null, useFlatConfig), "Cannot find module 'test111'"); + }); + + it(`should exit with no error if parser is valid with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; + const exit = await cli.execute(`${flag} --no-ignore --parser espree ${filePath}`, null, useFlatConfig); + + assert.strictEqual(exit, 0); + }); - await stdAssert.rejects(async () => await cli.execute(`--no-ignore --parser test111 ${filePath}`, null, useFlatConfig), "Cannot find module 'test111'"); }); - it(`should exit with no error if parser is valid with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); + describe("when supplied with report output file path", () => { const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; - const exit = await cli.execute(`${flag} --no-ignore --parser espree ${filePath}`, null, useFlatConfig); - assert.strictEqual(exit, 0); - }); + afterEach(() => { + sh.rm("-rf", "tests/output"); + }); - }); + it(`should write the file and create dirs if they don't exist with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const code = `${flag} --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; - describe("when supplied with report output file path", () => { - const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; + await cli.execute(code, null, useFlatConfig); + + assert.include(fs.readFileSync("tests/output/eslint-output.txt", "utf8"), filePath); + assert.isTrue(log.info.notCalled); + }); - afterEach(() => { - sh.rm("-rf", "tests/output"); - }); + // https://github.com/eslint/eslint/issues/17660 + it(`should write the file and create dirs if they don't exist even when output is empty with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const code = `${flag} --rule 'quotes: [1, single]' --o tests/output/eslint-output.txt ${filePath}`; - it(`should write the file and create dirs if they don't exist with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const code = `${flag} --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; + // TODO: fix this test to: await cli.execute(code, null, useFlatConfig); + await cli.execute(code, "var a = 'b'", useFlatConfig); - await cli.execute(code, null, useFlatConfig); + assert.isTrue(fs.existsSync("tests/output/eslint-output.txt")); + assert.strictEqual(fs.readFileSync("tests/output/eslint-output.txt", "utf8"), ""); + assert.isTrue(log.info.notCalled); + }); - assert.include(fs.readFileSync("tests/output/eslint-output.txt", "utf8"), filePath); - assert.isTrue(log.info.notCalled); - }); + it(`should return an error if the path is a directory with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const code = `${flag} --rule 'quotes: [1, double]' --o tests/output ${filePath}`; - // https://github.com/eslint/eslint/issues/17660 - it(`should write the file and create dirs if they don't exist even when output is empty with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const code = `${flag} --rule 'quotes: [1, single]' --o tests/output/eslint-output.txt ${filePath}`; + fs.mkdirSync("tests/output"); - // TODO: fix this test to: await cli.execute(code, null, useFlatConfig); - await cli.execute(code, "var a = 'b'", useFlatConfig); + const exit = await cli.execute(code, null, useFlatConfig); - assert.isTrue(fs.existsSync("tests/output/eslint-output.txt")); - assert.strictEqual(fs.readFileSync("tests/output/eslint-output.txt", "utf8"), ""); - assert.isTrue(log.info.notCalled); - }); + assert.strictEqual(exit, 2); + assert.isTrue(log.info.notCalled); + assert.isTrue(log.error.calledOnce); + }); - it(`should return an error if the path is a directory with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const code = `${flag} --rule 'quotes: [1, double]' --o tests/output ${filePath}`; + it(`should return an error if the path could not be written to with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const code = `${flag} --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; - fs.mkdirSync("tests/output"); + fs.writeFileSync("tests/output", "foo"); - const exit = await cli.execute(code, null, useFlatConfig); + const exit = await cli.execute(code, null, useFlatConfig); - assert.strictEqual(exit, 2); - assert.isTrue(log.info.notCalled); - assert.isTrue(log.error.calledOnce); + assert.strictEqual(exit, 2); + assert.isTrue(log.info.notCalled); + assert.isTrue(log.error.calledOnce); + }); }); - it(`should return an error if the path could not be written to with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const code = `${flag} --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; + describe("when passed --no-inline-config", () => { + let localCLI; - fs.writeFileSync("tests/output", "foo"); + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it(`should pass allowInlineConfig:false to ESLint when --no-inline-config is used with configType:${configType}`, async () => { + + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ allowInlineConfig: false })); + + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([{ + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 2, + message: "Fake message" + } + ], + errorCount: 1, + warningCount: 0 + }]); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.stub(); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./shared/logging": log + }); - const exit = await cli.execute(code, null, useFlatConfig); + await localCLI.execute("--no-inline-config .", null, useFlatConfig); + }); - assert.strictEqual(exit, 2); - assert.isTrue(log.info.notCalled); - assert.isTrue(log.error.calledOnce); - }); - }); + it(`should not error and allowInlineConfig should be true by default with configType:${configType}`, async () => { - describe("when passed --no-inline-config", () => { - let localCLI; + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ allowInlineConfig: true })); - afterEach(() => { - sinon.verifyAndRestore(); - }); + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.stub(); - it(`should pass allowInlineConfig:false to ESLint when --no-inline-config is used with configType:${configType}`, async () => { + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./shared/logging": log + }); - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ allowInlineConfig: false })); + const exitCode = await localCLI.execute(".", null, useFlatConfig); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 2, - message: "Fake message" - } - ], - errorCount: 1, - warningCount: 0 - }]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.stub(); + assert.strictEqual(exitCode, 0); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { LegacyESLint: fakeESLint }, - "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - "./shared/logging": log }); - await localCLI.execute("--no-inline-config .", null, useFlatConfig); }); - it(`should not error and allowInlineConfig should be true by default with configType:${configType}`, async () => { - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ allowInlineConfig: true })); + describe("when passed --fix", () => { + let localCLI; - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.stub(); - - localCLI = proxyquire("../../lib/cli", { - "./eslint": { LegacyESLint: fakeESLint }, - "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - "./shared/logging": log + afterEach(() => { + sinon.verifyAndRestore(); }); - const exitCode = await localCLI.execute(".", null, useFlatConfig); - - assert.strictEqual(exitCode, 0); - - }); - - }); + it(`should pass fix:true to ESLint when executing on files with configType:${configType}`, async () => { - describe("when passed --fix", () => { - let localCLI; + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - afterEach(() => { - sinon.verifyAndRestore(); - }); + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().once(); - it(`should pass fix:true to ESLint when executing on files with configType:${configType}`, async () => { + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./shared/logging": log + }); - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); + const exitCode = await localCLI.execute("--fix .", null, useFlatConfig); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().once(); + assert.strictEqual(exitCode, 0); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { LegacyESLint: fakeESLint }, - "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - "./shared/logging": log }); - const exitCode = await localCLI.execute("--fix .", null, useFlatConfig); - assert.strictEqual(exitCode, 0); + it(`should rewrite files when in fix mode with configType:${configType}`, async () => { - }); + const report = [{ + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 2, + message: "Fake message" + } + ], + errorCount: 1, + warningCount: 0 + }]; + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - it(`should rewrite files when in fix mode with configType:${configType}`, async () => { + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().withExactArgs(report); - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 2, - message: "Fake message" - } - ], - errorCount: 1, - warningCount: 0 - }]; + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./shared/logging": log + }); - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); + const exitCode = await localCLI.execute("--fix .", null, useFlatConfig); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().withExactArgs(report); + assert.strictEqual(exitCode, 1); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { LegacyESLint: fakeESLint }, - "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - "./shared/logging": log }); - const exitCode = await localCLI.execute("--fix .", null, useFlatConfig); + it(`should provide fix predicate and rewrite files when in fix mode and quiet mode with configType:${configType}`, async () => { - assert.strictEqual(exitCode, 1); + const report = [{ + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 1, + message: "Fake message" + } + ], + errorCount: 0, + warningCount: 1 + }]; - }); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: sinon.match.func })); - it(`should provide fix predicate and rewrite files when in fix mode and quiet mode with configType:${configType}`, async () => { + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.getErrorResults = sinon.stub().returns([]); + fakeESLint.outputFixes = sinon.mock().withExactArgs(report); - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 1, - message: "Fake message" - } - ], - errorCount: 0, - warningCount: 1 - }]; + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./shared/logging": log + }); - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: sinon.match.func })); + const exitCode = await localCLI.execute("--fix --quiet .", null, useFlatConfig); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.getErrorResults = sinon.stub().returns([]); - fakeESLint.outputFixes = sinon.mock().withExactArgs(report); + assert.strictEqual(exitCode, 0); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { LegacyESLint: fakeESLint }, - "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - "./shared/logging": log }); - const exitCode = await localCLI.execute("--fix --quiet .", null, useFlatConfig); + it(`should not call ESLint and return 2 when executing on text with configType:${configType}`, async () => { - assert.strictEqual(exitCode, 0); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().never(); - }); - - it(`should not call ESLint and return 2 when executing on text with configType:${configType}`, async () => { + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./shared/logging": log + }); - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().never(); + const exitCode = await localCLI.execute("--fix .", "foo = bar;", useFlatConfig); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { LegacyESLint: fakeESLint }, - "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - "./shared/logging": log + assert.strictEqual(exitCode, 2); }); - const exitCode = await localCLI.execute("--fix .", "foo = bar;", useFlatConfig); - - assert.strictEqual(exitCode, 2); - }); - - }); - - describe("when passed --fix-dry-run", () => { - let localCLI; - - afterEach(() => { - sinon.verifyAndRestore(); }); - it(`should pass fix:true to ESLint when executing on files with configType:${configType}`, async () => { - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().never(); + describe("when passed --fix-dry-run", () => { + let localCLI; - localCLI = proxyquire("../../lib/cli", { - "./eslint": { LegacyESLint: fakeESLint }, - "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - - "./shared/logging": log + afterEach(() => { + sinon.verifyAndRestore(); }); - const exitCode = await localCLI.execute("--fix-dry-run .", null, useFlatConfig); + it(`should pass fix:true to ESLint when executing on files with configType:${configType}`, async () => { - assert.strictEqual(exitCode, 0); - - }); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - it(`should pass fixTypes to ESLint when --fix-type is passed with configType:${configType}`, async () => { + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().never(); - const expectedESLintOptions = { - fix: true, - fixTypes: ["suggestion"] - }; + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match(expectedESLintOptions)); + "./shared/logging": log + }); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.stub(); + const exitCode = await localCLI.execute("--fix-dry-run .", null, useFlatConfig); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { LegacyESLint: fakeESLint }, - "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + assert.strictEqual(exitCode, 0); - "./shared/logging": log }); - const exitCode = await localCLI.execute("--fix-dry-run --fix-type suggestion .", null, useFlatConfig); + it(`should pass fixTypes to ESLint when --fix-type is passed with configType:${configType}`, async () => { - assert.strictEqual(exitCode, 0); - }); + const expectedESLintOptions = { + fix: true, + fixTypes: ["suggestion"] + }; - it(`should not rewrite files when in fix-dry-run mode with configType:${configType}`, async () => { + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match(expectedESLintOptions)); - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 2, - message: "Fake message" - } - ], - errorCount: 1, - warningCount: 0 - }]; + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.stub(); - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().never(); + "./shared/logging": log + }); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { LegacyESLint: fakeESLint }, - "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + const exitCode = await localCLI.execute("--fix-dry-run --fix-type suggestion .", null, useFlatConfig); - "./shared/logging": log + assert.strictEqual(exitCode, 0); }); - const exitCode = await localCLI.execute("--fix-dry-run .", null, useFlatConfig); + it(`should not rewrite files when in fix-dry-run mode with configType:${configType}`, async () => { - assert.strictEqual(exitCode, 1); + const report = [{ + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 2, + message: "Fake message" + } + ], + errorCount: 1, + warningCount: 0 + }]; - }); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - it(`should provide fix predicate when in fix-dry-run mode and quiet mode with configType:${configType}`, async () => { + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().never(); - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 1, - message: "Fake message" - } - ], - errorCount: 0, - warningCount: 1 - }]; + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: sinon.match.func })); + "./shared/logging": log + }); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.getErrorResults = sinon.stub().returns([]); - fakeESLint.outputFixes = sinon.mock().never(); + const exitCode = await localCLI.execute("--fix-dry-run .", null, useFlatConfig); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { LegacyESLint: fakeESLint }, - "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + assert.strictEqual(exitCode, 1); - "./shared/logging": log }); - const exitCode = await localCLI.execute("--fix-dry-run --quiet .", null, useFlatConfig); + it(`should provide fix predicate when in fix-dry-run mode and quiet mode with configType:${configType}`, async () => { - assert.strictEqual(exitCode, 0); + const report = [{ + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 1, + message: "Fake message" + } + ], + errorCount: 0, + warningCount: 1 + }]; - }); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: sinon.match.func })); - it(`should allow executing on text with configType:${configType}`, async () => { + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.getErrorResults = sinon.stub().returns([]); + fakeESLint.outputFixes = sinon.mock().never(); - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 2, - message: "Fake message" - } - ], - errorCount: 1, - warningCount: 0 - }]; + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); + "./shared/logging": log + }); - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintText").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().never(); + const exitCode = await localCLI.execute("--fix-dry-run --quiet .", null, useFlatConfig); - localCLI = proxyquire("../../lib/cli", { - "./eslint": { LegacyESLint: fakeESLint }, - "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + assert.strictEqual(exitCode, 0); - "./shared/logging": log }); - const exitCode = await localCLI.execute("--fix-dry-run .", "foo = bar;", useFlatConfig); - - assert.strictEqual(exitCode, 1); - }); - - it(`should not call ESLint and return 2 when used with --fix with configType:${configType}`, async () => { - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().never(); - - localCLI = proxyquire("../../lib/cli", { - "./eslint": { LegacyESLint: fakeESLint }, - "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - "./shared/logging": log - }); + it(`should allow executing on text with configType:${configType}`, async () => { - const exitCode = await localCLI.execute("--fix --fix-dry-run .", "foo = bar;", useFlatConfig); + const report = [{ + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 2, + message: "Fake message" + } + ], + errorCount: 1, + warningCount: 0 + }]; - assert.strictEqual(exitCode, 2); - }); - }); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - describe("when passing --print-config", () => { + Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ActiveESLint.prototype)); + sinon.stub(fakeESLint.prototype, "lintText").returns(report); + sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().never(); - const originalCwd = process.cwd; + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); + "./shared/logging": log + }); - afterEach(() => { - process.cwd = originalCwd; - }); + const exitCode = await localCLI.execute("--fix-dry-run .", "foo = bar;", useFlatConfig); - it(`should print out the configuration with configType:${configType}`, async () => { - const filePath = getFixturePath("xxx.js"); + assert.strictEqual(exitCode, 1); + }); - const exitCode = await cli.execute(`--print-config ${filePath}`, null, useFlatConfig); + it(`should not call ESLint and return 2 when used with --fix with configType:${configType}`, async () => { - assert.isTrue(log.info.calledOnce); - assert.strictEqual(exitCode, 0); - }); + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().never(); - it(`should error if any positional file arguments are passed with configType:${configType}`, async () => { - const filePath1 = getFixturePath("files", "bar.js"); - const filePath2 = getFixturePath("files", "foo.js"); + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./shared/logging": log + }); - const exitCode = await cli.execute(`--print-config ${filePath1} ${filePath2}`, null, useFlatConfig); + const exitCode = await localCLI.execute("--fix --fix-dry-run .", "foo = bar;", useFlatConfig); - assert.isTrue(log.info.notCalled); - assert.isTrue(log.error.calledOnce); - assert.strictEqual(exitCode, 2); + assert.strictEqual(exitCode, 2); + }); }); - it(`should error out when executing on text with configType:${configType}`, async () => { - const exitCode = await cli.execute("--print-config=myFile.js", "foo = bar;", useFlatConfig); + describe("when passing --print-config", () => { - assert.isTrue(log.info.notCalled); - assert.isTrue(log.error.calledOnce); - assert.strictEqual(exitCode, 2); - }); - }); - - describe("when passing --report-unused-disable-directives", () => { - describe(`config type: ${configType}`, () => { - it("errors when --report-unused-disable-directives", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives --rule "'no-console': 'error'"`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); + const originalCwd = process.cwd; - assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); - assert.strictEqual(log.info.callCount, 1, "log.info is called once"); - assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); - assert.ok(log.info.firstCall.args[0].includes("1 error and 0 warning"), "has correct error and warning count"); - assert.strictEqual(exitCode, 1, "exit code should be 1"); + beforeEach(() => { + process.cwd = () => getFixturePath(); }); - it("errors when --report-unused-disable-directives-severity error", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity error --rule "'no-console': 'error'"`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); - - assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); - assert.strictEqual(log.info.callCount, 1, "log.info is called once"); - assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); - assert.ok(log.info.firstCall.args[0].includes("1 error and 0 warning"), "has correct error and warning count"); - assert.strictEqual(exitCode, 1, "exit code should be 1"); + afterEach(() => { + process.cwd = originalCwd; }); - it("errors when --report-unused-disable-directives-severity 2", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity 2 --rule "'no-console': 'error'"`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); - - assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); - assert.strictEqual(log.info.callCount, 1, "log.info is called once"); - assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); - assert.ok(log.info.firstCall.args[0].includes("1 error and 0 warning"), "has correct error and warning count"); - assert.strictEqual(exitCode, 1, "exit code should be 1"); - }); + it(`should print out the configuration with configType:${configType}`, async () => { + const filePath = getFixturePath("xxx.js"); - it("warns when --report-unused-disable-directives-severity warn", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity warn --rule "'no-console': 'error'""`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); + const exitCode = await cli.execute(`--print-config ${filePath}`, null, useFlatConfig); - assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); - assert.strictEqual(log.info.callCount, 1, "log.info is called once"); - assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); - assert.ok(log.info.firstCall.args[0].includes("0 errors and 1 warning"), "has correct error and warning count"); - assert.strictEqual(exitCode, 0, "exit code should be 0"); + assert.isTrue(log.info.calledOnce); + assert.strictEqual(exitCode, 0); }); - it("warns when --report-unused-disable-directives-severity 1", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity 1 --rule "'no-console': 'error'"`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); + it(`should error if any positional file arguments are passed with configType:${configType}`, async () => { + const filePath1 = getFixturePath("files", "bar.js"); + const filePath2 = getFixturePath("files", "foo.js"); + + const exitCode = await cli.execute(`--print-config ${filePath1} ${filePath2}`, null, useFlatConfig); - assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); - assert.strictEqual(log.info.callCount, 1, "log.info is called once"); - assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); - assert.ok(log.info.firstCall.args[0].includes("0 errors and 1 warning"), "has correct error and warning count"); - assert.strictEqual(exitCode, 0, "exit code should be 0"); + assert.isTrue(log.info.notCalled); + assert.isTrue(log.error.calledOnce); + assert.strictEqual(exitCode, 2); }); - it("does not report when --report-unused-disable-directives-severity off", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity off --rule "'no-console': 'error'"`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); + it(`should error out when executing on text with configType:${configType}`, async () => { + const exitCode = await cli.execute("--print-config=myFile.js", "foo = bar;", useFlatConfig); - assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); - assert.strictEqual(log.info.callCount, 0, "log.info should not be called"); - assert.strictEqual(exitCode, 0, "exit code should be 0"); + assert.isTrue(log.info.notCalled); + assert.isTrue(log.error.calledOnce); + assert.strictEqual(exitCode, 2); }); + }); - it("does not report when --report-unused-disable-directives-severity 0", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity 0 --rule "'no-console': 'error'"`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); + describe("when passing --report-unused-disable-directives", () => { + describe(`config type: ${configType}`, () => { + it("errors when --report-unused-disable-directives", async () => { + const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives --rule "'no-console': 'error'"`, + "foo(); // eslint-disable-line no-console", + useFlatConfig); - assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); - assert.strictEqual(log.info.callCount, 0, "log.info should not be called"); - assert.strictEqual(exitCode, 0, "exit code should be 0"); - }); + assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); + assert.strictEqual(log.info.callCount, 1, "log.info is called once"); + assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); + assert.ok(log.info.firstCall.args[0].includes("1 error and 0 warning"), "has correct error and warning count"); + assert.strictEqual(exitCode, 1, "exit code should be 1"); + }); - it("fails when passing invalid string for --report-unused-disable-directives-severity", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity foo`, null, useFlatConfig); + it("errors when --report-unused-disable-directives-severity error", async () => { + const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity error --rule "'no-console': 'error'"`, + "foo(); // eslint-disable-line no-console", + useFlatConfig); - assert.strictEqual(log.info.callCount, 0, "log.info should not be called"); - assert.strictEqual(log.error.callCount, 1, "log.error should be called once"); + assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); + assert.strictEqual(log.info.callCount, 1, "log.info is called once"); + assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); + assert.ok(log.info.firstCall.args[0].includes("1 error and 0 warning"), "has correct error and warning count"); + assert.strictEqual(exitCode, 1, "exit code should be 1"); + }); - const lines = ["Option report-unused-disable-directives-severity: 'foo' not one of off, warn, error, 0, 1, or 2."]; + it("errors when --report-unused-disable-directives-severity 2", async () => { + const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity 2 --rule "'no-console': 'error'"`, + "foo(); // eslint-disable-line no-console", + useFlatConfig); - if (useFlatConfig) { - lines.push("You're using eslint.config.js, some command line flags are no longer available. Please see https://eslint.org/docs/latest/use/command-line-interface for details."); - } - assert.deepStrictEqual(log.error.firstCall.args, [lines.join("\n")], "has the right text to log.error"); - assert.strictEqual(exitCode, 2, "exit code should be 2"); - }); + assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); + assert.strictEqual(log.info.callCount, 1, "log.info is called once"); + assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); + assert.ok(log.info.firstCall.args[0].includes("1 error and 0 warning"), "has correct error and warning count"); + assert.strictEqual(exitCode, 1, "exit code should be 1"); + }); - it("fails when passing both --report-unused-disable-directives and --report-unused-disable-directives-severity", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives --report-unused-disable-directives-severity warn`, null, useFlatConfig); + it("warns when --report-unused-disable-directives-severity warn", async () => { + const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity warn --rule "'no-console': 'error'""`, + "foo(); // eslint-disable-line no-console", + useFlatConfig); - assert.strictEqual(log.info.callCount, 0, "log.info should not be called"); - assert.strictEqual(log.error.callCount, 1, "log.error should be called once"); - assert.deepStrictEqual(log.error.firstCall.args, ["The --report-unused-disable-directives option and the --report-unused-disable-directives-severity option cannot be used together."], "has the right text to log.error"); - assert.strictEqual(exitCode, 2, "exit code should be 2"); - }); + assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); + assert.strictEqual(log.info.callCount, 1, "log.info is called once"); + assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); + assert.ok(log.info.firstCall.args[0].includes("0 errors and 1 warning"), "has correct error and warning count"); + assert.strictEqual(exitCode, 0, "exit code should be 0"); + }); - it("warns by default in flat config only", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --rule "'no-console': 'error'"`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); + it("warns when --report-unused-disable-directives-severity 1", async () => { + const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity 1 --rule "'no-console': 'error'"`, + "foo(); // eslint-disable-line no-console", + useFlatConfig); - if (useFlatConfig) { assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); assert.strictEqual(log.info.callCount, 1, "log.info is called once"); assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); assert.ok(log.info.firstCall.args[0].includes("0 errors and 1 warning"), "has correct error and warning count"); assert.strictEqual(exitCode, 0, "exit code should be 0"); - } else { + }); + + it("does not report when --report-unused-disable-directives-severity off", async () => { + const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity off --rule "'no-console': 'error'"`, + "foo(); // eslint-disable-line no-console", + useFlatConfig); + assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); assert.strictEqual(log.info.callCount, 0, "log.info should not be called"); assert.strictEqual(exitCode, 0, "exit code should be 0"); - } + }); + + it("does not report when --report-unused-disable-directives-severity 0", async () => { + const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity 0 --rule "'no-console': 'error'"`, + "foo(); // eslint-disable-line no-console", + useFlatConfig); + + assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); + assert.strictEqual(log.info.callCount, 0, "log.info should not be called"); + assert.strictEqual(exitCode, 0, "exit code should be 0"); + }); + + it("fails when passing invalid string for --report-unused-disable-directives-severity", async () => { + const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity foo`, null, useFlatConfig); + + assert.strictEqual(log.info.callCount, 0, "log.info should not be called"); + assert.strictEqual(log.error.callCount, 1, "log.error should be called once"); + + const lines = ["Option report-unused-disable-directives-severity: 'foo' not one of off, warn, error, 0, 1, or 2."]; + + if (useFlatConfig) { + lines.push("You're using eslint.config.js, some command line flags are no longer available. Please see https://eslint.org/docs/latest/use/command-line-interface for details."); + } + assert.deepStrictEqual(log.error.firstCall.args, [lines.join("\n")], "has the right text to log.error"); + assert.strictEqual(exitCode, 2, "exit code should be 2"); + }); + + it("fails when passing both --report-unused-disable-directives and --report-unused-disable-directives-severity", async () => { + const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives --report-unused-disable-directives-severity warn`, null, useFlatConfig); + + assert.strictEqual(log.info.callCount, 0, "log.info should not be called"); + assert.strictEqual(log.error.callCount, 1, "log.error should be called once"); + assert.deepStrictEqual(log.error.firstCall.args, ["The --report-unused-disable-directives option and the --report-unused-disable-directives-severity option cannot be used together."], "has the right text to log.error"); + assert.strictEqual(exitCode, 2, "exit code should be 2"); + }); + + it("warns by default in flat config only", async () => { + const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --rule "'no-console': 'error'"`, + "foo(); // eslint-disable-line no-console", + useFlatConfig); + + if (useFlatConfig) { + assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); + assert.strictEqual(log.info.callCount, 1, "log.info is called once"); + assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); + assert.ok(log.info.firstCall.args[0].includes("0 errors and 1 warning"), "has correct error and warning count"); + assert.strictEqual(exitCode, 0, "exit code should be 0"); + } else { + assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); + assert.strictEqual(log.info.callCount, 0, "log.info should not be called"); + assert.strictEqual(exitCode, 0, "exit code should be 0"); + } + }); }); }); - }); - // --------- - }); + // --------- + }); - describe("when given a config file", () => { - it("should load the specified config file", async () => { - const configPath = getFixturePath("eslint.config.js"); - const filePath = getFixturePath("passing.js"); + describe("when given a config file", () => { + it("should load the specified config file", async () => { + const configPath = getFixturePath("eslint.config.js"); + const filePath = getFixturePath("passing.js"); - await cli.execute(`--config ${configPath} ${filePath}`); + await cli.execute(`--config ${configPath} ${filePath}`); + }); }); - }); - describe("eslintrc Only", () => { + describe("eslintrc Only", () => { - describe("Environments", () => { + describe("Environments", () => { - describe("when given a config with environment set to browser", () => { - it("should execute without any errors", async () => { - const configPath = getFixturePath("configurations", "env-browser.json"); - const filePath = getFixturePath("globals-browser.js"); - const code = `--config ${configPath} ${filePath}`; + describe("when given a config with environment set to browser", () => { + it("should execute without any errors", async () => { + const configPath = getFixturePath("configurations", "env-browser.json"); + const filePath = getFixturePath("globals-browser.js"); + const code = `--config ${configPath} ${filePath}`; - const exit = await cli.execute(code, null, false); + const exit = await cli.execute(code, null, false); - assert.strictEqual(exit, 0); + assert.strictEqual(exit, 0); + }); }); - }); - describe("when given a config with environment set to Node.js", () => { - it("should execute without any errors", async () => { - const configPath = getFixturePath("configurations", "env-node.json"); - const filePath = getFixturePath("globals-node.js"); - const code = `--config ${configPath} ${filePath}`; + describe("when given a config with environment set to Node.js", () => { + it("should execute without any errors", async () => { + const configPath = getFixturePath("configurations", "env-node.json"); + const filePath = getFixturePath("globals-node.js"); + const code = `--config ${configPath} ${filePath}`; - const exit = await cli.execute(code, null, false); + const exit = await cli.execute(code, null, false); - assert.strictEqual(exit, 0); + assert.strictEqual(exit, 0); + }); }); - }); - describe("when given a config with environment set to Nashorn", () => { - it("should execute without any errors", async () => { - const configPath = getFixturePath("configurations", "env-nashorn.json"); - const filePath = getFixturePath("globals-nashorn.js"); - const code = `--config ${configPath} ${filePath}`; + describe("when given a config with environment set to Nashorn", () => { + it("should execute without any errors", async () => { + const configPath = getFixturePath("configurations", "env-nashorn.json"); + const filePath = getFixturePath("globals-nashorn.js"); + const code = `--config ${configPath} ${filePath}`; - const exit = await cli.execute(code, null, false); + const exit = await cli.execute(code, null, false); - assert.strictEqual(exit, 0); + assert.strictEqual(exit, 0); + }); }); - }); - describe("when given a config with environment set to WebExtensions", () => { - it("should execute without any errors", async () => { - const configPath = getFixturePath("configurations", "env-webextensions.json"); - const filePath = getFixturePath("globals-webextensions.js"); - const code = `--config ${configPath} ${filePath}`; + describe("when given a config with environment set to WebExtensions", () => { + it("should execute without any errors", async () => { + const configPath = getFixturePath("configurations", "env-webextensions.json"); + const filePath = getFixturePath("globals-webextensions.js"); + const code = `--config ${configPath} ${filePath}`; - const exit = await cli.execute(code, null, false); + const exit = await cli.execute(code, null, false); - assert.strictEqual(exit, 0); + assert.strictEqual(exit, 0); + }); }); }); - }); - describe("when loading a custom rule", () => { - it("should return an error when rule isn't found", async () => { - const rulesPath = getFixturePath("rules", "wrong"); - const configPath = getFixturePath("rules", "eslint.json"); - const filePath = getFixturePath("rules", "test", "test-custom-rule.js"); - const code = `--rulesdir ${rulesPath} --config ${configPath} --no-ignore ${filePath}`; + describe("when loading a custom rule", () => { + it("should return an error when rule isn't found", async () => { + const rulesPath = getFixturePath("rules", "wrong"); + const configPath = getFixturePath("rules", "eslint.json"); + const filePath = getFixturePath("rules", "test", "test-custom-rule.js"); + const code = `--rulesdir ${rulesPath} --config ${configPath} --no-ignore ${filePath}`; - await stdAssert.rejects(async () => { - const exit = await cli.execute(code, null, false); + await stdAssert.rejects(async () => { + const exit = await cli.execute(code, null, false); - assert.strictEqual(exit, 2); - }, /Error while loading rule 'custom-rule': Boom!/u); - }); + assert.strictEqual(exit, 2); + }, /Error while loading rule 'custom-rule': Boom!/u); + }); - it("should return a warning when rule is matched", async () => { - const rulesPath = getFixturePath("rules"); - const configPath = getFixturePath("rules", "eslint.json"); - const filePath = getFixturePath("rules", "test", "test-custom-rule.js"); - const code = `--rulesdir ${rulesPath} --config ${configPath} --no-ignore ${filePath}`; + it("should return a warning when rule is matched", async () => { + const rulesPath = getFixturePath("rules"); + const configPath = getFixturePath("rules", "eslint.json"); + const filePath = getFixturePath("rules", "test", "test-custom-rule.js"); + const code = `--rulesdir ${rulesPath} --config ${configPath} --no-ignore ${filePath}`; - await cli.execute(code, null, false); + await cli.execute(code, null, false); - assert.isTrue(log.info.calledOnce); - assert.isTrue(log.info.neverCalledWith("")); - }); + assert.isTrue(log.info.calledOnce); + assert.isTrue(log.info.neverCalledWith("")); + }); - it("should return warnings from multiple rules in different directories", async () => { - const rulesPath = getFixturePath("rules", "dir1"); - const rulesPath2 = getFixturePath("rules", "dir2"); - const configPath = getFixturePath("rules", "multi-rulesdirs.json"); - const filePath = getFixturePath("rules", "test-multi-rulesdirs.js"); - const code = `--rulesdir ${rulesPath} --rulesdir ${rulesPath2} --config ${configPath} --no-ignore ${filePath}`; - const exit = await cli.execute(code, null, false); - - const call = log.info.getCall(0); - - assert.isTrue(log.info.calledOnce); - assert.isTrue(call.args[0].includes("String!")); - assert.isTrue(call.args[0].includes("Literal!")); - assert.isTrue(call.args[0].includes("2 problems")); - assert.isTrue(log.info.neverCalledWith("")); - assert.strictEqual(exit, 1); - }); + it("should return warnings from multiple rules in different directories", async () => { + const rulesPath = getFixturePath("rules", "dir1"); + const rulesPath2 = getFixturePath("rules", "dir2"); + const configPath = getFixturePath("rules", "multi-rulesdirs.json"); + const filePath = getFixturePath("rules", "test-multi-rulesdirs.js"); + const code = `--rulesdir ${rulesPath} --rulesdir ${rulesPath2} --config ${configPath} --no-ignore ${filePath}`; + const exit = await cli.execute(code, null, false); + const call = log.info.getCall(0); - }); + assert.isTrue(log.info.calledOnce); + assert.isTrue(call.args[0].includes("String!")); + assert.isTrue(call.args[0].includes("Literal!")); + assert.isTrue(call.args[0].includes("2 problems")); + assert.isTrue(log.info.neverCalledWith("")); + assert.strictEqual(exit, 1); + }); - describe("when executing with no-eslintrc flag", () => { - it("should ignore a local config file", async () => { - const filePath = getFixturePath("eslintrc", "quotes.js"); - const exit = await cli.execute(`--no-eslintrc --no-ignore ${filePath}`, null, false); - assert.isTrue(log.info.notCalled); - assert.strictEqual(exit, 0); }); - }); - describe("when executing without no-eslintrc flag", () => { - it("should load a local config file", async () => { - const filePath = getFixturePath("eslintrc", "quotes.js"); - const exit = await cli.execute(`--no-ignore ${filePath}`, null, false); + describe("when executing with no-eslintrc flag", () => { + it("should ignore a local config file", async () => { + const filePath = getFixturePath("eslintrc", "quotes.js"); + const exit = await cli.execute(`--no-eslintrc --no-ignore ${filePath}`, null, false); - assert.isTrue(log.info.calledOnce); - assert.strictEqual(exit, 1); + assert.isTrue(log.info.notCalled); + assert.strictEqual(exit, 0); + }); }); - }); - describe("when executing without env flag", () => { - it("should not define environment-specific globals", async () => { - const files = [ - getFixturePath("globals-browser.js"), - getFixturePath("globals-node.js") - ]; + describe("when executing without no-eslintrc flag", () => { + it("should load a local config file", async () => { + const filePath = getFixturePath("eslintrc", "quotes.js"); + const exit = await cli.execute(`--no-ignore ${filePath}`, null, false); - await cli.execute(`--no-eslintrc --config ./packages/js/src/configs/eslint-recommended.js --no-ignore ${files.join(" ")}`, null, false); - - assert.strictEqual(log.info.args[0][0].split("\n").length, 10); + assert.isTrue(log.info.calledOnce); + assert.strictEqual(exit, 1); + }); }); - }); + describe("when executing without env flag", () => { + it("should not define environment-specific globals", async () => { + const files = [ + getFixturePath("globals-browser.js"), + getFixturePath("globals-node.js") + ]; - describe("when supplied with a plugin", () => { - it("should pass plugins to ESLint", async () => { - const examplePluginName = "eslint-plugin-example"; + await cli.execute(`--no-eslintrc --config ./packages/js/src/configs/eslint-recommended.js --no-ignore ${files.join(" ")}`, null, false); - await verifyESLintOpts(`--no-ignore --plugin ${examplePluginName} foo.js`, { - overrideConfig: { - plugins: [examplePluginName] - } + assert.strictEqual(log.info.args[0][0].split("\n").length, 10); }); }); - }); - describe("when supplied with a plugin-loading path", () => { - it("should pass the option to ESLint", async () => { - const examplePluginDirPath = "foo/bar"; + describe("when supplied with a plugin", () => { + it("should pass plugins to ESLint", async () => { + const examplePluginName = "eslint-plugin-example"; - await verifyESLintOpts(`--resolve-plugins-relative-to ${examplePluginDirPath} foo.js`, { - resolvePluginsRelativeTo: examplePluginDirPath + await verifyESLintOpts(`--no-ignore --plugin ${examplePluginName} foo.js`, { + overrideConfig: { + plugins: [examplePluginName] + } + }); }); + }); - }); + describe("when supplied with a plugin-loading path", () => { + it("should pass the option to ESLint", async () => { + const examplePluginDirPath = "foo/bar"; + + await verifyESLintOpts(`--resolve-plugins-relative-to ${examplePluginDirPath} foo.js`, { + resolvePluginsRelativeTo: examplePluginDirPath + }); + }); + }); - }); + }); - describe("flat Only", () => { - describe("`--plugin` option", () => { + describe("flat Only", () => { - let originalCwd; + describe("`--plugin` option", () => { - beforeEach(() => { - originalCwd = process.cwd(); - process.chdir(getFixturePath("plugins")); - }); + let originalCwd; - afterEach(() => { - process.chdir(originalCwd); - originalCwd = void 0; - }); + beforeEach(() => { + originalCwd = process.cwd(); + process.chdir(getFixturePath("plugins")); + }); - it("should load a plugin from a CommonJS package", async () => { - const code = "--plugin hello-cjs --rule 'hello-cjs/hello: error' ../files/*.js"; + afterEach(() => { + process.chdir(originalCwd); + originalCwd = void 0; + }); - const exitCode = await cli.execute(code, null, true); + it("should load a plugin from a CommonJS package", async () => { + const code = "--plugin hello-cjs --rule 'hello-cjs/hello: error' ../files/*.js"; - assert.strictEqual(exitCode, 1); - assert.ok(log.info.calledOnce); - assert.include(log.info.firstCall.firstArg, "Hello CommonJS!"); - }); + const exitCode = await cli.execute(code, null, true); - it("should load a plugin from an ESM package", async () => { - const code = "--plugin hello-esm --rule 'hello-esm/hello: error' ../files/*.js"; + assert.strictEqual(exitCode, 1); + assert.ok(log.info.calledOnce); + assert.include(log.info.firstCall.firstArg, "Hello CommonJS!"); + }); - const exitCode = await cli.execute(code, null, true); + it("should load a plugin from an ESM package", async () => { + const code = "--plugin hello-esm --rule 'hello-esm/hello: error' ../files/*.js"; - assert.strictEqual(exitCode, 1); - assert.ok(log.info.calledOnce); - assert.include(log.info.firstCall.firstArg, "Hello ESM!"); - }); + const exitCode = await cli.execute(code, null, true); + + assert.strictEqual(exitCode, 1); + assert.ok(log.info.calledOnce); + assert.include(log.info.firstCall.firstArg, "Hello ESM!"); + }); - it("should load multiple plugins", async () => { - const code = "--plugin 'hello-cjs, hello-esm' --rule 'hello-cjs/hello: warn, hello-esm/hello: error' ../files/*.js"; + it("should load multiple plugins", async () => { + const code = "--plugin 'hello-cjs, hello-esm' --rule 'hello-cjs/hello: warn, hello-esm/hello: error' ../files/*.js"; - const exitCode = await cli.execute(code, null, true); + const exitCode = await cli.execute(code, null, true); - assert.strictEqual(exitCode, 1); - assert.ok(log.info.calledOnce); - assert.include(log.info.firstCall.firstArg, "Hello CommonJS!"); - assert.include(log.info.firstCall.firstArg, "Hello ESM!"); - }); + assert.strictEqual(exitCode, 1); + assert.ok(log.info.calledOnce); + assert.include(log.info.firstCall.firstArg, "Hello CommonJS!"); + assert.include(log.info.firstCall.firstArg, "Hello ESM!"); + }); - it("should resolve plugins specified with 'eslint-plugin-'", async () => { - const code = "--plugin 'eslint-plugin-schema-array, @scope/eslint-plugin-example' --rule 'schema-array/rule1: warn, @scope/example/test: warn' ../passing.js"; + it("should resolve plugins specified with 'eslint-plugin-'", async () => { + const code = "--plugin 'eslint-plugin-schema-array, @scope/eslint-plugin-example' --rule 'schema-array/rule1: warn, @scope/example/test: warn' ../passing.js"; - const exitCode = await cli.execute(code, null, true); + const exitCode = await cli.execute(code, null, true); - assert.strictEqual(exitCode, 0); - }); + assert.strictEqual(exitCode, 0); + }); - it("should resolve plugins in the parent directory's node_module subdirectory", async () => { - process.chdir("subdir"); - const code = "--plugin 'example, @scope/example' file.js"; + it("should resolve plugins in the parent directory's node_module subdirectory", async () => { + process.chdir("subdir"); + const code = "--plugin 'example, @scope/example' file.js"; - const exitCode = await cli.execute(code, null, true); + const exitCode = await cli.execute(code, null, true); - assert.strictEqual(exitCode, 0); - }); + assert.strictEqual(exitCode, 0); + }); - it("should fail if a plugin is not found", async () => { - const code = "--plugin 'example, no-such-plugin' ../passing.js"; - - await stdAssert.rejects( - cli.execute(code, null, true), - ({ message }) => { - assert( - message.startsWith("Cannot find module 'eslint-plugin-no-such-plugin'\n"), - `Unexpected error message:\n${message}` - ); - return true; - } - ); - }); + it("should fail if a plugin is not found", async () => { + const code = "--plugin 'example, no-such-plugin' ../passing.js"; - it("should fail if a plugin throws an error while loading", async () => { - const code = "--plugin 'example, throws-on-load' ../passing.js"; + await stdAssert.rejects( + cli.execute(code, null, true), + ({ message }) => { + assert( + message.startsWith("Cannot find module 'eslint-plugin-no-such-plugin'\n"), + `Unexpected error message:\n${message}` + ); + return true; + } + ); + }); - await stdAssert.rejects( - cli.execute(code, null, true), - { message: "error thrown while loading this module" } - ); - }); + it("should fail if a plugin throws an error while loading", async () => { + const code = "--plugin 'example, throws-on-load' ../passing.js"; - it("should fail to load a plugin from a package without a default export", async () => { - const code = "--plugin 'example, no-default-export' ../passing.js"; + await stdAssert.rejects( + cli.execute(code, null, true), + { message: "error thrown while loading this module" } + ); + }); + + it("should fail to load a plugin from a package without a default export", async () => { + const code = "--plugin 'example, no-default-export' ../passing.js"; - await stdAssert.rejects( - cli.execute(code, null, true), - { message: '"eslint-plugin-no-default-export" cannot be used with the `--plugin` option because its default module does not provide a `default` export' } - ); + await stdAssert.rejects( + cli.execute(code, null, true), + { message: '"eslint-plugin-no-default-export" cannot be used with the `--plugin` option because its default module does not provide a `default` export' } + ); + }); }); }); + }); }); From 0d8cf6350ce3dc417d6e23922e6d4ad03952aaaa Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 17 Apr 2024 02:17:38 -0700 Subject: [PATCH 089/166] fix: EMFILE errors (#18313) * fix: EMFILE errors fixes #18301 * Move catch handler * Add intentional EMFILE failure * Use actual limit on Linux systems in test * Adjust emfile test limit * Fix linting error * Fix test for MacOS * Up MacOS limit in test * Move tmp file output directory * Update .gitignore Co-authored-by: Francesco Trotta --------- Co-authored-by: Francesco Trotta --- .github/workflows/ci.yml | 2 + lib/eslint/eslint.js | 9 +- package.json | 4 +- tests/fixtures/emfile/eslint.config.js | 5 ++ tools/check-emfile-handling.js | 109 +++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/emfile/eslint.config.js create mode 100644 tools/check-emfile-handling.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1555a688fa3..e06d299bdc2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,6 +65,8 @@ jobs: run: node Makefile mocha - name: Fuzz Test run: node Makefile fuzz + - name: Test EMFILE Handling + run: npm run test:emfile test_on_browser: name: Browser Test diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 4f2c1f3a86c..d3875425e01 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -42,6 +42,7 @@ const { const { pathToFileURL } = require("url"); const { FlatConfigArray } = require("../config/flat-config-array"); const LintResultCache = require("../cli-engine/lint-result-cache"); +const { Retrier } = require("@humanwhocodes/retry"); /* * This is necessary to allow overwriting writeFile for testing purposes. @@ -851,6 +852,8 @@ class ESLint { errorOnUnmatchedPattern }); const controller = new AbortController(); + const retryCodes = new Set(["ENFILE", "EMFILE"]); + const retrier = new Retrier(error => retryCodes.has(error.code)); debug(`${filePaths.length} files found in: ${Date.now() - startTime}ms`); @@ -919,7 +922,7 @@ class ESLint { fixer = message => shouldMessageBeFixed(message, config, fixTypesSet) && originalFix(message); } - return fs.readFile(filePath, { encoding: "utf8", signal: controller.signal }) + return retrier.retry(() => fs.readFile(filePath, { encoding: "utf8", signal: controller.signal }) .then(text => { // fail immediately if an error occurred in another file @@ -949,11 +952,11 @@ class ESLint { } return result; - }).catch(error => { + })) + .catch(error => { controller.abort(error); throw error; }); - }) ); diff --git a/package.json b/package.json index ba0f59f0c28..366974e51b2 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "test:browser": "node Makefile.js wdio", "test:cli": "mocha", "test:fuzz": "node Makefile.js fuzz", - "test:performance": "node Makefile.js perf" + "test:performance": "node Makefile.js perf", + "test:emfile": "node tools/check-emfile-handling.js" }, "gitHooks": { "pre-commit": "lint-staged" @@ -71,6 +72,7 @@ "@eslint/js": "9.0.0", "@humanwhocodes/config-array": "^0.12.3", "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.2.3", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", "chalk": "^4.0.0", diff --git a/tests/fixtures/emfile/eslint.config.js b/tests/fixtures/emfile/eslint.config.js new file mode 100644 index 00000000000..bdfb8f7349f --- /dev/null +++ b/tests/fixtures/emfile/eslint.config.js @@ -0,0 +1,5 @@ +module.exports = { + rules: { + "no-unused-vars": "error" + } +}; diff --git a/tools/check-emfile-handling.js b/tools/check-emfile-handling.js new file mode 100644 index 00000000000..be0fd262ebc --- /dev/null +++ b/tools/check-emfile-handling.js @@ -0,0 +1,109 @@ +/** + * @fileoverview A utility to test that ESLint doesn't crash with EMFILE/ENFILE errors. + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const fs = require("fs"); +const { readFile } = require("fs/promises"); +const { execSync } = require("child_process"); +const os = require("os"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const OUTPUT_DIRECTORY = "tmp/emfile-check"; +const CONFIG_DIRECTORY = "tests/fixtures/emfile"; + +/* + * Every operating system has a different limit for the number of files that can + * be opened at once. This number is meant to be larger than the default limit + * on most systems. + * + * Linux systems typically start at a count of 1024 and may be increased to 4096. + * MacOS Sonoma v14.4 has a limit of 10496. + * Windows has no hard limit but may be limited by available memory. + */ +const DEFAULT_FILE_COUNT = 15000; +let FILE_COUNT = DEFAULT_FILE_COUNT; + +// if the platform isn't windows, get the ulimit to see what the actual limit is +if (os.platform() !== "win32") { + try { + FILE_COUNT = parseInt(execSync("ulimit -n").toString().trim(), 10) + 1; + + console.log(`Detected Linux file limit of ${FILE_COUNT}.`); + + // if we're on a Mac, make sure the limit isn't high enough to cause a call stack error + if (os.platform() === "darwin") { + FILE_COUNT = Math.min(FILE_COUNT, 100000); + } + } catch { + + // ignore error and use default + } +} + +/** + * Generates files in a directory. + * @returns {void} + */ +function generateFiles() { + + fs.mkdirSync(OUTPUT_DIRECTORY, { recursive: true }); + + for (let i = 0; i < FILE_COUNT; i++) { + const fileName = `file_${i}.js`; + const fileContent = `// This is file ${i}`; + + fs.writeFileSync(`${OUTPUT_DIRECTORY}/${fileName}`, fileContent); + } + +} + +/** + * Generates an EMFILE error by reading all files in the output directory. + * @returns {Promise} A promise that resolves with the contents of all files. + */ +function generateEmFileError() { + return Promise.all( + Array.from({ length: FILE_COUNT }, (_, i) => { + const fileName = `file_${i}.js`; + + return readFile(`${OUTPUT_DIRECTORY}/${fileName}`); + }) + ); +} + +//------------------------------------------------------------------------------ +// Main +//------------------------------------------------------------------------------ + +console.log(`Generating ${FILE_COUNT} files in ${OUTPUT_DIRECTORY}...`); +generateFiles(); + +console.log("Running ESLint..."); +execSync(`node bin/eslint.js ${OUTPUT_DIRECTORY} -c ${CONFIG_DIRECTORY}/eslint.config.js`, { stdio: "inherit" }); +console.log("✅ No errors encountered running ESLint."); + +console.log("Checking that this number of files would cause an EMFILE error..."); +generateEmFileError() + .then(() => { + throw new Error("EMFILE error not encountered."); + }) + .catch(error => { + if (error.code === "EMFILE") { + console.log("✅ EMFILE error encountered:", error.message); + } else if (error.code === "ENFILE") { + console.log("✅ ENFILE error encountered:", error.message); + } else { + console.error("❌ Unexpected error encountered:", error.message); + throw error; + } + }); From 0588fc5ecb87fddd70e1848e417ba712b48473c3 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 17 Apr 2024 04:58:13 -0700 Subject: [PATCH 090/166] refactor: Move directive gathering to SourceCode (#18328) * feat: Move directive gathering to SourceCode refs #16999 * Add caching * Update lib/linter/apply-disable-directives.js Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- lib/linter/apply-disable-directives.js | 48 +-- lib/linter/linter.js | 84 ++--- lib/source-code/source-code.js | 128 +++++++ tests/lib/linter/apply-disable-directives.js | 356 +++++++++---------- 4 files changed, 357 insertions(+), 259 deletions(-) diff --git a/lib/linter/apply-disable-directives.js b/lib/linter/apply-disable-directives.js index f511821dd53..0ed11bb197f 100644 --- a/lib/linter/apply-disable-directives.js +++ b/lib/linter/apply-disable-directives.js @@ -38,16 +38,16 @@ function compareLocations(itemA, itemB) { * @param {Iterable} directives Unused directives to be removed. * @returns {Directive[][]} Directives grouped by their parent comment. */ -function groupByParentComment(directives) { +function groupByParentDirective(directives) { const groups = new Map(); for (const directive of directives) { - const { unprocessedDirective: { parentComment } } = directive; + const { unprocessedDirective: { parentDirective } } = directive; - if (groups.has(parentComment)) { - groups.get(parentComment).push(directive); + if (groups.has(parentDirective)) { + groups.get(parentDirective).push(directive); } else { - groups.set(parentComment, [directive]); + groups.set(parentDirective, [directive]); } } @@ -57,19 +57,19 @@ function groupByParentComment(directives) { /** * Creates removal details for a set of directives within the same comment. * @param {Directive[]} directives Unused directives to be removed. - * @param {Token} commentToken The backing Comment token. + * @param {Token} node The backing Comment token. * @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems. */ -function createIndividualDirectivesRemoval(directives, commentToken) { +function createIndividualDirectivesRemoval(directives, node) { /* - * `commentToken.value` starts right after `//` or `/*`. + * `node.value` starts right after `//` or `/*`. * All calculated offsets will be relative to this index. */ - const commentValueStart = commentToken.range[0] + "//".length; + const commentValueStart = node.range[0] + "//".length; // Find where the list of rules starts. `\S+` matches with the directive name (e.g. `eslint-disable-line`) - const listStartOffset = /^\s*\S+\s+/u.exec(commentToken.value)[0].length; + const listStartOffset = /^\s*\S+\s+/u.exec(node.value)[0].length; /* * Get the list text without any surrounding whitespace. In order to preserve the original @@ -78,7 +78,7 @@ function createIndividualDirectivesRemoval(directives, commentToken) { * // eslint-disable-line rule-one , rule-two , rule-three -- comment * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ - const listText = commentToken.value + const listText = node.value .slice(listStartOffset) // remove directive name and all whitespace before the list .split(/\s-{2,}\s/u)[0] // remove `-- comment`, if it exists .trimEnd(); // remove all whitespace after the list @@ -159,13 +159,13 @@ function createIndividualDirectivesRemoval(directives, commentToken) { } /** - * Creates a description of deleting an entire unused disable comment. + * Creates a description of deleting an entire unused disable directive. * @param {Directive[]} directives Unused directives to be removed. - * @param {Token} commentToken The backing Comment token. - * @returns {{ description, fix, unprocessedDirective }} Details for later creation of an output Problem. + * @param {Token} node The backing Comment token. + * @returns {{ description, fix, unprocessedDirective }} Details for later creation of an output problem. */ -function createCommentRemoval(directives, commentToken) { - const { range } = commentToken; +function createDirectiveRemoval(directives, node) { + const { range } = node; const ruleIds = directives.filter(directive => directive.ruleId).map(directive => `'${directive.ruleId}'`); return { @@ -186,20 +186,20 @@ function createCommentRemoval(directives, commentToken) { * @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems. */ function processUnusedDirectives(allDirectives) { - const directiveGroups = groupByParentComment(allDirectives); + const directiveGroups = groupByParentDirective(allDirectives); return directiveGroups.flatMap( directives => { - const { parentComment } = directives[0].unprocessedDirective; - const remainingRuleIds = new Set(parentComment.ruleIds); + const { parentDirective } = directives[0].unprocessedDirective; + const remainingRuleIds = new Set(parentDirective.ruleIds); for (const directive of directives) { remainingRuleIds.delete(directive.ruleId); } return remainingRuleIds.size - ? createIndividualDirectivesRemoval(directives, parentComment.commentToken) - : [createCommentRemoval(directives, parentComment.commentToken)]; + ? createIndividualDirectivesRemoval(directives, parentDirective.node) + : [createDirectiveRemoval(directives, parentDirective.node)]; } ); } @@ -372,7 +372,7 @@ function applyDirectives(options) { const unusedDirectives = processed .map(({ description, fix, unprocessedDirective }) => { - const { parentComment, type, line, column } = unprocessedDirective; + const { parentDirective, type, line, column } = unprocessedDirective; let message; @@ -388,8 +388,8 @@ function applyDirectives(options) { return { ruleId: null, message, - line: type === "disable-next-line" ? parentComment.commentToken.loc.start.line : line, - column: type === "disable-next-line" ? parentComment.commentToken.loc.start.column + 1 : column, + line: type === "disable-next-line" ? parentDirective.node.loc.start.line : line, + column: type === "disable-next-line" ? parentDirective.node.loc.start.column + 1 : column, severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2, nodeType: null, ...options.disableFixes ? {} : { fix } diff --git a/lib/linter/linter.js b/lib/linter/linter.js index 22cf17b54ba..c76b85224d1 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -273,23 +273,21 @@ function createLintingProblem(options) { * Creates a collection of disable directives from a comment * @param {Object} options to create disable directives * @param {("disable"|"enable"|"disable-line"|"disable-next-line")} options.type The type of directive comment - * @param {token} options.commentToken The Comment token * @param {string} options.value The value after the directive in the comment * comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`) * @param {string} options.justification The justification of the directive - * @param {function(string): {create: Function}} options.ruleMapper A map from rule IDs to defined rules + * @param {ASTNode|token} options.node The Comment node/token. + * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules * @returns {Object} Directives and problems from the comment */ -function createDisableDirectives(options) { - const { commentToken, type, value, justification, ruleMapper } = options; +function createDisableDirectives({ type, value, justification, node }, ruleMapper) { const ruleIds = Object.keys(commentParser.parseListConfig(value)); const directiveRules = ruleIds.length ? ruleIds : [null]; const result = { directives: [], // valid disable directives directiveProblems: [] // problems in directives }; - - const parentComment = { commentToken, ruleIds }; + const parentDirective = { node, ruleIds }; for (const ruleId of directiveRules) { @@ -297,25 +295,25 @@ function createDisableDirectives(options) { if (ruleId === null || !!ruleMapper(ruleId)) { if (type === "disable-next-line") { result.directives.push({ - parentComment, + parentDirective, type, - line: commentToken.loc.end.line, - column: commentToken.loc.end.column + 1, + line: node.loc.end.line, + column: node.loc.end.column + 1, ruleId, justification }); } else { result.directives.push({ - parentComment, + parentDirective, type, - line: commentToken.loc.start.line, - column: commentToken.loc.start.column + 1, + line: node.loc.start.line, + column: node.loc.start.column + 1, ruleId, justification }); } } else { - result.directiveProblems.push(createLintingProblem({ ruleId, loc: commentToken.loc })); + result.directiveProblems.push(createLintingProblem({ ruleId, loc: node.loc })); } } return result; @@ -388,8 +386,12 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config) case "eslint-disable-next-line": case "eslint-disable-line": { const directiveType = directiveText.slice("eslint-".length); - const options = { commentToken: comment, type: directiveType, value: directiveValue, justification: justificationPart, ruleMapper }; - const { directives, directiveProblems } = createDisableDirectives(options); + const { directives, directiveProblems } = createDisableDirectives({ + type: directiveType, + value: directiveValue, + justification: justificationPart, + node: comment + }, ruleMapper); disableDirectives.push(...directives); problems.push(...directiveProblems); @@ -543,53 +545,21 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config) * A collection of the directive comments that were found, along with any problems that occurred when parsing */ function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper) { - const problems = []; const disableDirectives = []; + const problems = []; - sourceCode.getInlineConfigNodes().filter(token => token.type !== "Shebang").forEach(comment => { - const { directivePart, justificationPart } = commentParser.extractDirectiveComment(comment.value); - - const match = directivesPattern.exec(directivePart); - - if (!match) { - return; - } - const directiveText = match[1]; - const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText); - - if (comment.type === "Line" && !lineCommentSupported) { - return; - } - - if (directiveText === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) { - const message = `${directiveText} comment should not span multiple lines.`; - - problems.push(createLintingProblem({ - ruleId: null, - message, - loc: comment.loc - })); - return; - } - - const directiveValue = directivePart.slice(match.index + directiveText.length); + const { + directives: directivesSources, + problems: directivesProblems + } = sourceCode.getDisableDirectives(); - switch (directiveText) { - case "eslint-disable": - case "eslint-enable": - case "eslint-disable-next-line": - case "eslint-disable-line": { - const directiveType = directiveText.slice("eslint-".length); - const options = { commentToken: comment, type: directiveType, value: directiveValue, justification: justificationPart, ruleMapper }; - const { directives, directiveProblems } = createDisableDirectives(options); + problems.push(...directivesProblems.map(createLintingProblem)); - disableDirectives.push(...directives); - problems.push(...directiveProblems); - break; - } + directivesSources.forEach(directive => { + const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper); - // no default - } + disableDirectives.push(...directives); + problems.push(...directiveProblems); }); return { diff --git a/lib/source-code/source-code.js b/lib/source-code/source-code.js index f3418e7e5b7..c749c2e45c5 100644 --- a/lib/source-code/source-code.js +++ b/lib/source-code/source-code.js @@ -373,6 +373,56 @@ class TraversalStep { } } +/** + * A class to represent a directive comment. + */ +class Directive { + + /** + * The type of directive. + * @type {"disable"|"enable"|"disable-next-line"|"disable-line"} + * @readonly + */ + type; + + /** + * The node representing the directive. + * @type {ASTNode|Comment} + * @readonly + */ + node; + + /** + * Everything after the "eslint-disable" portion of the directive, + * but before the "--" that indicates the justification. + * @type {string} + * @readonly + */ + value; + + /** + * The justification for the directive. + * @type {string} + * @readonly + */ + justification; + + /** + * Creates a new instance. + * @param {Object} options The options for the directive. + * @param {"disable"|"enable"|"disable-next-line"|"disable-line"} options.type The type of directive. + * @param {ASTNode|Comment} options.node The node representing the directive. + * @param {string} options.value The value of the directive. + * @param {string} options.justification The justification for the directive. + */ + constructor({ type, node, value, justification }) { + this.type = type; + this.node = node; + this.value = value; + this.justification = justification; + } +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -921,6 +971,84 @@ class SourceCode extends TokenStore { return configNodes; } + /** + * Returns an all directive nodes that enable or disable rules along with any problems + * encountered while parsing the directives. + * @returns {{problems:Array,directives:Array}} Information + * that ESLint needs to further process the directives. + */ + getDisableDirectives() { + + // check the cache first + const cachedDirectives = this[caches].get("disableDirectives"); + + if (cachedDirectives) { + return cachedDirectives; + } + + const problems = []; + const directives = []; + + this.getInlineConfigNodes().forEach(comment => { + const { directivePart, justificationPart } = commentParser.extractDirectiveComment(comment.value); + + // Step 1: Extract the directive text + const match = directivesPattern.exec(directivePart); + + if (!match) { + return; + } + + const directiveText = match[1]; + + // Step 2: Extract the directive value + const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText); + + if (comment.type === "Line" && !lineCommentSupported) { + return; + } + + // Step 3: Validate the directive does not span multiple lines + if (directiveText === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) { + const message = `${directiveText} comment should not span multiple lines.`; + + problems.push({ + ruleId: null, + message, + loc: comment.loc + }); + return; + } + + // Step 4: Extract the directive value and create the Directive object + const directiveValue = directivePart.slice(match.index + directiveText.length); + + switch (directiveText) { + case "eslint-disable": + case "eslint-enable": + case "eslint-disable-next-line": + case "eslint-disable-line": { + const directiveType = directiveText.slice("eslint-".length); + + directives.push(new Directive({ + type: directiveType, + node: comment, + value: directiveValue, + justification: justificationPart + })); + } + + // no default + } + }); + + const result = { problems, directives }; + + this[caches].set("disableDirectives", result); + + return result; + } + /** * Applies language options sent in from the core. * @param {Object} languageOptions The language options for this run. diff --git a/tests/lib/linter/apply-disable-directives.js b/tests/lib/linter/apply-disable-directives.js index d56bf5bc1b0..8cafe255e74 100644 --- a/tests/lib/linter/apply-disable-directives.js +++ b/tests/lib/linter/apply-disable-directives.js @@ -17,15 +17,15 @@ const applyDisableDirectives = require("../../../lib/linter/apply-disable-direct //----------------------------------------------------------------------------- /** - * Creates a ParentComment for a given range. + * Creates a ParentDirective for a given range. * @param {[number, number]} range total range of the comment * @param {string} value String value of the comment * @param {string[]} ruleIds Rule IDs reported in the value - * @returns {ParentComment} Test-ready ParentComment object. + * @returns {ParentDirective} Test-ready ParentDirective object. */ -function createParentComment(range, value, ruleIds = []) { +function createParentDirective(range, value, ruleIds = []) { return { - commentToken: { + node: { range, loc: { start: { @@ -52,7 +52,7 @@ describe("apply-disable-directives", () => { it("keeps problems before the comment on the same line", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ parentComment: createParentComment([0, 7]), type: "disable", line: 1, column: 8, ruleId: null, justification: "justification" }], + directives: [{ parentDirective: createParentDirective([0, 7]), type: "disable", line: 1, column: 8, ruleId: null, justification: "justification" }], problems: [{ line: 1, column: 7, ruleId: "foo" }] }), [{ line: 1, column: 7, ruleId: "foo" }] @@ -62,7 +62,7 @@ describe("apply-disable-directives", () => { it("keeps problems on a previous line before the comment", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ parentComment: createParentComment([21, 27]), type: "disable", line: 2, column: 1, ruleId: null, justification: "justification" }], + directives: [{ parentDirective: createParentDirective([21, 27]), type: "disable", line: 2, column: 1, ruleId: null, justification: "justification" }], problems: [{ line: 1, column: 10, ruleId: "foo" }] }), [{ line: 1, column: 10, ruleId: "foo" }] @@ -125,7 +125,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([26, 29]), + parentDirective: createParentDirective([26, 29]), type: "disable", line: 1, column: 1, @@ -142,7 +142,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([7, 31]), + parentDirective: createParentDirective([7, 31]), type: "disable", line: 1, column: 8, @@ -161,7 +161,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 26]), + parentDirective: createParentDirective([0, 26]), type: "disable", line: 1, column: 1, @@ -169,7 +169,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([27, 45]), + parentDirective: createParentDirective([27, 45]), type: "enable", line: 1, column: 26, @@ -188,7 +188,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 25]), + parentDirective: createParentDirective([0, 25]), type: "disable", line: 1, column: 1, @@ -196,7 +196,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([26, 40]), + parentDirective: createParentDirective([26, 40]), type: "enable", line: 1, column: 26, @@ -228,7 +228,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, @@ -236,7 +236,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([26, 44]), + parentDirective: createParentDirective([26, 44]), type: "enable", line: 1, column: 26, @@ -244,7 +244,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment: createParentComment([45, 63]), + parentDirective: createParentDirective([45, 63]), type: "disable", line: 2, column: 1, @@ -263,7 +263,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, @@ -271,7 +271,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([21, 44]), + parentDirective: createParentDirective([21, 44]), type: "enable", line: 1, column: 26, @@ -279,7 +279,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment: createParentComment([45, 63]), + parentDirective: createParentDirective([45, 63]), type: "disable", line: 2, column: 1, @@ -298,7 +298,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, @@ -306,7 +306,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([25, 44]), + parentDirective: createParentDirective([25, 44]), type: "enable", line: 1, column: 26, @@ -327,7 +327,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, @@ -335,7 +335,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([21, 44]), + parentDirective: createParentDirective([21, 44]), type: "enable", line: 2, column: 1, @@ -354,7 +354,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, @@ -362,7 +362,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([21, 44]), + parentDirective: createParentDirective([21, 44]), type: "enable", line: 2, column: 1, @@ -381,7 +381,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, @@ -389,7 +389,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([21, 44]), + parentDirective: createParentDirective([21, 44]), type: "enable", line: 2, column: 1, @@ -437,7 +437,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([6, 27]), + parentDirective: createParentDirective([6, 27]), type: "disable-line", line: 2, column: 1, @@ -454,7 +454,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([7, 28]), + parentDirective: createParentDirective([7, 28]), type: "disable-line", line: 1, column: 8, @@ -471,7 +471,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([7, 28]), + parentDirective: createParentDirective([7, 28]), type: "disable-line", line: 1, column: 8, @@ -488,7 +488,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([7, 34]), + parentDirective: createParentDirective([7, 34]), type: "disable-line", line: 1, column: 8, @@ -507,7 +507,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([7, 34]), + parentDirective: createParentDirective([7, 34]), type: "disable-line", line: 1, column: 8, @@ -524,7 +524,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 27]), + parentDirective: createParentDirective([0, 27]), type: "disable-line", line: 1, column: 1, @@ -542,7 +542,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "disable", line: 1, column: 1, @@ -550,7 +550,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([24, 28]), + parentDirective: createParentDirective([24, 28]), type: "disable-line", line: 1, column: 22, @@ -569,7 +569,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([7, 34]), + parentDirective: createParentDirective([7, 34]), type: "disable-line", line: 1, column: 8, @@ -577,7 +577,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([38, 73]), + parentDirective: createParentDirective([38, 73]), type: "disable-line", line: 2, column: 8, @@ -585,7 +585,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment: createParentComment([76, 111]), + parentDirective: createParentDirective([76, 111]), type: "disable-line", line: 3, column: 8, @@ -593,7 +593,7 @@ describe("apply-disable-directives", () => { justification: "j3" }, { - parentComment: createParentComment([114, 149]), + parentDirective: createParentDirective([114, 149]), type: "disable-line", line: 4, column: 8, @@ -601,7 +601,7 @@ describe("apply-disable-directives", () => { justification: "j4" }, { - parentComment: createParentComment([152, 187]), + parentDirective: createParentDirective([152, 187]), type: "disable-line", line: 5, column: 8, @@ -609,7 +609,7 @@ describe("apply-disable-directives", () => { justification: "j5" }, { - parentComment: createParentComment([190, 225]), + parentDirective: createParentDirective([190, 225]), type: "disable-line", line: 6, column: 8, @@ -629,7 +629,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 31]), + parentDirective: createParentDirective([0, 31]), type: "disable-next-line", line: 1, column: 1, @@ -646,7 +646,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 31]), + parentDirective: createParentDirective([0, 31]), type: "disable-next-line", line: 1, column: 1, @@ -662,7 +662,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 31]), + parentDirective: createParentDirective([0, 31]), type: "disable-next-line", line: 1, column: 1, @@ -679,8 +679,8 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { parentComment: createParentComment([0, 31]), type: "disable-next-line", line: 1, column: 1, ruleId: null, justification: "j1" }, - { parentComment: createParentComment([31, 50]), type: "enable", line: 1, column: 31, ruleId: null, justification: "j2" } + { parentDirective: createParentDirective([0, 31]), type: "disable-next-line", line: 1, column: 1, ruleId: null, justification: "j1" }, + { parentDirective: createParentDirective([31, 50]), type: "enable", line: 1, column: 31, ruleId: null, justification: "j2" } ], problems: [{ line: 2, column: 2, ruleId: "foo" }] }), @@ -704,7 +704,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 31]), + parentDirective: createParentDirective([0, 31]), type: "disable-next-line", line: 1, column: 1, @@ -736,7 +736,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, @@ -764,7 +764,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, @@ -800,7 +800,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "disable", line: 1, column: 1, @@ -829,7 +829,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 24]), + parentDirective: createParentDirective([0, 24]), type: "disable", line: 1, column: 1, @@ -866,7 +866,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "disable", line: 1, column: 8, @@ -874,7 +874,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "enable", line: 1, column: 24, @@ -912,7 +912,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, @@ -920,7 +920,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([21, 41]), + parentDirective: createParentDirective([21, 41]), type: "enable", line: 1, column: 12, @@ -951,7 +951,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "disable", line: 1, column: 1, @@ -959,7 +959,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([21, 42]), + parentDirective: createParentDirective([21, 42]), type: "disable", line: 2, column: 1, @@ -1004,7 +1004,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "disable", line: 1, column: 1, @@ -1012,7 +1012,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([22, 45]), + parentDirective: createParentDirective([22, 45]), type: "disable", line: 2, column: 1, @@ -1054,7 +1054,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "disable", line: 1, column: 1, @@ -1062,7 +1062,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([22, 45]), + parentDirective: createParentDirective([22, 45]), type: "disable", line: 2, column: 1, @@ -1115,7 +1115,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "disable", line: 1, column: 1, @@ -1123,7 +1123,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([22, 45]), + parentDirective: createParentDirective([22, 45]), type: "disable", line: 2, column: 1, @@ -1165,7 +1165,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, @@ -1173,7 +1173,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([21, 45]), + parentDirective: createParentDirective([21, 45]), type: "disable", line: 2, column: 1, @@ -1212,7 +1212,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, @@ -1220,7 +1220,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([25, 46]), + parentDirective: createParentDirective([25, 46]), type: "enable", line: 1, column: 26, @@ -1258,7 +1258,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 24]), + parentDirective: createParentDirective([0, 24]), type: "disable", line: 1, column: 1, @@ -1266,7 +1266,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([25, 49]), + parentDirective: createParentDirective([25, 49]), type: "enable", line: 1, column: 26, @@ -1304,7 +1304,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "disable", line: 1, column: 1, @@ -1312,7 +1312,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([22, 45]), + parentDirective: createParentDirective([22, 45]), type: "disable", line: 2, column: 1, @@ -1320,7 +1320,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment: createParentComment([46, 69]), + parentDirective: createParentDirective([46, 69]), type: "enable", line: 3, column: 1, @@ -1369,7 +1369,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "enable", line: 1, column: 1, @@ -1397,7 +1397,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "enable", line: 1, column: 1, @@ -1436,7 +1436,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "enable", line: 1, column: 1, @@ -1466,7 +1466,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 24]), + parentDirective: createParentDirective([0, 24]), type: "disable", line: 1, column: 1, @@ -1474,7 +1474,7 @@ describe("apply-disable-directives", () => { justification: "justification" }, { - parentComment: createParentComment([48, 72]), + parentDirective: createParentDirective([48, 72]), type: "enable", line: 3, column: 1, @@ -1513,7 +1513,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "disable", line: 1, column: 1, @@ -1521,7 +1521,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([42, 63]), + parentDirective: createParentDirective([42, 63]), type: "enable", line: 3, column: 1, @@ -1529,7 +1529,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([63, 84]), + parentDirective: createParentDirective([63, 84]), type: "enable", line: 4, column: 1, @@ -1537,7 +1537,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([84, 105]), + parentDirective: createParentDirective([84, 105]), type: "enable", line: 5, column: 1, @@ -1588,7 +1588,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "disable", line: 1, column: 1, @@ -1596,7 +1596,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([42, 63]), + parentDirective: createParentDirective([42, 63]), type: "enable", line: 3, column: 1, @@ -1604,7 +1604,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([63, 84]), + parentDirective: createParentDirective([63, 84]), type: "enable", line: 4, column: 1, @@ -1643,7 +1643,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "enable", line: 1, column: 1, @@ -1651,7 +1651,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([21, 42]), + parentDirective: createParentDirective([21, 42]), type: "enable", line: 2, column: 1, @@ -1696,7 +1696,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "enable", line: 1, column: 1, @@ -1704,7 +1704,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([21, 42]), + parentDirective: createParentDirective([21, 42]), type: "disable", line: 2, column: 1, @@ -1712,7 +1712,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment: createParentComment([63, 84]), + parentDirective: createParentDirective([63, 84]), type: "enable", line: 4, column: 1, @@ -1751,7 +1751,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "disable", line: 1, column: 1, @@ -1759,7 +1759,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([42, 63]), + parentDirective: createParentDirective([42, 63]), type: "enable", line: 3, column: 1, @@ -1767,7 +1767,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment: createParentComment([63, 84]), + parentDirective: createParentDirective([63, 84]), type: "enable", line: 4, column: 1, @@ -1820,7 +1820,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 21]), + parentDirective: createParentDirective([0, 21]), type: "disable", line: 1, column: 1, @@ -1828,7 +1828,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([42, 63]), + parentDirective: createParentDirective([42, 63]), type: "enable", line: 3, column: 1, @@ -1836,7 +1836,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment: createParentComment([63, 84]), + parentDirective: createParentDirective([63, 84]), type: "enable", line: 4, column: 1, @@ -1877,7 +1877,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, @@ -1885,7 +1885,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([60, 80]), + parentDirective: createParentDirective([60, 80]), type: "enable", line: 3, column: 1, @@ -1893,7 +1893,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment: createParentComment([80, 100]), + parentDirective: createParentDirective([80, 100]), type: "enable", line: 4, column: 1, @@ -1932,7 +1932,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, @@ -1940,7 +1940,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([60, 80]), + parentDirective: createParentDirective([60, 80]), type: "enable", line: 3, column: 1, @@ -1948,7 +1948,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment: createParentComment([80, 100]), + parentDirective: createParentDirective([80, 100]), type: "enable", line: 4, column: 1, @@ -1987,7 +1987,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, @@ -1995,7 +1995,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([40, 60]), + parentDirective: createParentDirective([40, 60]), type: "enable", line: 3, column: 1, @@ -2003,7 +2003,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment: createParentComment([60, 80]), + parentDirective: createParentDirective([60, 80]), type: "enable", line: 4, column: 1, @@ -2011,7 +2011,7 @@ describe("apply-disable-directives", () => { justification: "j3" }, { - parentComment: createParentComment([80, 100]), + parentDirective: createParentDirective([80, 100]), type: "enable", line: 5, column: 1, @@ -2062,7 +2062,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 20]), + parentDirective: createParentDirective([0, 20]), ruleId: "used", type: "disable", line: 1, @@ -2070,7 +2070,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([40, 60]), + parentDirective: createParentDirective([40, 60]), ruleId: "used", type: "disable", line: 3, @@ -2078,7 +2078,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment: createParentComment([80, 100]), + parentDirective: createParentDirective([80, 100]), ruleId: "used", type: "enable", line: 5, @@ -2086,7 +2086,7 @@ describe("apply-disable-directives", () => { justification: "j3" }, { - parentComment: createParentComment([100, 120]), + parentDirective: createParentDirective([100, 120]), ruleId: null, type: "enable", line: 6, @@ -2130,7 +2130,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 22]), + parentDirective: createParentDirective([0, 22]), type: "disable-line", line: 1, column: 1, @@ -2173,7 +2173,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 27]), + parentDirective: createParentDirective([0, 27]), type: "disable-next-line", line: 1, column: 2, @@ -2215,8 +2215,8 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { parentComment: createParentComment([0, 20]), type: "disable", line: 1, column: 1, ruleId: null }, - { parentComment: createParentComment([20, 43]), type: "disable-line", line: 1, column: 22, ruleId: null } + { parentDirective: createParentDirective([0, 20]), type: "disable", line: 1, column: 1, ruleId: null }, + { parentDirective: createParentDirective([20, 43]), type: "disable-line", line: 1, column: 22, ruleId: null } ], problems: [], reportUnusedDisableDirectives: "error" @@ -2254,7 +2254,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 27]), + parentDirective: createParentDirective([0, 27]), type: "disable-next-line", line: 1, column: 1, @@ -2271,13 +2271,13 @@ describe("apply-disable-directives", () => { describe("unused rules within directives", () => { it("Adds a problem for /* eslint-disable used, unused */", () => { - const parentComment = createParentComment([0, 32], " eslint-disable used, unused ", ["used", "unused"]); + const parentDirective = createParentDirective([0, 32], " eslint-disable used, unused ", ["used", "unused"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, + parentDirective, ruleId: "used", type: "disable", line: 1, @@ -2285,7 +2285,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment, + parentDirective, ruleId: "unused", type: "disable", line: 1, @@ -2319,13 +2319,13 @@ describe("apply-disable-directives", () => { ); }); it("Adds a problem for /* eslint-disable used , unused , -- unused and used are ok */", () => { - const parentComment = createParentComment([0, 62], " eslint-disable used , unused , -- unused and used are ok ", ["used", "unused"]); + const parentDirective = createParentDirective([0, 62], " eslint-disable used , unused , -- unused and used are ok ", ["used", "unused"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, + parentDirective, ruleId: "used", type: "disable", line: 1, @@ -2333,7 +2333,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment, + parentDirective, ruleId: "unused", type: "disable", line: 1, @@ -2368,13 +2368,13 @@ describe("apply-disable-directives", () => { }); it("Adds a problem for /* eslint-disable unused, used */", () => { - const parentComment = createParentComment([0, 32], " eslint-disable unused, used ", ["unused", "used"]); + const parentDirective = createParentDirective([0, 32], " eslint-disable unused, used ", ["unused", "used"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, + parentDirective, ruleId: "unused", type: "disable", line: 1, @@ -2382,7 +2382,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment, + parentDirective, ruleId: "used", type: "disable", line: 1, @@ -2417,13 +2417,13 @@ describe("apply-disable-directives", () => { }); it("Adds a problem for /* eslint-disable unused,, ,, used */", () => { - const parentComment = createParentComment([0, 37], " eslint-disable unused,, ,, used ", ["unused", "used"]); + const parentDirective = createParentDirective([0, 37], " eslint-disable unused,, ,, used ", ["unused", "used"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, + parentDirective, ruleId: "unused", type: "disable", line: 1, @@ -2431,7 +2431,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment, + parentDirective, ruleId: "used", type: "disable", line: 1, @@ -2466,13 +2466,13 @@ describe("apply-disable-directives", () => { }); it("Adds a problem for /* eslint-disable unused-1, unused-2, used */", () => { - const parentComment = createParentComment([0, 45], " eslint-disable unused-1, unused-2, used ", ["unused-1", "unused-2", "used"]); + const parentDirective = createParentDirective([0, 45], " eslint-disable unused-1, unused-2, used ", ["unused-1", "unused-2", "used"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, + parentDirective, ruleId: "unused-1", type: "disable", line: 1, @@ -2480,7 +2480,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment, + parentDirective, ruleId: "unused-2", type: "disable", line: 1, @@ -2488,7 +2488,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment, + parentDirective, ruleId: "used", type: "disable", line: 1, @@ -2535,13 +2535,13 @@ describe("apply-disable-directives", () => { }); it("Adds a problem for /* eslint-disable unused-1, unused-2, used, unused-3 */", () => { - const parentComment = createParentComment([0, 55], " eslint-disable unused-1, unused-2, used, unused-3 ", ["unused-1", "unused-2", "used", "unused-3"]); + const parentDirective = createParentDirective([0, 55], " eslint-disable unused-1, unused-2, used, unused-3 ", ["unused-1", "unused-2", "used", "unused-3"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, + parentDirective, ruleId: "unused-1", type: "disable", line: 1, @@ -2549,7 +2549,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment, + parentDirective, ruleId: "unused-2", type: "disable", line: 1, @@ -2557,7 +2557,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment, + parentDirective, ruleId: "used", type: "disable", line: 1, @@ -2565,7 +2565,7 @@ describe("apply-disable-directives", () => { justification: "j3" }, { - parentComment, + parentDirective, ruleId: "unused-3", type: "disable", line: 1, @@ -2624,13 +2624,13 @@ describe("apply-disable-directives", () => { }); it("Adds a problem for /* eslint-disable unused-1, unused-2 */", () => { - const parentComment = createParentComment([0, 39], " eslint-disable unused-1, unused-2 ", ["unused-1", "unused-2"]); + const parentDirective = createParentDirective([0, 39], " eslint-disable unused-1, unused-2 ", ["unused-1", "unused-2"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, + parentDirective, ruleId: "unused-1", type: "disable", line: 1, @@ -2638,7 +2638,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment, + parentDirective, ruleId: "unused-2", type: "disable", line: 1, @@ -2667,27 +2667,27 @@ describe("apply-disable-directives", () => { }); it("Adds a problem for /* eslint-disable unused-1, unused-2, unused-3 */", () => { - const parentComment = createParentComment([0, 49], " eslint-disable unused-1, unused-2, unused-3 ", ["unused-1", "unused-2", "unused-3"]); + const parentDirective = createParentDirective([0, 49], " eslint-disable unused-1, unused-2, unused-3 ", ["unused-1", "unused-2", "unused-3"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, + parentDirective, ruleId: "unused-1", type: "disable", line: 1, column: 18 }, { - parentComment, + parentDirective, ruleId: "unused-2", type: "disable", line: 1, column: 28 }, { - parentComment, + parentDirective, ruleId: "unused-3", type: "disable", line: 1, @@ -2719,7 +2719,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), + parentDirective: createParentDirective([0, 29], " eslint-disable foo ", ["foo"]), ruleId: "foo", type: "disable", line: 1, @@ -2727,7 +2727,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment: createParentComment([41, 81], " eslint-disable-line foo, bar", ["foo", "bar"]), + parentDirective: createParentDirective([41, 81], " eslint-disable-line foo, bar", ["foo", "bar"]), ruleId: "foo", type: "disable-line", line: 2, @@ -2735,7 +2735,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment: createParentComment([41, 81], " eslint-disable-line foo, bar ", ["foo", "bar"]), + parentDirective: createParentDirective([41, 81], " eslint-disable-line foo, bar ", ["foo", "bar"]), ruleId: "bar", type: "disable-line", line: 2, @@ -2782,13 +2782,13 @@ describe("apply-disable-directives", () => { }); it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable used, unused */", () => { - const parentComment = createParentComment([0, 32], " eslint-enable used, unused ", ["used", "unused"]); + const parentDirective = createParentDirective([0, 32], " eslint-enable used, unused ", ["used", "unused"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), + parentDirective: createParentDirective([0, 29], " eslint-disable foo ", ["foo"]), ruleId: "used", type: "disable", line: 1, @@ -2796,7 +2796,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment, + parentDirective, ruleId: "used", type: "enable", line: 3, @@ -2804,7 +2804,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment, + parentDirective, ruleId: "unused", type: "enable", line: 4, @@ -2838,13 +2838,13 @@ describe("apply-disable-directives", () => { ); }); it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable used , unused , -- unused and used are ok */", () => { - const parentComment = createParentComment([0, 62], " eslint-enable used , unused , -- unused and used are ok ", ["used", "unused"]); + const parentDirective = createParentDirective([0, 62], " eslint-enable used , unused , -- unused and used are ok ", ["used", "unused"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), + parentDirective: createParentDirective([0, 29], " eslint-disable foo ", ["foo"]), ruleId: "used", type: "disable", line: 1, @@ -2852,7 +2852,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment, + parentDirective, ruleId: "used", type: "enable", line: 3, @@ -2860,7 +2860,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment, + parentDirective, ruleId: "unused", type: "enable", line: 4, @@ -2895,13 +2895,13 @@ describe("apply-disable-directives", () => { }); it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable unused, used */", () => { - const parentComment = createParentComment([0, 32], " eslint-enable unused, used ", ["unused", "used"]); + const parentDirective = createParentDirective([0, 32], " eslint-enable unused, used ", ["unused", "used"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), + parentDirective: createParentDirective([0, 29], " eslint-disable foo ", ["foo"]), ruleId: "used", type: "disable", line: 1, @@ -2909,7 +2909,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment, + parentDirective, ruleId: "unused", type: "enable", line: 3, @@ -2917,7 +2917,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment, + parentDirective, ruleId: "used", type: "enable", line: 4, @@ -2952,13 +2952,13 @@ describe("apply-disable-directives", () => { }); it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable unused,, ,, used */", () => { - const parentComment = createParentComment([0, 37], " eslint-enable unused,, ,, used ", ["unused", "used"]); + const parentDirective = createParentDirective([0, 37], " eslint-enable unused,, ,, used ", ["unused", "used"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), + parentDirective: createParentDirective([0, 29], " eslint-disable foo ", ["foo"]), ruleId: "used", type: "disable", line: 1, @@ -2966,7 +2966,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment, + parentDirective, ruleId: "unused", type: "enable", line: 3, @@ -2974,7 +2974,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment, + parentDirective, ruleId: "used", type: "enable", line: 4, @@ -3009,13 +3009,13 @@ describe("apply-disable-directives", () => { }); it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable unused-1, unused-2, used */", () => { - const parentComment = createParentComment([0, 45], " eslint-enable unused-1, unused-2, used ", ["unused-1", "unused-2", "used"]); + const parentDirective = createParentDirective([0, 45], " eslint-enable unused-1, unused-2, used ", ["unused-1", "unused-2", "used"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), + parentDirective: createParentDirective([0, 29], " eslint-disable foo ", ["foo"]), ruleId: "used", type: "disable", line: 1, @@ -3023,7 +3023,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment, + parentDirective, ruleId: "unused-1", type: "enable", line: 3, @@ -3031,7 +3031,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment, + parentDirective, ruleId: "unused-2", type: "enable", line: 4, @@ -3039,7 +3039,7 @@ describe("apply-disable-directives", () => { justification: "j3" }, { - parentComment, + parentDirective, ruleId: "used", type: "enable", line: 5, @@ -3086,13 +3086,13 @@ describe("apply-disable-directives", () => { }); it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable unused-1, unused-2, used, unused-3 */", () => { - const parentComment = createParentComment([0, 55], " eslint-enable unused-1, unused-2, used, unused-3 ", ["unused-1", "unused-2", "used", "unused-3"]); + const parentDirective = createParentDirective([0, 55], " eslint-enable unused-1, unused-2, used, unused-3 ", ["unused-1", "unused-2", "used", "unused-3"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), + parentDirective: createParentDirective([0, 29], " eslint-disable foo ", ["foo"]), ruleId: "used", type: "disable", line: 1, @@ -3100,7 +3100,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment, + parentDirective, ruleId: "unused-1", type: "enable", line: 3, @@ -3108,7 +3108,7 @@ describe("apply-disable-directives", () => { justification: "j2" }, { - parentComment, + parentDirective, ruleId: "unused-2", type: "enable", line: 4, @@ -3116,7 +3116,7 @@ describe("apply-disable-directives", () => { justification: "j3" }, { - parentComment, + parentDirective, ruleId: "used", type: "enable", line: 5, @@ -3124,7 +3124,7 @@ describe("apply-disable-directives", () => { justification: "j4" }, { - parentComment, + parentDirective, ruleId: "unused-3", type: "enable", line: 6, @@ -3183,13 +3183,13 @@ describe("apply-disable-directives", () => { }); it("Adds a problem for /* eslint-enable unused-1, unused-2 */", () => { - const parentComment = createParentComment([0, 39], " eslint-enable unused-1, unused-2 ", ["unused-1", "unused-2"]); + const parentDirective = createParentDirective([0, 39], " eslint-enable unused-1, unused-2 ", ["unused-1", "unused-2"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, + parentDirective, ruleId: "unused-1", type: "enable", line: 1, @@ -3197,7 +3197,7 @@ describe("apply-disable-directives", () => { justification: "j1" }, { - parentComment, + parentDirective, ruleId: "unused-2", type: "enable", line: 1, @@ -3226,27 +3226,27 @@ describe("apply-disable-directives", () => { }); it("Adds a problem for /* eslint-enable unused-1, unused-2, unused-3 */", () => { - const parentComment = createParentComment([0, 49], " eslint-enable unused-1, unused-2, unused-3 ", ["unused-1", "unused-2", "unused-3"]); + const parentDirective = createParentDirective([0, 49], " eslint-enable unused-1, unused-2, unused-3 ", ["unused-1", "unused-2", "unused-3"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, + parentDirective, ruleId: "unused-1", type: "enable", line: 1, column: 18 }, { - parentComment, + parentDirective, ruleId: "unused-2", type: "enable", line: 1, column: 28 }, { - parentComment, + parentDirective, ruleId: "unused-3", type: "enable", line: 1, From 1cbe1f6d38272784307c260f2375ab30e68716e8 Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Wed, 17 Apr 2024 23:06:22 +0530 Subject: [PATCH 091/166] feat: allow `while(true)` in `no-constant-condition` (#18286) * feat: change values of checkLoops option * feat: add both boolean and string values * feat: unify both ways of configuring * add suggestions * update code * change to else if condition --- docs/src/rules/no-constant-condition.md | 107 +++++++++++++++++++++-- lib/rules/no-constant-condition.js | 25 ++++-- tests/lib/rules/no-constant-condition.js | 52 ++++++++++- 3 files changed, 170 insertions(+), 14 deletions(-) diff --git a/docs/src/rules/no-constant-condition.md b/docs/src/rules/no-constant-condition.md index 2ac613f7abf..26b6e8c54ba 100644 --- a/docs/src/rules/no-constant-condition.md +++ b/docs/src/rules/no-constant-condition.md @@ -118,14 +118,94 @@ if(input === "hello" || input === "bye"){ ### checkLoops -Set to `true` by default. Setting this option to `false` allows constant expressions in loops. +This is a string option having following values: -Examples of **correct** code for when `checkLoops` is `false`: +* `"all"` - Disallow constant expressions in all loops. +* `"allExceptWhileTrue"` (default) - Disallow constant expressions in all loops except `while` loops with expression `true`. +* `"none"` - Allow constant expressions in loops. + +Or instead you can set the `checkLoops` value to booleans where `true` is same as `"all"` and `false` is same as `"none"`. + +Examples of **incorrect** code for when `checkLoops` is `"all"` or `true`: + +::: incorrect + +```js +/*eslint no-constant-condition: ["error", { "checkLoops": "all" }]*/ + +while (true) { + doSomething(); +}; + +for (;true;) { + doSomething(); +}; +``` + +::: + +::: incorrect + +```js +/*eslint no-constant-condition: ["error", { "checkLoops": true }]*/ + +while (true) { + doSomething(); +}; + +do { + doSomething(); +} while (true) +``` + +::: + +Examples of **correct** code for when `checkLoops` is `"all"` or `true`: ::: correct ```js -/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ +/*eslint no-constant-condition: ["error", { "checkLoops": "all" }]*/ + +while (a === b) { + doSomething(); +}; +``` + +::: + +::: correct + +```js +/*eslint no-constant-condition: ["error", { "checkLoops": true }]*/ + +for (let x = 0; x <= 10; x++) { + doSomething(); +}; +``` + +::: + +Example of **correct** code for when `checkLoops` is `"allExceptWhileTrue"`: + +::: correct + +```js +/*eslint no-constant-condition: "error"*/ + +while (true) { + doSomething(); +}; +``` + +::: + +Examples of **correct** code for when `checkLoops` is `"none"` or `false`: + +::: correct + +```js +/*eslint no-constant-condition: ["error", { "checkLoops": "none" }]*/ while (true) { doSomething(); @@ -134,19 +214,34 @@ while (true) { } }; -for (;true;) { +do { + doSomething(); + if (condition()) { + break; + } +} while (true) +``` + +::: + +::: correct + +```js +/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ + +while (true) { doSomething(); if (condition()) { break; } }; -do { +for (;true;) { doSomething(); if (condition()) { break; } -} while (true) +}; ``` ::: diff --git a/lib/rules/no-constant-condition.js b/lib/rules/no-constant-condition.js index 24abe363280..b7d0c755654 100644 --- a/lib/rules/no-constant-condition.js +++ b/lib/rules/no-constant-condition.js @@ -31,8 +31,7 @@ module.exports = { type: "object", properties: { checkLoops: { - type: "boolean", - default: true + enum: ["all", "allExceptWhileTrue", "none", true, false] } }, additionalProperties: false @@ -45,11 +44,17 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}, - checkLoops = options.checkLoops !== false, - loopSetStack = []; + const options = context.options[0] || {}; + let checkLoops = options.checkLoops ?? "allExceptWhileTrue"; + const loopSetStack = []; const sourceCode = context.sourceCode; + if (options.checkLoops === true) { + checkLoops = "all"; + } else if (options.checkLoops === false) { + checkLoops = "none"; + } + let loopsInCurrentScope = new Set(); //-------------------------------------------------------------------------- @@ -120,7 +125,7 @@ module.exports = { * @private */ function checkLoop(node) { - if (checkLoops) { + if (checkLoops === "all" || checkLoops === "allExceptWhileTrue") { trackConstantConditionLoop(node); } } @@ -132,7 +137,13 @@ module.exports = { return { ConditionalExpression: reportIfConstant, IfStatement: reportIfConstant, - WhileStatement: checkLoop, + WhileStatement(node) { + if (node.test.type === "Literal" && node.test.value === true && checkLoops === "allExceptWhileTrue") { + return; + } + + checkLoop(node); + }, "WhileStatement:exit": checkConstantConditionLoopInSet, DoWhileStatement: checkLoop, "DoWhileStatement:exit": checkConstantConditionLoopInSet, diff --git a/tests/lib/rules/no-constant-condition.js b/tests/lib/rules/no-constant-condition.js index 3ffad20d0c0..8628821c8ae 100644 --- a/tests/lib/rules/no-constant-condition.js +++ b/tests/lib/rules/no-constant-condition.js @@ -166,6 +166,20 @@ ruleTester.run("no-constant-condition", rule, { { code: "for(;true;);", options: [{ checkLoops: false }] }, { code: "do{}while(true)", options: [{ checkLoops: false }] }, + // { checkLoops: "none" } + { code: "while(true);", options: [{ checkLoops: "none" }] }, + { code: "for(;true;);", options: [{ checkLoops: "none" }] }, + { code: "do{}while(true)", options: [{ checkLoops: "none" }] }, + + // { checkloops: "allExceptWhileTrue" } + { code: "while(true);", options: [{ checkLoops: "allExceptWhileTrue" }] }, + "while(true);", + + // { checkloops: "all" } + { code: "while(a == b);", options: [{ checkLoops: "all" }] }, + { code: "do{ }while(x);", options: [{ checkLoops: "all" }] }, + { code: "for (let x = 0; x <= 10; x++) {};", options: [{ checkLoops: "all" }] }, + "function* foo(){while(true){yield 'foo';}}", "function* foo(){for(;true;){yield 'foo';}}", "function* foo(){do{yield 'foo';}while(true)}", @@ -272,7 +286,7 @@ ruleTester.run("no-constant-condition", rule, { { code: "while(~!0);", errors: [{ messageId: "unexpected", type: "UnaryExpression" }] }, { code: "while(x = 1);", errors: [{ messageId: "unexpected", type: "AssignmentExpression" }] }, { code: "while(function(){});", errors: [{ messageId: "unexpected", type: "FunctionExpression" }] }, - { code: "while(true);", errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "while(true);", options: [{ checkLoops: "all" }], errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "while(1);", errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "while(() => {});", errors: [{ messageId: "unexpected", type: "ArrowFunctionExpression" }] }, { code: "while(`foo`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] }, @@ -314,24 +328,58 @@ ruleTester.run("no-constant-condition", rule, { { code: "if(abc==='str' || 'str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, { code: "if(a || 'str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }, + { code: "while(x = 1);", options: [{ checkLoops: "all" }], errors: [{ messageId: "unexpected", type: "AssignmentExpression" }] }, + { code: "do{ }while(x = 1)", options: [{ checkLoops: "all" }], errors: [{ messageId: "unexpected", type: "AssignmentExpression" }] }, + { code: "for (;true;) {};", options: [{ checkLoops: "all" }], errors: [{ messageId: "unexpected", type: "Literal" }] }, + + { + code: "function* foo(){while(true){} yield 'foo';}", + options: [{ checkLoops: "all" }], + errors: [{ messageId: "unexpected", type: "Literal" }] + }, { code: "function* foo(){while(true){} yield 'foo';}", + options: [{ checkLoops: true }], + errors: [{ messageId: "unexpected", type: "Literal" }] + }, + { + code: "function* foo(){while(true){if (true) {yield 'foo';}}}", + options: [{ checkLoops: "all" }], errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "function* foo(){while(true){if (true) {yield 'foo';}}}", + options: [{ checkLoops: true }], errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "function* foo(){while(true){yield 'foo';} while(true) {}}", + options: [{ checkLoops: "all" }], + errors: [{ messageId: "unexpected", type: "Literal" }] + }, + { + code: "function* foo(){while(true){yield 'foo';} while(true) {}}", + options: [{ checkLoops: true }], + errors: [{ messageId: "unexpected", type: "Literal" }] + }, + { + code: "var a = function* foo(){while(true){} yield 'foo';}", + options: [{ checkLoops: "all" }], errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "var a = function* foo(){while(true){} yield 'foo';}", + options: [{ checkLoops: true }], + errors: [{ messageId: "unexpected", type: "Literal" }] + }, + { + code: "while (true) { function* foo() {yield;}}", + options: [{ checkLoops: "all" }], errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "while (true) { function* foo() {yield;}}", + options: [{ checkLoops: true }], errors: [{ messageId: "unexpected", type: "Literal" }] }, { @@ -348,10 +396,12 @@ ruleTester.run("no-constant-condition", rule, { }, { code: "function foo() {while (true) {function* bar() {while (true) {yield;}}}}", + options: [{ checkLoops: "all" }], errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "function foo() {while (true) {const bar = function*() {while (true) {yield;}}}}", + options: [{ checkLoops: "all" }], errors: [{ messageId: "unexpected", type: "Literal" }] }, { From 4d11e567baff575146fd267b3765ab2c788aa1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Thu, 18 Apr 2024 15:34:53 +0800 Subject: [PATCH 092/166] feat: add `name` to eslint configs (#18289) * feat: add `name` to @eslint/js configs * feat: add `name` to eslint-config-eslint configs * chore: add name to eslint.config.js * chore: update failing tests * chore: add name for eslint-comment * chore: fix failing tests --- eslint.config.js | 14 ++++++++++++++ packages/eslint-config-eslint/base.js | 6 +++++- packages/eslint-config-eslint/nodejs.js | 2 ++ packages/js/src/configs/eslint-all.js | 1 + packages/js/src/configs/eslint-recommended.js | 1 + tests/fixtures/config-file/js/.eslintrc.js | 1 + tests/lib/cli.js | 3 +-- tools/update-eslint-all.js | 2 +- 8 files changed, 26 insertions(+), 4 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index e51829b602b..2d7b781f661 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -76,6 +76,7 @@ function createInternalFilesPatterns(pattern = null) { module.exports = [ ...eslintConfigESLintCJS, { + name: "eslint/global-ignores", ignores: [ "build/**", "coverage/**", @@ -92,6 +93,7 @@ module.exports = [ ] }, { + name: "eslint/internal-rules", plugins: { "internal-rules": internalPlugin }, @@ -103,6 +105,7 @@ module.exports = [ } }, { + name: "eslint/tools", files: ["tools/*.js", "docs/tools/*.js"], rules: { "no-console": "off", @@ -110,6 +113,7 @@ module.exports = [ } }, { + name: "eslint/rules", files: ["lib/rules/*", "tools/internal-rules/*"], ignores: ["**/index.js"], ...eslintPluginRulesRecommendedConfig, @@ -123,6 +127,7 @@ module.exports = [ } }, { + name: "eslint/core-rules", files: ["lib/rules/*"], ignores: ["**/index.js"], rules: { @@ -130,6 +135,7 @@ module.exports = [ } }, { + name: "eslint/rules-tests", files: ["tests/lib/rules/*", "tests/tools/internal-rules/*"], ...eslintPluginTestsRecommendedConfig, rules: { @@ -150,6 +156,7 @@ module.exports = [ } }, { + name: "eslint/tests", files: ["tests/**/*.js"], languageOptions: { globals: { @@ -166,6 +173,7 @@ module.exports = [ // Restrict relative path imports { + name: "eslint/lib", files: ["lib/*"], ignores: ["lib/unsupported-api.js"], rules: { @@ -175,6 +183,7 @@ module.exports = [ } }, { + name: "eslint/cli-engine", files: [INTERNAL_FILES.CLI_ENGINE_PATTERN], rules: { "n/no-restricted-require": ["error", [ @@ -183,6 +192,7 @@ module.exports = [ } }, { + name: "eslint/linter", files: [INTERNAL_FILES.LINTER_PATTERN], rules: { "n/no-restricted-require": ["error", [ @@ -194,6 +204,7 @@ module.exports = [ } }, { + name: "eslint/rules", files: [INTERNAL_FILES.RULES_PATTERN], rules: { "n/no-restricted-require": ["error", [ @@ -207,6 +218,7 @@ module.exports = [ } }, { + name: "eslint/shared", files: ["lib/shared/**/*"], rules: { "n/no-restricted-require": ["error", [ @@ -219,6 +231,7 @@ module.exports = [ } }, { + name: "eslint/source-code", files: [INTERNAL_FILES.SOURCE_CODE_PATTERN], rules: { "n/no-restricted-require": ["error", [ @@ -232,6 +245,7 @@ module.exports = [ } }, { + name: "eslint/rule-tester", files: [INTERNAL_FILES.RULE_TESTER_PATTERN], rules: { "n/no-restricted-require": ["error", [ diff --git a/packages/eslint-config-eslint/base.js b/packages/eslint-config-eslint/base.js index 21d5260860b..4240a9c8bb0 100644 --- a/packages/eslint-config-eslint/base.js +++ b/packages/eslint-config-eslint/base.js @@ -7,6 +7,7 @@ const unicorn = require("eslint-plugin-unicorn"); // extends eslint recommended config const jsConfigs = [js.configs.recommended, { + name: "eslint-config-eslint/js", rules: { "array-bracket-spacing": "error", "array-callback-return": "error", @@ -259,6 +260,7 @@ const jsConfigs = [js.configs.recommended, { // extends eslint-plugin-jsdoc's recommended config const jsdocConfigs = [jsdoc.configs["flat/recommended"], { + name: "eslint-config-eslint/jsdoc", settings: { jsdoc: { mode: "typescript", @@ -350,6 +352,7 @@ const jsdocConfigs = [jsdoc.configs["flat/recommended"], { // extends eslint-plugin-unicorn's config const unicornConfigs = [{ + name: "eslint-config-eslint/unicorn", plugins: { unicorn }, rules: { "unicorn/prefer-array-find": "error", @@ -368,6 +371,7 @@ const unicornConfigs = [{ // extends @eslint-community/eslint-plugin-eslint-comments's recommended config const eslintCommentsConfigs = [eslintCommentsPluginConfigs.recommended, { + name: "eslint-config-eslint/eslint-comments", rules: { "@eslint-community/eslint-comments/disable-enable-pair": ["error"], "@eslint-community/eslint-comments/no-unused-disable": "error", @@ -376,7 +380,7 @@ const eslintCommentsConfigs = [eslintCommentsPluginConfigs.recommended, { }]; module.exports = [ - { linterOptions: { reportUnusedDisableDirectives: "error" } }, + { name: "eslint-config-eslint/base", linterOptions: { reportUnusedDisableDirectives: "error" } }, ...jsConfigs, ...unicornConfigs, ...jsdocConfigs, diff --git a/packages/eslint-config-eslint/nodejs.js b/packages/eslint-config-eslint/nodejs.js index 368c67dc86b..d61c1ee7600 100644 --- a/packages/eslint-config-eslint/nodejs.js +++ b/packages/eslint-config-eslint/nodejs.js @@ -11,6 +11,7 @@ const sharedRules = { const cjsConfigs = [ recommendedScriptConfig, { + name: "eslint-config-eslint/cjs", rules: { ...sharedRules, "n/no-mixed-requires": "error", @@ -23,6 +24,7 @@ const cjsConfigs = [ const esmConfigs = [ recommendedModuleConfig, { + name: "eslint-config-eslint/esm", rules: sharedRules } ]; diff --git a/packages/js/src/configs/eslint-all.js b/packages/js/src/configs/eslint-all.js index 3fe1552af9d..513cc17d11f 100644 --- a/packages/js/src/configs/eslint-all.js +++ b/packages/js/src/configs/eslint-all.js @@ -7,6 +7,7 @@ /* eslint quote-props: off -- autogenerated so don't lint */ module.exports = Object.freeze({ + "name": "@eslint/js/all", "rules": { "accessor-pairs": "error", "array-callback-return": "error", diff --git a/packages/js/src/configs/eslint-recommended.js b/packages/js/src/configs/eslint-recommended.js index b105595877b..083be6b0ee2 100644 --- a/packages/js/src/configs/eslint-recommended.js +++ b/packages/js/src/configs/eslint-recommended.js @@ -9,6 +9,7 @@ /* eslint sort-keys: ["error", "asc"] -- Long, so make more readable */ module.exports = Object.freeze({ + name: "@eslint/js/recommended", rules: Object.freeze({ "constructor-super": "error", "for-direction": "error", diff --git a/tests/fixtures/config-file/js/.eslintrc.js b/tests/fixtures/config-file/js/.eslintrc.js index b3100591dcb..b904405460d 100644 --- a/tests/fixtures/config-file/js/.eslintrc.js +++ b/tests/fixtures/config-file/js/.eslintrc.js @@ -1,4 +1,5 @@ module.exports = { + extends: "eslint:recommended", rules: { semi: [2, "always"] } diff --git a/tests/lib/cli.js b/tests/lib/cli.js index 01aaebcec7e..4c91be3665b 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -1771,7 +1771,7 @@ describe("cli", () => { getFixturePath("globals-node.js") ]; - await cli.execute(`--no-eslintrc --config ./packages/js/src/configs/eslint-recommended.js --no-ignore ${files.join(" ")}`, null, false); + await cli.execute(`--no-eslintrc --config ./tests/fixtures/config-file/js/.eslintrc.js --no-ignore ${files.join(" ")}`, null, false); assert.strictEqual(log.info.args[0][0].split("\n").length, 10); }); @@ -1903,7 +1903,6 @@ describe("cli", () => { }); }); }); - }); }); diff --git a/tools/update-eslint-all.js b/tools/update-eslint-all.js index b964876f736..d135cffebf5 100644 --- a/tools/update-eslint-all.js +++ b/tools/update-eslint-all.js @@ -36,7 +36,7 @@ const code = `/* /* eslint quote-props: off -- autogenerated so don't lint */ -module.exports = Object.freeze(${JSON.stringify({ rules: allRules }, null, 4)}); +module.exports = Object.freeze(${JSON.stringify({ name: "@eslint/js/all", rules: allRules }, null, 4)}); `; fs.writeFileSync("./packages/js/src/configs/eslint-all.js", code, "utf8"); From fb50077fec497fbf01d754fc75aa22cff43ef066 Mon Sep 17 00:00:00 2001 From: Gabriel Rohden Date: Thu, 18 Apr 2024 17:51:29 -0300 Subject: [PATCH 093/166] docs: include notes about globals in migration-guide (#18356) * chore: include notes about globals in migration-guide * chore: remove specific error message from code Co-authored-by: Nicholas C. Zakas --------- Co-authored-by: Nicholas C. Zakas --- docs/src/use/configure/migration-guide.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/use/configure/migration-guide.md b/docs/src/use/configure/migration-guide.md index c99080f4da2..f6f6669a631 100644 --- a/docs/src/use/configure/migration-guide.md +++ b/docs/src/use/configure/migration-guide.md @@ -330,6 +330,10 @@ export default [ ]; ``` +::: tip +You'll need to install the `globals` package from npm for this example to work. It is not automatically installed by ESLint. +::: + ### `eslint-env` Configuration Comments In the eslintrc config system it was possible to use `eslint-env` configuration comments to define globals for a file. From 751b518f02b1e9f4f0cb4a4007ffacb1be2246af Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 19 Apr 2024 04:12:34 +0200 Subject: [PATCH 094/166] feat: replace dependency graphemer with `Intl.Segmenter` (#18110) --- lib/shared/string-utils.js | 20 +++++++++----------- package.json | 1 - 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/shared/string-utils.js b/lib/shared/string-utils.js index ed0781e578c..31d0df982ca 100644 --- a/lib/shared/string-utils.js +++ b/lib/shared/string-utils.js @@ -5,12 +5,6 @@ "use strict"; -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const Graphemer = require("graphemer").default; - //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ @@ -18,8 +12,8 @@ const Graphemer = require("graphemer").default; // eslint-disable-next-line no-control-regex -- intentionally including control characters const ASCII_REGEX = /^[\u0000-\u007f]*$/u; -/** @type {Graphemer | undefined} */ -let splitter; +/** @type {Intl.Segmenter | undefined} */ +let segmenter; //------------------------------------------------------------------------------ // Public Interface @@ -47,11 +41,15 @@ function getGraphemeCount(value) { return value.length; } - if (!splitter) { - splitter = new Graphemer(); + segmenter ??= new Intl.Segmenter("en-US"); // en-US locale should be supported everywhere + let graphemeCount = 0; + + // eslint-disable-next-line no-unused-vars -- for-of needs a variable + for (const unused of segmenter.segment(value)) { + graphemeCount++; } - return splitter.countGraphemes(value); + return graphemeCount; } module.exports = { diff --git a/package.json b/package.json index 366974e51b2..8a88f66ade7 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,6 @@ "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", From 594eb0e5c2b14a418d686c33d2d40fb439888b70 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 19 Apr 2024 11:27:13 +0200 Subject: [PATCH 095/166] fix: do not crash on error in `fs.walk` filter (#18295) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: do not crash on error in `fs.walk` filter * `wrapCallback` → `wrapFilter` * do not avoid multiple resolves --- lib/eslint/eslint-helpers.js | 124 ++++++++++++++++++++++------------- tests/lib/eslint/eslint.js | 35 ++++++++++ 2 files changed, 114 insertions(+), 45 deletions(-) diff --git a/lib/eslint/eslint-helpers.js b/lib/eslint/eslint-helpers.js index 44a5a253cd6..97c8c99dd8b 100644 --- a/lib/eslint/eslint-helpers.js +++ b/lib/eslint/eslint-helpers.js @@ -15,7 +15,6 @@ const fsp = fs.promises; const isGlob = require("is-glob"); const hash = require("../cli-engine/hash"); const minimatch = require("minimatch"); -const util = require("util"); const fswalk = require("@nodelib/fs.walk"); const globParent = require("glob-parent"); const isPathInside = require("is-path-inside"); @@ -24,7 +23,6 @@ const isPathInside = require("is-path-inside"); // Fixup references //----------------------------------------------------------------------------- -const doFsWalk = util.promisify(fswalk.walk); const Minimatch = minimatch.Minimatch; const MINIMATCH_OPTIONS = { dot: true }; @@ -280,56 +278,92 @@ async function globSearch({ */ const unmatchedPatterns = new Set([...relativeToPatterns.keys()]); - const filePaths = (await doFsWalk(basePath, { + const filePaths = (await new Promise((resolve, reject) => { - deepFilter(entry) { - const relativePath = normalizeToPosix(path.relative(basePath, entry.path)); - const matchesPattern = matchers.some(matcher => matcher.match(relativePath, true)); - - return matchesPattern && !configs.isDirectoryIgnored(entry.path); - }, - entryFilter(entry) { - const relativePath = normalizeToPosix(path.relative(basePath, entry.path)); + let promiseRejected = false; - // entries may be directories or files so filter out directories - if (entry.dirent.isDirectory()) { + /** + * Wraps a boolean-returning filter function. The wrapped function will reject the promise if an error occurs. + * @param {Function} filter A filter function to wrap. + * @returns {Function} A function similar to the wrapped filter that rejects the promise if an error occurs. + */ + function wrapFilter(filter) { + return (...args) => { + + // No need to run the filter if an error has been thrown. + if (!promiseRejected) { + try { + return filter(...args); + } catch (error) { + promiseRejected = true; + reject(error); + } + } return false; - } + }; + } - /* - * Optimization: We need to track when patterns are left unmatched - * and so we use `unmatchedPatterns` to do that. There is a bit of - * complexity here because the same file can be matched by more than - * one pattern. So, when we start, we actually need to test every - * pattern against every file. Once we know there are no remaining - * unmatched patterns, then we can switch to just looking for the - * first matching pattern for improved speed. - */ - const matchesPattern = unmatchedPatterns.size > 0 - ? matchers.reduce((previousValue, matcher) => { - const pathMatches = matcher.match(relativePath); + fswalk.walk( + basePath, + { + deepFilter: wrapFilter(entry => { + const relativePath = normalizeToPosix(path.relative(basePath, entry.path)); + const matchesPattern = matchers.some(matcher => matcher.match(relativePath, true)); + + return matchesPattern && !configs.isDirectoryIgnored(entry.path); + }), + entryFilter: wrapFilter(entry => { + const relativePath = normalizeToPosix(path.relative(basePath, entry.path)); + + // entries may be directories or files so filter out directories + if (entry.dirent.isDirectory()) { + return false; + } /* - * We updated the unmatched patterns set only if the path - * matches and the file isn't ignored. If the file is - * ignored, that means there wasn't a match for the - * pattern so it should not be removed. - * - * Performance note: isFileIgnored() aggressively caches - * results so there is no performance penalty for calling - * it twice with the same argument. + * Optimization: We need to track when patterns are left unmatched + * and so we use `unmatchedPatterns` to do that. There is a bit of + * complexity here because the same file can be matched by more than + * one pattern. So, when we start, we actually need to test every + * pattern against every file. Once we know there are no remaining + * unmatched patterns, then we can switch to just looking for the + * first matching pattern for improved speed. */ - if (pathMatches && !configs.isFileIgnored(entry.path)) { - unmatchedPatterns.delete(matcher.pattern); - } - - return pathMatches || previousValue; - }, false) - : matchers.some(matcher => matcher.match(relativePath)); - - return matchesPattern && !configs.isFileIgnored(entry.path); - } - + const matchesPattern = unmatchedPatterns.size > 0 + ? matchers.reduce((previousValue, matcher) => { + const pathMatches = matcher.match(relativePath); + + /* + * We updated the unmatched patterns set only if the path + * matches and the file isn't ignored. If the file is + * ignored, that means there wasn't a match for the + * pattern so it should not be removed. + * + * Performance note: isFileIgnored() aggressively caches + * results so there is no performance penalty for calling + * it twice with the same argument. + */ + if (pathMatches && !configs.isFileIgnored(entry.path)) { + unmatchedPatterns.delete(matcher.pattern); + } + + return pathMatches || previousValue; + }, false) + : matchers.some(matcher => matcher.match(relativePath)); + + return matchesPattern && !configs.isFileIgnored(entry.path); + }) + }, + (error, entries) => { + + // If the promise is already rejected, calling `resolve` or `reject` will do nothing. + if (error) { + reject(error); + } else { + resolve(entries); + } + } + ); })).map(entry => entry.path); // now check to see if we have any unmatched patterns diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 66ea4d90c62..0e7bcfc4a26 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -4515,6 +4515,41 @@ describe("ESLint", () => { assert.strictEqual(createCallCount, 1); }); + describe("Error while globbing", () => { + + it("should throw an error with a glob pattern if an invalid config was provided", async () => { + + const cwd = getFixturePath("files"); + + eslint = new ESLint({ + cwd, + overrideConfig: [{ invalid: "foobar" }] + }); + + await assert.rejects(eslint.lintFiles("*.js")); + }); + + it("should throw an error with a glob pattern if an error occurs traversing a directory", async () => { + + const fsWalk = require("@nodelib/fs.walk"); + const error = new Error("Boom!"); + + sinon + .stub(fsWalk, "walk") + .value(sinon.stub().yieldsRight(error)); // call the callback passed to `fs.walk` with an error + + const cwd = getFixturePath("files"); + + eslint = new ESLint({ + cwd, + overrideConfigFile: true + }); + + await assert.rejects(eslint.lintFiles("*.js"), error); + }); + + }); + }); describe("Fix Types", () => { From 155c71c210aaa7235ddadabb067813d8b1c76f65 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 19 Apr 2024 20:04:39 +0000 Subject: [PATCH 096/166] chore: package.json update for @eslint/js release --- packages/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/package.json b/packages/js/package.json index 88772efbe84..a4b5acb40b3 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "9.0.0", + "version": "9.1.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 8d189586d60f9beda7be8cdefd4156c023c4fdde Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 19 Apr 2024 13:46:19 -0700 Subject: [PATCH 097/166] fix: Remove name from eslint/js packages (#18368) --- packages/js/src/configs/eslint-all.js | 9 ++++++++- packages/js/src/configs/eslint-recommended.js | 9 ++++++++- tools/update-eslint-all.js | 10 +++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/js/src/configs/eslint-all.js b/packages/js/src/configs/eslint-all.js index 513cc17d11f..e0753003ed2 100644 --- a/packages/js/src/configs/eslint-all.js +++ b/packages/js/src/configs/eslint-all.js @@ -6,8 +6,15 @@ /* eslint quote-props: off -- autogenerated so don't lint */ +/* + * IMPORTANT! + * + * We cannot add a "name" property to this object because it's still used in eslintrc + * which doesn't support the "name" property. If we add a "name" property, it will + * cause an error. + */ + module.exports = Object.freeze({ - "name": "@eslint/js/all", "rules": { "accessor-pairs": "error", "array-callback-return": "error", diff --git a/packages/js/src/configs/eslint-recommended.js b/packages/js/src/configs/eslint-recommended.js index 083be6b0ee2..3559267efc6 100644 --- a/packages/js/src/configs/eslint-recommended.js +++ b/packages/js/src/configs/eslint-recommended.js @@ -8,8 +8,15 @@ /* eslint sort-keys: ["error", "asc"] -- Long, so make more readable */ +/* + * IMPORTANT! + * + * We cannot add a "name" property to this object because it's still used in eslintrc + * which doesn't support the "name" property. If we add a "name" property, it will + * cause an error. + */ + module.exports = Object.freeze({ - name: "@eslint/js/recommended", rules: Object.freeze({ "constructor-super": "error", "for-direction": "error", diff --git a/tools/update-eslint-all.js b/tools/update-eslint-all.js index d135cffebf5..f6a802de1b7 100644 --- a/tools/update-eslint-all.js +++ b/tools/update-eslint-all.js @@ -36,7 +36,15 @@ const code = `/* /* eslint quote-props: off -- autogenerated so don't lint */ -module.exports = Object.freeze(${JSON.stringify({ name: "@eslint/js/all", rules: allRules }, null, 4)}); +/* + * IMPORTANT! + * + * We cannot add a "name" property to this object because it's still used in eslintrc + * which doesn't support the "name" property. If we add a "name" property, it will + * cause an error. + */ + +module.exports = Object.freeze(${JSON.stringify({ rules: allRules }, null, 4)}); `; fs.writeFileSync("./packages/js/src/configs/eslint-all.js", code, "utf8"); From 50d406d68c0304370fa47d156a407258b68dfa1b Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 19 Apr 2024 20:48:55 +0000 Subject: [PATCH 098/166] chore: package.json update for @eslint/js release --- packages/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/package.json b/packages/js/package.json index a4b5acb40b3..2c9dbf1eec4 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "9.1.0", + "version": "9.1.1", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 03068f13c0e3e6b34b8ca63628cfc79dd256feac Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 19 Apr 2024 14:02:15 -0700 Subject: [PATCH 099/166] feat: Provide helpful error message for nullish configs (#18357) * feat: Provide helpful error message for nullish configs fixes #18259 * Update lib/config/flat-config-array.js Co-authored-by: Francesco Trotta * Update lib/config/flat-config-array.js Co-authored-by: Francesco Trotta --------- Co-authored-by: Francesco Trotta --- lib/config/flat-config-array.js | 101 +++++++++++++++++ package.json | 2 +- tests/lib/config/flat-config-array.js | 154 +++++++++++++++++++++++++- 3 files changed, 253 insertions(+), 4 deletions(-) diff --git a/lib/config/flat-config-array.js b/lib/config/flat-config-array.js index e09fdcef0e5..4312066d18d 100644 --- a/lib/config/flat-config-array.js +++ b/lib/config/flat-config-array.js @@ -79,7 +79,53 @@ function getObjectId(object) { return name; } +/** + * Wraps a config error with details about where the error occurred. + * @param {Error} error The original error. + * @param {number} originalLength The original length of the config array. + * @param {number} baseLength The length of the base config. + * @returns {TypeError} The new error with details. + */ +function wrapConfigErrorWithDetails(error, originalLength, baseLength) { + + let location = "user-defined"; + let configIndex = error.index; + + /* + * A config array is set up in this order: + * 1. Base config + * 2. Original configs + * 3. User-defined configs + * 4. CLI-defined configs + * + * So we need to adjust the index to account for the base config. + * + * - If the index is less than the base length, it's in the base config + * (as specified by `baseConfig` argument to `FlatConfigArray` constructor). + * - If the index is greater than the base length but less than the original + * length + base length, it's in the original config. The original config + * is passed to the `FlatConfigArray` constructor as the first argument. + * - Otherwise, it's in the user-defined config, which is loaded from the + * config file and merged with any command-line options. + */ + if (error.index < baseLength) { + location = "base"; + } else if (error.index < originalLength + baseLength) { + location = "original"; + configIndex = error.index - baseLength; + } else { + configIndex = error.index - originalLength - baseLength; + } + + return new TypeError( + `${error.message.slice(0, -1)} at ${location} index ${configIndex}.`, + { cause: error } + ); +} + const originalBaseConfig = Symbol("originalBaseConfig"); +const originalLength = Symbol("originalLength"); +const baseLength = Symbol("baseLength"); //----------------------------------------------------------------------------- // Exports @@ -106,12 +152,24 @@ class FlatConfigArray extends ConfigArray { schema: flatConfigSchema }); + /** + * The original length of the array before any modifications. + * @type {number} + */ + this[originalLength] = this.length; + if (baseConfig[Symbol.iterator]) { this.unshift(...baseConfig); } else { this.unshift(baseConfig); } + /** + * The length of the array after applying the base config. + * @type {number} + */ + this[baseLength] = this.length - this[originalLength]; + /** * The base config used to build the config array. * @type {Array} @@ -129,6 +187,49 @@ class FlatConfigArray extends ConfigArray { Object.defineProperty(this, "shouldIgnore", { writable: false }); } + /** + * Normalizes the array by calling the superclass method and catching/rethrowing + * any ConfigError exceptions with additional details. + * @param {any} [context] The context to use to normalize the array. + * @returns {Promise} A promise that resolves when the array is normalized. + */ + normalize(context) { + return super.normalize(context) + .catch(error => { + if (error.name === "ConfigError") { + throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]); + } + + throw error; + + }); + } + + /** + * Normalizes the array by calling the superclass method and catching/rethrowing + * any ConfigError exceptions with additional details. + * @param {any} [context] The context to use to normalize the array. + * @returns {FlatConfigArray} The current instance. + * @throws {TypeError} If the config is invalid. + */ + normalizeSync(context) { + + try { + + return super.normalizeSync(context); + + } catch (error) { + + if (error.name === "ConfigError") { + throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]); + } + + throw error; + + } + + } + /* eslint-disable class-methods-use-this -- Desired as instance method */ /** * Replaces a config with another config to allow us to put strings diff --git a/package.json b/package.json index 8a88f66ade7..c08cf158ebc 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^3.0.2", "@eslint/js": "9.0.0", - "@humanwhocodes/config-array": "^0.12.3", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.2.3", "@nodelib/fs.walk": "^1.2.8", diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index a8bff24417a..9da4d050ddf 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -191,9 +191,9 @@ async function assertMergedResult(values, result) { async function assertInvalidConfig(values, message) { const configs = createFlatConfigArray(values); - await configs.normalize(); assert.throws(() => { + configs.normalizeSync(); configs.getConfig("foo.js"); }, message); } @@ -719,14 +719,162 @@ describe("FlatConfigArray", () => { describe("Config array elements", () => { it("should error on 'eslint:recommended' string config", async () => { - await assertInvalidConfig(["eslint:recommended"], "All arguments must be objects."); + await assertInvalidConfig(["eslint:recommended"], "Config (unnamed): Unexpected non-object config at original index 0."); }); it("should error on 'eslint:all' string config", async () => { - await assertInvalidConfig(["eslint:all"], "All arguments must be objects."); + await assertInvalidConfig(["eslint:all"], "Config (unnamed): Unexpected non-object config at original index 0."); + }); + + + it("should throw an error when undefined original config is normalized", () => { + + const configs = new FlatConfigArray([void 0]); + + assert.throws(() => { + configs.normalizeSync(); + }, "Config (unnamed): Unexpected undefined config at original index 0."); + + }); + + it("should throw an error when undefined original config is normalized asynchronously", async () => { + + const configs = new FlatConfigArray([void 0]); + + try { + await configs.normalize(); + assert.fail("Error not thrown"); + } catch (error) { + assert.strictEqual(error.message, "Config (unnamed): Unexpected undefined config at original index 0."); + } + + }); + + it("should throw an error when null original config is normalized", () => { + + const configs = new FlatConfigArray([null]); + + assert.throws(() => { + configs.normalizeSync(); + }, "Config (unnamed): Unexpected null config at original index 0."); + + }); + + it("should throw an error when null original config is normalized asynchronously", async () => { + + const configs = new FlatConfigArray([null]); + + try { + await configs.normalize(); + assert.fail("Error not thrown"); + } catch (error) { + assert.strictEqual(error.message, "Config (unnamed): Unexpected null config at original index 0."); + } + + }); + + it("should throw an error when undefined base config is normalized", () => { + + const configs = new FlatConfigArray([], { baseConfig: [void 0] }); + + assert.throws(() => { + configs.normalizeSync(); + }, "Config (unnamed): Unexpected undefined config at base index 0."); + + }); + + it("should throw an error when undefined base config is normalized asynchronously", async () => { + + const configs = new FlatConfigArray([], { baseConfig: [void 0] }); + + try { + await configs.normalize(); + assert.fail("Error not thrown"); + } catch (error) { + assert.strictEqual(error.message, "Config (unnamed): Unexpected undefined config at base index 0."); + } + + }); + + it("should throw an error when null base config is normalized", () => { + + const configs = new FlatConfigArray([], { baseConfig: [null] }); + + assert.throws(() => { + configs.normalizeSync(); + }, "Config (unnamed): Unexpected null config at base index 0."); + + }); + + it("should throw an error when null base config is normalized asynchronously", async () => { + + const configs = new FlatConfigArray([], { baseConfig: [null] }); + + try { + await configs.normalize(); + assert.fail("Error not thrown"); + } catch (error) { + assert.strictEqual(error.message, "Config (unnamed): Unexpected null config at base index 0."); + } + }); + it("should throw an error when undefined user-defined config is normalized", () => { + + const configs = new FlatConfigArray([]); + + configs.push(void 0); + + assert.throws(() => { + configs.normalizeSync(); + }, "Config (unnamed): Unexpected undefined config at user-defined index 0."); + + }); + + it("should throw an error when undefined user-defined config is normalized asynchronously", async () => { + + const configs = new FlatConfigArray([]); + + configs.push(void 0); + + try { + await configs.normalize(); + assert.fail("Error not thrown"); + } catch (error) { + assert.strictEqual(error.message, "Config (unnamed): Unexpected undefined config at user-defined index 0."); + } + + }); + + it("should throw an error when null user-defined config is normalized", () => { + + const configs = new FlatConfigArray([]); + + configs.push(null); + + assert.throws(() => { + configs.normalizeSync(); + }, "Config (unnamed): Unexpected null config at user-defined index 0."); + + }); + + it("should throw an error when null user-defined config is normalized asynchronously", async () => { + + const configs = new FlatConfigArray([]); + + configs.push(null); + + try { + await configs.normalize(); + assert.fail("Error not thrown"); + } catch (error) { + assert.strictEqual(error.message, "Config (unnamed): Unexpected null config at user-defined index 0."); + } + + }); + + }); describe("Config Properties", () => { From d9a2983e1301599117cf554aa6a9bd44b84f2e55 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 19 Apr 2024 23:12:06 +0200 Subject: [PATCH 100/166] chore: upgrade @eslint/js to v9.1.1 (#18367) * Update package.json * Use `@eslint/js` v9.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c08cf158ebc..25118970b11 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^3.0.2", - "@eslint/js": "9.0.0", + "@eslint/js": "9.1.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.2.3", From e4d9c921f64175d90de4d3094b4d68f4da4f95cf Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 19 Apr 2024 21:29:06 +0000 Subject: [PATCH 101/166] Build: changelog update for 9.1.0 --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0a8e666538..c18aa9dae82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,37 @@ +v9.1.0 - April 19, 2024 + +* [`d9a2983`](https://github.com/eslint/eslint/commit/d9a2983e1301599117cf554aa6a9bd44b84f2e55) chore: upgrade @eslint/js to v9.1.1 (#18367) (Francesco Trotta) +* [`03068f1`](https://github.com/eslint/eslint/commit/03068f13c0e3e6b34b8ca63628cfc79dd256feac) feat: Provide helpful error message for nullish configs (#18357) (Nicholas C. Zakas) +* [`50d406d`](https://github.com/eslint/eslint/commit/50d406d68c0304370fa47d156a407258b68dfa1b) chore: package.json update for @eslint/js release (Jenkins) +* [`8d18958`](https://github.com/eslint/eslint/commit/8d189586d60f9beda7be8cdefd4156c023c4fdde) fix: Remove name from eslint/js packages (#18368) (Nicholas C. Zakas) +* [`155c71c`](https://github.com/eslint/eslint/commit/155c71c210aaa7235ddadabb067813d8b1c76f65) chore: package.json update for @eslint/js release (Jenkins) +* [`594eb0e`](https://github.com/eslint/eslint/commit/594eb0e5c2b14a418d686c33d2d40fb439888b70) fix: do not crash on error in `fs.walk` filter (#18295) (Francesco Trotta) +* [`751b518`](https://github.com/eslint/eslint/commit/751b518f02b1e9f4f0cb4a4007ffacb1be2246af) feat: replace dependency graphemer with `Intl.Segmenter` (#18110) (Francesco Trotta) +* [`fb50077`](https://github.com/eslint/eslint/commit/fb50077fec497fbf01d754fc75aa22cff43ef066) docs: include notes about globals in migration-guide (#18356) (Gabriel Rohden) +* [`4d11e56`](https://github.com/eslint/eslint/commit/4d11e567baff575146fd267b3765ab2c788aa1e5) feat: add `name` to eslint configs (#18289) (唯然) +* [`1cbe1f6`](https://github.com/eslint/eslint/commit/1cbe1f6d38272784307c260f2375ab30e68716e8) feat: allow `while(true)` in `no-constant-condition` (#18286) (Tanuj Kanti) +* [`0588fc5`](https://github.com/eslint/eslint/commit/0588fc5ecb87fddd70e1848e417ba712b48473c3) refactor: Move directive gathering to SourceCode (#18328) (Nicholas C. Zakas) +* [`0d8cf63`](https://github.com/eslint/eslint/commit/0d8cf6350ce3dc417d6e23922e6d4ad03952aaaa) fix: EMFILE errors (#18313) (Nicholas C. Zakas) +* [`e1ac0b5`](https://github.com/eslint/eslint/commit/e1ac0b5c035bfdff7be08b69e89e1470a7becac3) fix: --inspect-config only for flat config and respect -c (#18306) (Nicholas C. Zakas) +* [`09675e1`](https://github.com/eslint/eslint/commit/09675e153169d4d0f4a85a95007dcd17d34d70c7) fix: `--no-ignore` should not apply to non-global ignores (#18334) (Milos Djermanovic) +* [`9048e21`](https://github.com/eslint/eslint/commit/9048e2184c19799bb9b8a5908345d4ce05020c41) chore: lint `docs/src/_data` js files (#18335) (Milos Djermanovic) +* [`4820790`](https://github.com/eslint/eslint/commit/48207908a8291916a124af60e02d0327276f8957) chore: upgrade globals@15.0.0 dev dependency (#18332) (Milos Djermanovic) +* [`698d9ff`](https://github.com/eslint/eslint/commit/698d9ff2c9c4e24836d69358b93d42c356eb853b) chore: upgrade jsdoc & unicorn plugins in eslint-config-eslint (#18333) (Milos Djermanovic) +* [`71c771f`](https://github.com/eslint/eslint/commit/71c771fb390cf178220d06fd7316033a385128a9) docs: Fix missing accessible name for scroll-to-top link (#18329) (Germán Freixinós) +* [`0db676f`](https://github.com/eslint/eslint/commit/0db676f9c64d2622ada86b653136d2bda4f0eee0) feat: add `Intl` in es6 globals (#18318) (唯然) +* [`200fd4e`](https://github.com/eslint/eslint/commit/200fd4e3223d1ad22dca3dc79aa6eaa860fefe32) docs: indicate eslintrc mode for `.eslintignore` (#18285) (Francesco Trotta) +* [`32c08cf`](https://github.com/eslint/eslint/commit/32c08cf66536e595e93284500b0b8d702e30cfd8) chore: drop Node < 18 and use @eslint/js v9 in eslint-config-eslint (#18323) (Milos Djermanovic) +* [`16b6a8b`](https://github.com/eslint/eslint/commit/16b6a8b469d2e0ba6d904b9e858711590568b246) docs: Update README (GitHub Actions Bot) +* [`a76fb55`](https://github.com/eslint/eslint/commit/a76fb55004ea095c68dde134ca7db0212c93c86e) chore: @eslint-community/eslint-plugin-eslint-comments v4.3.0 (#18319) (Milos Djermanovic) +* [`df5f8a9`](https://github.com/eslint/eslint/commit/df5f8a9bc1042c13f1969c9fbd8c72eee0662daa) docs: `paths` and `patterns` difference in `no-restricted-imports` (#18273) (Tanuj Kanti) +* [`c537d76`](https://github.com/eslint/eslint/commit/c537d76327586616b7ca5d00e76eaf6c76e6bcd2) docs: update `npm init @eslint/config` generated file names (#18298) (唯然) +* [`78e45b1`](https://github.com/eslint/eslint/commit/78e45b1d8d6b673ced233ca82b9ff1dddcdd1fec) chore: eslint-plugin-eslint-plugin v6.0.0 (#18316) (唯然) +* [`36103a5`](https://github.com/eslint/eslint/commit/36103a52432fffa20b90f2c6960757e6b9dc778f) chore: eslint-plugin-n v17.0.0 (#18315) (唯然) +* [`e1e305d`](https://github.com/eslint/eslint/commit/e1e305defaab98605d79c81d67ee5a48558c458a) docs: fix `linebreak-style` examples (#18262) (Francesco Trotta) +* [`113f51e`](https://github.com/eslint/eslint/commit/113f51ec4e52d3082a74b9682239a6e28d1a70ee) docs: Mention package.json config support dropped (#18305) (Nicholas C. Zakas) +* [`1fa6622`](https://github.com/eslint/eslint/commit/1fa66220ad130eeb69cfa0207d3896b7bb09c576) build: do not use `--force` flag to install dependencies (#18284) (Francesco Trotta) +* [`5c35321`](https://github.com/eslint/eslint/commit/5c353215e05818e17e83192acbb4d3730c716afa) docs: add eslintrc-only note to `--rulesdir` (#18281) (Adam Lui 刘展鹏) + v9.0.0 - April 5, 2024 * [`19f9a89`](https://github.com/eslint/eslint/commit/19f9a8926bd7888ab4a813ae323ad3c332fd5d5c) chore: Update dependencies for v9.0.0 (#18275) (Nicholas C. Zakas) From b78d831e244171c939279b03be519b5c13836fce Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 19 Apr 2024 21:29:07 +0000 Subject: [PATCH 102/166] 9.1.0 --- docs/package.json | 2 +- docs/src/_data/versions.json | 2 +- docs/src/use/formatters/html-formatter-example.html | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/package.json b/docs/package.json index 0bcceba76ac..8febd3a3675 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "9.0.0", + "version": "9.1.0", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/_data/versions.json b/docs/src/_data/versions.json index 09d0c1d79c4..fb794b64a09 100644 --- a/docs/src/_data/versions.json +++ b/docs/src/_data/versions.json @@ -6,7 +6,7 @@ "path": "/docs/head/" }, { - "version": "9.0.0", + "version": "9.1.0", "branch": "latest", "path": "/docs/latest/" }, diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index 570830c9204..855cfa6aa91 100644 --- a/docs/src/use/formatters/html-formatter-example.html +++ b/docs/src/use/formatters/html-formatter-example.html @@ -118,7 +118,7 @@

ESLint Report

- 8 problems (4 errors, 4 warnings) - Generated on Fri Apr 05 2024 20:52:07 GMT+0000 (Coordinated Universal Time) + 8 problems (4 errors, 4 warnings) - Generated on Fri Apr 19 2024 21:29:08 GMT+0000 (Coordinated Universal Time)
diff --git a/package.json b/package.json index 25118970b11..6f1682fb9db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "9.0.0", + "version": "9.1.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From a26b40279f283853717236b44602b27b57f0b627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Tue, 23 Apr 2024 03:07:29 +0800 Subject: [PATCH 103/166] fix: use @eslint/create-config latest (#18373) * docs: use @eslint/create-config latest refs: https://github.com/eslint/eslint/issues/18369, https://github.com/eslint/eslint/discussions/18361, * chore: review suggestions --- README.md | 2 +- bin/eslint.js | 4 ++-- docs/src/use/getting-started.md | 4 ++-- messages/no-config-found.js | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7f97a76cd9f..28af2b81000 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Prerequisites: [Node.js](https://nodejs.org/) (`^18.18.0`, `^20.9.0`, or `>=21.1 You can install and configure ESLint using this command: ```shell -npm init @eslint/config +npm init @eslint/config@latest ``` After that, you can run ESLint on any file or directory like this: diff --git a/bin/eslint.js b/bin/eslint.js index 46ca98738b2..5f8eaa9b451 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -140,11 +140,11 @@ ${getErrorMessage(error)}`; if (process.argv.includes("--init")) { // `eslint --init` has been moved to `@eslint/create-config` - console.warn("You can also run this command directly using 'npm init @eslint/config'."); + console.warn("You can also run this command directly using 'npm init @eslint/config@latest'."); const spawn = require("cross-spawn"); - spawn.sync("npm", ["init", "@eslint/config"], { encoding: "utf8", stdio: "inherit" }); + spawn.sync("npm", ["init", "@eslint/config@latest"], { encoding: "utf8", stdio: "inherit" }); return; } diff --git a/docs/src/use/getting-started.md b/docs/src/use/getting-started.md index 449e3575885..1dcf471a152 100644 --- a/docs/src/use/getting-started.md +++ b/docs/src/use/getting-started.md @@ -21,7 +21,7 @@ To use ESLint, you must have [Node.js](https://nodejs.org/en/) (`^18.18.0`, `^20 You can install and configure ESLint using this command: ```shell -npm init @eslint/config +npm init @eslint/config@latest ``` If you want to use a specific shareable config that is hosted on npm, you can use the `--config` option and specify the package name: @@ -30,7 +30,7 @@ If you want to use a specific shareable config that is hosted on npm, you can us # use `eslint-config-standard` shared config # npm 7+ -npm init @eslint/config -- --config eslint-config-standard +npm init @eslint/config@latest -- --config eslint-config-standard ``` diff --git a/messages/no-config-found.js b/messages/no-config-found.js index 21cf549ebc8..64b93edbca1 100644 --- a/messages/no-config-found.js +++ b/messages/no-config-found.js @@ -6,7 +6,7 @@ module.exports = function(it) { return ` ESLint couldn't find a configuration file. To set up a configuration file for this project, please run: - npm init @eslint/config + npm init @eslint/config@latest ESLint looked for configuration files in ${directoryPath} and its ancestors. If it found none, it then looked in your home directory. From ef36aa46b9d946d67f90c0b6aa07d66c83b5fcbc Mon Sep 17 00:00:00 2001 From: Jenkins Date: Mon, 22 Apr 2024 19:21:45 +0000 Subject: [PATCH 104/166] Build: changelog update for 9.1.1 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c18aa9dae82..9e815421a60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.1.1 - April 22, 2024 + +* [`a26b402`](https://github.com/eslint/eslint/commit/a26b40279f283853717236b44602b27b57f0b627) fix: use @eslint/create-config latest (#18373) (唯然) + v9.1.0 - April 19, 2024 * [`d9a2983`](https://github.com/eslint/eslint/commit/d9a2983e1301599117cf554aa6a9bd44b84f2e55) chore: upgrade @eslint/js to v9.1.1 (#18367) (Francesco Trotta) From b4d2512809a1b28466ad1ce5af9d01c181b9bf9e Mon Sep 17 00:00:00 2001 From: Jenkins Date: Mon, 22 Apr 2024 19:21:46 +0000 Subject: [PATCH 105/166] 9.1.1 --- docs/package.json | 2 +- docs/src/_data/versions.json | 2 +- docs/src/use/formatters/html-formatter-example.html | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/package.json b/docs/package.json index 8febd3a3675..4c5bfbe44a4 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "9.1.0", + "version": "9.1.1", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/_data/versions.json b/docs/src/_data/versions.json index fb794b64a09..02b8561e4cb 100644 --- a/docs/src/_data/versions.json +++ b/docs/src/_data/versions.json @@ -6,7 +6,7 @@ "path": "/docs/head/" }, { - "version": "9.1.0", + "version": "9.1.1", "branch": "latest", "path": "/docs/latest/" }, diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index 855cfa6aa91..a2b2ac85b5f 100644 --- a/docs/src/use/formatters/html-formatter-example.html +++ b/docs/src/use/formatters/html-formatter-example.html @@ -118,7 +118,7 @@

ESLint Report

- 8 problems (4 errors, 4 warnings) - Generated on Fri Apr 19 2024 21:29:08 GMT+0000 (Coordinated Universal Time) + 8 problems (4 errors, 4 warnings) - Generated on Mon Apr 22 2024 19:21:47 GMT+0000 (Coordinated Universal Time)
diff --git a/package.json b/package.json index 6f1682fb9db..19ec7b9c0ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "9.1.0", + "version": "9.1.1", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From f12a02c5749d31beefe46d2753a0d68b56f2281d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Tue, 23 Apr 2024 18:19:38 +0800 Subject: [PATCH 106/166] docs: update to eslint v9 in custom-rule-tutorial (#18383) * docs: update rule tester options in custom-rule-tutorial Fixes #18382 * chore: update examples * chore: update more places to eslint v9 --- .../custom-rule-tutorial-code/enforce-foo-bar.test.js | 6 +++--- docs/_examples/custom-rule-tutorial-code/package.json | 7 ++++--- docs/_examples/integration-tutorial-code/package.json | 3 ++- docs/src/extend/custom-rule-tutorial.md | 10 +++++----- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.test.js b/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.test.js index d5f9c40334d..3498c2261c1 100644 --- a/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.test.js +++ b/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.test.js @@ -1,4 +1,4 @@ -/** +/** * @fileoverview Tests for enforce-foo-bar.js rule. * @author Ben Perlmutter */ @@ -10,7 +10,7 @@ const fooBarRule = require("./enforce-foo-bar"); const ruleTester = new RuleTester({ // Must use at least ecmaVersion 2015 because // that's when `const` variable were introduced. - parserOptions: { ecmaVersion: 2015 } + languageOptions: { ecmaVersion: 2015 } }); // Throws error if the tests in ruleTester.run() do not pass @@ -31,4 +31,4 @@ ruleTester.run( } ); -console.log("All tests passed!"); \ No newline at end of file +console.log("All tests passed!"); diff --git a/docs/_examples/custom-rule-tutorial-code/package.json b/docs/_examples/custom-rule-tutorial-code/package.json index 0578c79496c..12c159677a2 100644 --- a/docs/_examples/custom-rule-tutorial-code/package.json +++ b/docs/_examples/custom-rule-tutorial-code/package.json @@ -1,5 +1,6 @@ { "name": "eslint-plugin-example", + "private": true, "version": "1.0.0", "description": "ESLint plugin for enforce-foo-bar rule.", "main": "eslint-plugin-example.js", @@ -9,7 +10,7 @@ "eslint-plugin" ], "peerDependencies": { - "eslint": ">=8.0.0" + "eslint": ">=9.0.0" }, "scripts": { "test": "node enforce-foo-bar.test.js" @@ -17,6 +18,6 @@ "author": "", "license": "ISC", "devDependencies": { - "eslint": "^8.36.0" + "eslint": "^9.1.1" } -} \ No newline at end of file +} diff --git a/docs/_examples/integration-tutorial-code/package.json b/docs/_examples/integration-tutorial-code/package.json index df00d7382f1..34dca364511 100644 --- a/docs/_examples/integration-tutorial-code/package.json +++ b/docs/_examples/integration-tutorial-code/package.json @@ -1,5 +1,6 @@ { "name": "_integration-tutorial-code", + "private": true, "version": "1.0.0", "description": "", "main": "index.js", @@ -10,6 +11,6 @@ "author": "", "license": "ISC", "dependencies": { - "eslint": "^8.39.0" + "eslint": "^9.1.1" } } diff --git a/docs/src/extend/custom-rule-tutorial.md b/docs/src/extend/custom-rule-tutorial.md index 9b03cab075a..7dd748daff8 100644 --- a/docs/src/extend/custom-rule-tutorial.md +++ b/docs/src/extend/custom-rule-tutorial.md @@ -217,7 +217,7 @@ const fooBarRule = require("./enforce-foo-bar"); const ruleTester = new RuleTester({ // Must use at least ecmaVersion 2015 because // that's when `const` variables were introduced. - parserOptions: { ecmaVersion: 2015 } + languageOptions: { ecmaVersion: 2015 } }); // Throws error if the tests in ruleTester.run() do not pass @@ -362,7 +362,7 @@ To publish a plugin containing a rule to npm, you need to configure the `package 1. `"name"`: A unique name for the package. No other package on npm can have the same name. 1. `"main"`: The relative path to the plugin file. Following this example, the path is `"eslint-plugin-example.js"`. 1. `"description"`: A description of the package that's viewable on npm. -1. `"peerDependencies"`: Add `"eslint": ">=8.0.0"` as a peer dependency. Any version greater than or equal to that is necessary to use the plugin. Declaring `eslint` as a peer dependency requires that users add the package to the project separately from the plugin. +1. `"peerDependencies"`: Add `"eslint": ">=9.0.0"` as a peer dependency. Any version greater than or equal to that is necessary to use the plugin. Declaring `eslint` as a peer dependency requires that users add the package to the project separately from the plugin. 1. `"keywords"`: Include the standard keywords `["eslint", "eslintplugin", "eslint-plugin"]` to make the package easy to find. You can add any other keywords that might be relevant to your plugin as well. A complete annotated example of what a plugin's `package.json` file should look like: @@ -379,9 +379,9 @@ A complete annotated example of what a plugin's `package.json` file should look "scripts": { "test": "node enforce-foo-bar.test.js" }, - // Add eslint>=8.0.0 as a peer dependency. + // Add eslint>=9.0.0 as a peer dependency. "peerDependencies": { - "eslint": ">=8.0.0" + "eslint": ">=9.0.0" }, // Add these standard keywords to make plugin easy to find! "keywords": [ @@ -392,7 +392,7 @@ A complete annotated example of what a plugin's `package.json` file should look "author": "", "license": "ISC", "devDependencies": { - "eslint": "^8.36.0" + "eslint": "^9.0.0" } } ``` From eeec41346738afb491958fdbf0bcf45a302ca1b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Tue, 23 Apr 2024 18:55:59 +0800 Subject: [PATCH 107/166] fix: do not throw when defining a global named __defineSetter__ (#18364) It replaced {} with `Object.create(null)` to avoid accessing properties on the Object prototype. fixes #18363 --- lib/linter/linter.js | 2 +- lib/source-code/source-code.js | 2 +- tests/lib/linter/linter.js | 51 ++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/lib/linter/linter.js b/lib/linter/linter.js index c76b85224d1..9154fd703e5 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -775,7 +775,7 @@ function createLanguageOptions({ globals: configuredGlobals, parser, parserOptio */ function resolveGlobals(providedGlobals, enabledEnvironments) { return Object.assign( - {}, + Object.create(null), ...enabledEnvironments.filter(env => env.globals).map(env => env.globals), providedGlobals ); diff --git a/lib/source-code/source-code.js b/lib/source-code/source-code.js index c749c2e45c5..0ed291afa4f 100644 --- a/lib/source-code/source-code.js +++ b/lib/source-code/source-code.js @@ -1063,7 +1063,7 @@ class SourceCode extends TokenStore { * https://github.com/eslint/eslint/issues/16302 */ const configGlobals = Object.assign( - {}, + Object.create(null), // https://github.com/eslint/eslint/issues/18363 getGlobalsForEcmaVersion(languageOptions.ecmaVersion), languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0, languageOptions.globals diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index 6e98b93f3a1..d839fd5d9d4 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -10250,6 +10250,48 @@ describe("Linter with FlatConfigArray", () => { describe("when evaluating code containing /*global */ and /*globals */ blocks", () => { + /** + * Asserts the global variables in the provided code using the specified language options and data. + * @param {string} code The code to verify. + * @param {Object} languageOptions The language options to use. + * @param {Object} [data={}] Additional data for the assertion. + * @returns {void} + */ + function assertGlobalVariable(code, languageOptions, data = {}) { + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node); + const g = getVariable(scope, data.name); + + assert.strictEqual(g.name, data.name); + assert.strictEqual(g.writeable, data.writeable); + }); + + return { Program: spy }; + } + } + } + } + }, + rules: { "test/checker": "error" } + }; + + if (languageOptions !== void 0) { + config.languageOptions = languageOptions; + } + + linter.verify(code, config); + assert(spy && spy.calledOnce); + + } + it("variables should be available in global scope", () => { const code = ` /*global a b:true c:false d:readable e:writeable Math:off */ @@ -10310,6 +10352,15 @@ describe("Linter with FlatConfigArray", () => { linter.verify(code, config); assert(spy && spy.calledOnce); }); + + // https://github.com/eslint/eslint/issues/18363 + it("not throw when defining a global named __defineSetter__", () => { + assertGlobalVariable("/*global __defineSetter__ */", {}, { name: "__defineSetter__", writeable: false }); + assertGlobalVariable("/*global __defineSetter__ */", void 0, { name: "__defineSetter__", writeable: false }); + assertGlobalVariable("/*global __defineSetter__ */", { globals: { __defineSetter__: "off" } }, { name: "__defineSetter__", writeable: false }); + assertGlobalVariable("/*global __defineSetter__ */", { globals: { __defineSetter__: "writeable" } }, { name: "__defineSetter__", writeable: false }); + assertGlobalVariable("/*global __defineSetter__:writeable */", {}, { name: "__defineSetter__", writeable: true }); + }); }); describe("when evaluating code containing a /*global */ block with sloppy whitespace", () => { From f316e2009a8aa902fa447a49b6b5e560848f0711 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Thu, 25 Apr 2024 07:35:17 +0200 Subject: [PATCH 108/166] ci: run tests in Node.js 22 (#18393) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e06d299bdc2..1ddad354441 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node: [21.x, 20.x, 18.x, "18.18.0"] + node: [22.x, 21.x, 20.x, 18.x, "18.18.0"] include: - os: windows-latest node: "lts/*" From 1579ce05cbb523cb5b04ff77fab06ba1ecd18dce Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger <53019676+kirkwaiblinger@users.noreply.github.com> Date: Thu, 25 Apr 2024 02:50:50 -0600 Subject: [PATCH 109/166] docs: update wording regarding indirect eval (#18394) update doc on indirect eval --- docs/src/rules/no-eval.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/rules/no-eval.md b/docs/src/rules/no-eval.md index 69b4f19828d..15c59ecc98b 100644 --- a/docs/src/rules/no-eval.md +++ b/docs/src/rules/no-eval.md @@ -105,7 +105,7 @@ class A { ### allowIndirect -This rule has an option to allow indirect calls to `eval`. +This rule has an option to allow ["indirect eval"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#direct_and_indirect_eval). Indirect calls to `eval` are less dangerous than direct calls to `eval` because they cannot dynamically change the scope. Because of this, they also will not negatively impact performance to the degree of direct `eval`. ```js From a498f35cef4df9c9f5387fafafaf482d913d5765 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Thu, 25 Apr 2024 17:09:03 +0200 Subject: [PATCH 110/166] feat: update Unicode letter detection in capitalized-comments rule (#18375) * feat: simplify `LETTER_PATTERN` regexp * check characters beyond U+FFFF --- lib/rules/capitalized-comments.js | 17 ++++++----- lib/rules/utils/patterns/letters.js | 36 ----------------------- tests/lib/rules/capitalized-comments.js | 38 +++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 43 deletions(-) delete mode 100644 lib/rules/utils/patterns/letters.js diff --git a/lib/rules/capitalized-comments.js b/lib/rules/capitalized-comments.js index 3a17b056620..07a27b6ec96 100644 --- a/lib/rules/capitalized-comments.js +++ b/lib/rules/capitalized-comments.js @@ -8,7 +8,6 @@ // Requirements //------------------------------------------------------------------------------ -const LETTER_PATTERN = require("./utils/patterns/letters"); const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ @@ -17,7 +16,8 @@ const astUtils = require("./utils/ast-utils"); const DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN, WHITESPACE = /\s/gu, - MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/u; // TODO: Combine w/ max-len pattern? + MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/u, // TODO: Combine w/ max-len pattern? + LETTER_PATTERN = /\p{L}/u; /* * Base schema body for defining the basic capitalization rule, ignorePattern, @@ -233,7 +233,8 @@ module.exports = { return true; } - const firstWordChar = commentWordCharsOnly[0]; + // Get the first Unicode character (1 or 2 code units). + const [firstWordChar] = commentWordCharsOnly; if (!LETTER_PATTERN.test(firstWordChar)) { return true; @@ -273,12 +274,14 @@ module.exports = { messageId, fix(fixer) { const match = comment.value.match(LETTER_PATTERN); + const char = match[0]; - return fixer.replaceTextRange( + // Offset match.index by 2 to account for the first 2 characters that start the comment (// or /*) + const charIndex = comment.range[0] + match.index + 2; - // Offset match.index by 2 to account for the first 2 characters that start the comment (// or /*) - [comment.range[0] + match.index + 2, comment.range[0] + match.index + 3], - capitalize === "always" ? match[0].toLocaleUpperCase() : match[0].toLocaleLowerCase() + return fixer.replaceTextRange( + [charIndex, charIndex + char.length], + capitalize === "always" ? char.toLocaleUpperCase() : char.toLocaleLowerCase() ); } }); diff --git a/lib/rules/utils/patterns/letters.js b/lib/rules/utils/patterns/letters.js deleted file mode 100644 index 9bb2de31010..00000000000 --- a/lib/rules/utils/patterns/letters.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @fileoverview Pattern for detecting any letter (even letters outside of ASCII). - * NOTE: This file was generated using this script in JSCS based on the Unicode 7.0.0 standard: https://github.com/jscs-dev/node-jscs/blob/f5ed14427deb7e7aac84f3056a5aab2d9f3e563e/publish/helpers/generate-patterns.js - * Do not edit this file by hand-- please use https://github.com/mathiasbynens/regenerate to regenerate the regular expression exported from this file. - * @author Kevin Partington - * @license MIT License (from JSCS). See below. - */ - -/* - * The MIT License (MIT) - * - * Copyright 2013-2016 Dulin Marat and other contributors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -"use strict"; - -module.exports = /[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDE00-\uDE11\uDE13-\uDE2B\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF5D-\uDF61]|\uD805[\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDE00-\uDE2F\uDE44\uDE80-\uDEAA]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF98]|[\uD80C\uD840-\uD868\uD86A-\uD86C][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D]|\uD87E[\uDC00-\uDE1D]/u; diff --git a/tests/lib/rules/capitalized-comments.js b/tests/lib/rules/capitalized-comments.js index 1b5df6db858..09fa95c6678 100644 --- a/tests/lib/rules/capitalized-comments.js +++ b/tests/lib/rules/capitalized-comments.js @@ -367,6 +367,24 @@ ruleTester.run("capitalized-comments", rule, { column: 1 }] }, + { + code: "// ꮳꮃꭹ", + output: "// Ꮳꮃꭹ", + errors: [{ + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1 + }] + }, + { + code: "/* 𐳡𐳡𐳡 */", // right-to-left-text + output: "/* 𐲡𐳡𐳡 */", + errors: [{ + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1 + }] + }, // Using "always" string option { @@ -541,6 +559,26 @@ ruleTester.run("capitalized-comments", rule, { column: 1 }] }, + { + code: "// Გ", // Georgian Mtavruli Capital Letter Gan (U+1C92) + output: "// გ", // Georgian Letter Gan (U+10D2) + options: ["never"], + errors: [{ + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1 + }] + }, + { + code: "// 𑢢", // Warang Citi Capital Letter Wi (U+118A2) + output: "// 𑣂", // Warang Citi Small Letter Wi (U+118C2) + options: ["never"], + errors: [{ + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1 + }] + }, // Default ignore words should be warned if there are non-whitespace characters in the way { From 8485d76134bdbd29230780fadc284c482cd1d963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Fri, 26 Apr 2024 07:28:15 -0400 Subject: [PATCH 111/166] feat: `no-case-declarations` add suggestions (#18388) * feat: `no-case-declarations` add suggestions * Add extra test case for declared identifiers --- lib/rules/no-case-declarations.js | 14 +- tests/lib/rules/no-case-declarations.js | 265 ++++++++++++++++++++++-- 2 files changed, 260 insertions(+), 19 deletions(-) diff --git a/lib/rules/no-case-declarations.js b/lib/rules/no-case-declarations.js index 8dc5b021d82..55f82e241f0 100644 --- a/lib/rules/no-case-declarations.js +++ b/lib/rules/no-case-declarations.js @@ -19,9 +19,12 @@ module.exports = { url: "https://eslint.org/docs/latest/rules/no-case-declarations" }, + hasSuggestions: true, + schema: [], messages: { + addBrackets: "Add {} brackets around the case block.", unexpected: "Unexpected lexical declaration in case block." } }, @@ -53,7 +56,16 @@ module.exports = { if (isLexicalDeclaration(statement)) { context.report({ node: statement, - messageId: "unexpected" + messageId: "unexpected", + suggest: [ + { + messageId: "addBrackets", + fix: fixer => [ + fixer.insertTextBefore(node.consequent[0], "{ "), + fixer.insertTextAfter(node.consequent.at(-1), " }") + ] + } + ] }); } } diff --git a/tests/lib/rules/no-case-declarations.js b/tests/lib/rules/no-case-declarations.js index e6ffcf3b4a5..63d862396fb 100644 --- a/tests/lib/rules/no-case-declarations.js +++ b/tests/lib/rules/no-case-declarations.js @@ -37,79 +37,308 @@ ruleTester.run("no-case-declarations", rule, { languageOptions: { ecmaVersion: 6 } }, ` + switch (a) { + case 1: + case 2: {} + } + `, + ` + switch (a) { + case 1: var x; + } + ` + ], + invalid: [ + { + code: ` switch (a) { case 1: - case 2: {} + {} + function f() {} + break; } `, - ` + errors: [{ + messageId: "unexpected", + type: "FunctionDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: ` switch (a) { - case 1: var x; + case 1: + { {} + function f() {} + break; } } ` - ], - invalid: [ + } + ] + }] + }, { code: ` switch (a) { case 1: - {} - function f() {} - break; + case 2: + let x; } `, - errors: [{ messageId: "unexpected", type: "FunctionDeclaration" }] + languageOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unexpected", + type: "VariableDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: ` + switch (a) { + case 1: + case 2: + { let x; } + } + ` + } + ] + }] }, { code: ` switch (a) { case 1: + let x; + case 2: + let y; + } + `, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpected", + type: "VariableDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: ` + switch (a) { + case 1: + { let x; } case 2: + let y; + } + ` + } + ] + }, + { + messageId: "unexpected", + type: "VariableDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: ` + switch (a) { + case 1: let x; + case 2: + { let y; } + } + ` + } + ] + } + ] + }, + { + code: ` + switch (a) { + case 1: + let x; + default: + let y; } `, languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "unexpected", type: "VariableDeclaration" }] + errors: [ + { + messageId: "unexpected", + type: "VariableDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: ` + switch (a) { + case 1: + { let x; } + default: + let y; + } + ` + } + ] + }, + { + messageId: "unexpected", + type: "VariableDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: ` + switch (a) { + case 1: + let x; + default: + { let y; } + } + ` + } + ] + } + ] }, { code: "switch (a) { case 1: let x = 1; break; }", languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "unexpected", type: "VariableDeclaration" }] + errors: [{ + messageId: "unexpected", + type: "VariableDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: "switch (a) { case 1: { let x = 1; break; } }" + } + ] + }] }, { code: "switch (a) { default: let x = 2; break; }", languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "unexpected", type: "VariableDeclaration" }] + errors: [{ + messageId: "unexpected", + type: "VariableDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: "switch (a) { default: { let x = 2; break; } }" + } + ] + }] }, { code: "switch (a) { case 1: const x = 1; break; }", languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "unexpected", type: "VariableDeclaration" }] + errors: [{ + messageId: "unexpected", + type: "VariableDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: "switch (a) { case 1: { const x = 1; break; } }" + } + ] + }] }, { code: "switch (a) { default: const x = 2; break; }", languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "unexpected", type: "VariableDeclaration" }] + errors: [{ + messageId: "unexpected", + type: "VariableDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: "switch (a) { default: { const x = 2; break; } }" + } + ] + }] }, { code: "switch (a) { case 1: function f() {} break; }", languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "unexpected", type: "FunctionDeclaration" }] + errors: [{ + messageId: "unexpected", + type: "FunctionDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: "switch (a) { case 1: { function f() {} break; } }" + } + ] + }] }, { code: "switch (a) { default: function f() {} break; }", languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "unexpected", type: "FunctionDeclaration" }] + errors: [{ + messageId: "unexpected", + type: "FunctionDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: "switch (a) { default: { function f() {} break; } }" + } + ] + }] }, { code: "switch (a) { case 1: class C {} break; }", languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "unexpected", type: "ClassDeclaration" }] + errors: [{ + messageId: "unexpected", + type: "ClassDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: "switch (a) { case 1: { class C {} break; } }" + } + ] + }] }, { code: "switch (a) { default: class C {} break; }", languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "unexpected", type: "ClassDeclaration" }] + errors: [{ + messageId: "unexpected", + type: "ClassDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: "switch (a) { default: { class C {} break; } }" + } + ] + }] + }, + + // https://github.com/eslint/eslint/pull/18388#issuecomment-2075356456 + { + code: ` + switch ("foo") { + case "bar": + function baz() { } + break; + default: + baz(); + } + `, + languageOptions: { ecmaVersion: "latest" }, + errors: [{ + messageId: "unexpected", + type: "FunctionDeclaration", + suggestions: [ + { + messageId: "addBrackets", + output: ` + switch ("foo") { + case "bar": + { function baz() { } + break; } + default: + baz(); + } + ` + } + ] + }] } ] }); From 347d44f96b3d9d690e4f7380029e8a5a60b2fdc7 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sat, 27 Apr 2024 04:55:01 +0200 Subject: [PATCH 112/166] chore: remove eslintrc export from eslint-config-eslint (#18400) * chore: remove eslintrc export from eslint-config-eslint * updates to make the project lint-free when .eslintrc.js is used * try to fix knip error * ignore knip error * remove eslint plugins from knip ignore --- .eslintrc.js | 465 ++++++++++++++++++++- knip.jsonc | 11 +- package.json | 8 +- packages/eslint-config-eslint/eslintrc.js | 459 -------------------- packages/eslint-config-eslint/package.json | 3 +- 5 files changed, 468 insertions(+), 478 deletions(-) delete mode 100644 packages/eslint-config-eslint/eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js index 7757f21bd2b..c51fc72691a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -57,18 +57,461 @@ function createInternalFilesPatterns(pattern = null) { module.exports = { root: true, + reportUnusedDisableDirectives: true, plugins: [ "eslint-plugin", - "internal-rules" + "internal-rules", + "unicorn" ], extends: [ - "eslint/eslintrc" + "eslint:recommended", + "plugin:n/recommended", + "plugin:jsdoc/recommended", + "plugin:@eslint-community/eslint-comments/recommended" ], + settings: { + jsdoc: { + mode: "typescript", + tagNamePreference: { + file: "fileoverview", + augments: "extends", + class: "constructor" + }, + preferredTypes: { + "*": { + message: + "Use a more precise type or if necessary use `any` or `ArbitraryCallbackResult`", + replacement: "any" + }, + Any: { + message: + "Use a more precise type or if necessary use `any` or `ArbitraryCallbackResult`", + replacement: "any" + }, + function: { + message: + "Point to a `@callback` namepath or `Function` if truly arbitrary in form", + replacement: "Function" + }, + Promise: { + message: + "Specify the specific Promise type, including, if necessary, the type `any`" + }, + ".<>": { + message: "Prefer type form without dot", + replacement: "<>" + }, + object: { + message: + "Use the specific object type or `Object` if truly arbitrary", + replacement: "Object" + }, + array: "Array" + } + } + }, parserOptions: { - ecmaVersion: 2021 + ecmaVersion: "latest" }, rules: { - "internal-rules/multiline-comment-style": "error" + "internal-rules/multiline-comment-style": "error", + "array-bracket-spacing": "error", + "array-callback-return": "error", + "arrow-body-style": ["error", "as-needed"], + "arrow-parens": ["error", "as-needed"], + "arrow-spacing": "error", + indent: [ + "error", + 4, + { + SwitchCase: 1 + } + ], + "block-spacing": "error", + "brace-style": ["error", "1tbs"], + camelcase: "error", + "class-methods-use-this": "error", + "comma-dangle": "error", + "comma-spacing": "error", + "comma-style": ["error", "last"], + "computed-property-spacing": "error", + "consistent-return": "error", + curly: ["error", "all"], + "default-case": "error", + "default-case-last": "error", + "default-param-last": "error", + "dot-location": ["error", "property"], + "dot-notation": [ + "error", + { + allowKeywords: true + } + ], + "eol-last": "error", + eqeqeq: "error", + "@eslint-community/eslint-comments/disable-enable-pair": ["error"], + "@eslint-community/eslint-comments/no-unused-disable": "error", + "@eslint-community/eslint-comments/require-description": "error", + "func-call-spacing": "error", + "func-style": ["error", "declaration"], + "function-call-argument-newline": ["error", "consistent"], + "function-paren-newline": ["error", "consistent"], + "generator-star-spacing": "error", + "grouped-accessor-pairs": "error", + "guard-for-in": "error", + "jsdoc/check-syntax": "error", + "jsdoc/check-values": [ + "error", + { + allowedLicenses: true + } + ], + "jsdoc/no-bad-blocks": "error", + "jsdoc/no-defaults": "off", + "jsdoc/require-asterisk-prefix": "error", + "jsdoc/require-description": [ + "error", + { + checkConstructors: false + } + ], + "jsdoc/require-hyphen-before-param-description": ["error", "never"], + "jsdoc/require-returns": [ + "error", + { + forceRequireReturn: true, + forceReturnsWithAsync: true + } + ], + "jsdoc/require-throws": "error", + "jsdoc/tag-lines": [ + "error", + "never", + { + tags: { + example: { + lines: "always" + }, + fileoverview: { + lines: "any" + } + }, + startLines: 0 + } + + ], + "jsdoc/no-undefined-types": "off", + "jsdoc/require-yields": "off", + "jsdoc/check-access": "error", + "jsdoc/check-alignment": "error", + "jsdoc/check-param-names": "error", + "jsdoc/check-property-names": "error", + "jsdoc/check-tag-names": "error", + "jsdoc/check-types": "error", + "jsdoc/empty-tags": "error", + "jsdoc/implements-on-classes": "error", + "jsdoc/multiline-blocks": "error", + "jsdoc/no-multi-asterisks": ["error", { allowWhitespace: true }], + "jsdoc/require-jsdoc": [ + "error", + { + require: { + ClassDeclaration: true + } + } + ], + "jsdoc/require-param": "error", + "jsdoc/require-param-description": "error", + "jsdoc/require-param-name": "error", + "jsdoc/require-param-type": "error", + "jsdoc/require-property": "error", + "jsdoc/require-property-description": "error", + "jsdoc/require-property-name": "error", + "jsdoc/require-property-type": "error", + "jsdoc/require-returns-check": "error", + "jsdoc/require-returns-description": "error", + "jsdoc/require-returns-type": "error", + "jsdoc/require-yields-check": "error", + "jsdoc/valid-types": "error", + "key-spacing": [ + "error", + { + beforeColon: false, + afterColon: true + } + ], + "keyword-spacing": "error", + "lines-around-comment": [ + "error", + { + beforeBlockComment: true, + afterBlockComment: false, + beforeLineComment: true, + afterLineComment: false + } + ], + "max-len": [ + "error", + 160, + { + ignoreComments: true, + ignoreUrls: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreRegExpLiterals: true + } + ], + "max-statements-per-line": "error", + "new-cap": "error", + "new-parens": "error", + "no-alert": "error", + "no-array-constructor": "error", + "no-caller": "error", + "no-confusing-arrow": "error", + "no-console": "error", + "no-constant-binary-expression": "error", + "no-constructor-return": "error", + "no-else-return": [ + "error", + { + allowElseIf: false + } + ], + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-semi": "error", + "no-floating-decimal": "error", + "no-implied-eval": "error", + "no-inner-declarations": "error", + "no-invalid-this": "error", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-loop-func": "error", + "no-mixed-spaces-and-tabs": ["error", false], + "no-multi-spaces": "error", + "no-multi-str": "error", + "no-multiple-empty-lines": [ + "error", + { + max: 2, + maxBOF: 0, + maxEOF: 0 + } + ], + "no-nested-ternary": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-object": "error", + "no-new-wrappers": "error", + "no-octal-escape": "error", + "no-param-reassign": "error", + "no-proto": "error", + "no-process-exit": "off", + "no-restricted-properties": [ + "error", + { + property: "substring", + message: "Use .slice instead of .substring." + }, + { + property: "substr", + message: "Use .slice instead of .substr." + }, + { + object: "assert", + property: "equal", + message: "Use assert.strictEqual instead of assert.equal." + }, + { + object: "assert", + property: "notEqual", + message: + "Use assert.notStrictEqual instead of assert.notEqual." + }, + { + object: "assert", + property: "deepEqual", + message: + "Use assert.deepStrictEqual instead of assert.deepEqual." + }, + { + object: "assert", + property: "notDeepEqual", + message: + "Use assert.notDeepStrictEqual instead of assert.notDeepEqual." + } + ], + "no-return-assign": "error", + "no-script-url": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow": "error", + "no-tabs": "error", + "no-throw-literal": "error", + "no-trailing-spaces": "error", + "no-undef": [ + "error", + { + typeof: true + } + ], + "no-undef-init": "error", + "no-undefined": "error", + "no-underscore-dangle": [ + "error", + { + allowAfterThis: true + } + ], + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": "error", + "no-unreachable-loop": "error", + "no-unused-expressions": "error", + "no-unused-vars": [ + "error", + { + vars: "all", + args: "after-used", + caughtErrors: "all" + } + ], + "no-use-before-define": "error", + "no-useless-assignment": "error", + "no-useless-call": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-constructor": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-whitespace-before-property": "error", + "no-var": "error", + "n/callback-return": ["error", ["cb", "callback", "next"]], + "n/handle-callback-err": ["error", "err"], + "n/no-deprecated-api": "error", + "n/no-mixed-requires": "error", + "n/no-new-require": "error", + "n/no-path-concat": "error", + "object-curly-newline": [ + "error", + { + consistent: true, + multiline: true + } + ], + "object-curly-spacing": ["error", "always"], + "object-property-newline": [ + "error", + { + allowAllPropertiesOnSameLine: true + } + ], + "object-shorthand": [ + "error", + "always", + { + avoidExplicitReturnArrows: true + } + ], + "one-var-declaration-per-line": "error", + "operator-assignment": "error", + "operator-linebreak": "error", + "padding-line-between-statements": [ + "error", + { + blankLine: "always", + prev: ["const", "let", "var"], + next: "*" + }, + { + blankLine: "any", + prev: ["const", "let", "var"], + next: ["const", "let", "var"] + } + ], + "prefer-arrow-callback": "error", + "prefer-const": "error", + "prefer-exponentiation-operator": "error", + "prefer-numeric-literals": "error", + "prefer-object-has-own": "error", + "prefer-promise-reject-errors": "error", + "prefer-regex-literals": "error", + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "error", + quotes: [ + "error", + "double", + { + avoidEscape: true + } + ], + "quote-props": ["error", "as-needed"], + radix: "error", + "require-unicode-regexp": "error", + "rest-spread-spacing": "error", + semi: "error", + "semi-spacing": [ + "error", + { + before: false, + after: true + } + ], + "semi-style": "error", + "space-before-blocks": "error", + "space-before-function-paren": [ + "error", + { + anonymous: "never", + named: "never", + asyncArrow: "always" + } + ], + "space-in-parens": "error", + "space-infix-ops": "error", + "space-unary-ops": [ + "error", + { + words: true, + nonwords: false + } + ], + "spaced-comment": [ + "error", + "always", + { + exceptions: ["-"] + } + ], + strict: ["error", "global"], + "switch-colon-spacing": "error", + "symbol-description": "error", + "template-curly-spacing": ["error", "never"], + "template-tag-spacing": "error", + "unicode-bom": "error", + "unicorn/prefer-array-find": "error", + "unicorn/prefer-array-flat-map": "error", + "unicorn/prefer-array-flat": "error", + "unicorn/prefer-array-index-of": "error", + "unicorn/prefer-array-some": "error", + "unicorn/prefer-at": "error", + "unicorn/prefer-includes": "error", + "unicorn/prefer-set-has": "error", + "unicorn/prefer-string-slice": "error", + "unicorn/prefer-string-starts-ends-with": "error", + "unicorn/prefer-string-trim-start-end": "error", + "wrap-iife": "error", + "yield-star-spacing": "error", + yoda: [ + "error", + "never", + { + exceptRange: true + } + ] }, overrides: [ { @@ -105,7 +548,18 @@ module.exports = { "plugin:eslint-plugin/tests-recommended" ], rules: { - "eslint-plugin/test-case-property-ordering": "error", + "eslint-plugin/test-case-property-ordering": [ + "error", + [ + "name", + "filename", + "code", + "output", + "options", + "languageOptions", + "errors" + ] + ], "eslint-plugin/test-case-shorthand-strings": "error" } }, @@ -191,7 +645,6 @@ module.exports = { files: [INTERNAL_FILES.RULE_TESTER_PATTERN], rules: { "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns(INTERNAL_FILES.RULE_TESTER_PATTERN), resolveAbsolutePath("lib/cli-engine/index.js") ]] } diff --git a/knip.jsonc b/knip.jsonc index 06a7c3944eb..577086ba3cc 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -31,15 +31,12 @@ "ignoreDependencies": [ "c8", - // These will be removed in https://github.com/eslint/eslint/pull/18011 - "eslint-plugin-eslint-comments", - "eslint-plugin-jsdoc", - "eslint-plugin-n", - "eslint-plugin-unicorn", - // Ignore until Knip has a wdio plugin: "@wdio/*", - "rollup-plugin-node-polyfills" + "rollup-plugin-node-polyfills", + + // FIXME: not sure why is eslint-config-eslint reported as unused + "eslint-config-eslint" ] }, "docs": { diff --git a/package.json b/package.json index 19ec7b9c0ae..7e70b4e1386 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "devDependencies": { "@babel/core": "^7.4.3", "@babel/preset-env": "^7.4.3", + "@eslint-community/eslint-plugin-eslint-comments": "^4.3.0", "@types/estree": "^1.0.5", "@types/node": "^20.11.5", "@wdio/browser-runner": "^8.14.6", @@ -120,12 +121,11 @@ "ejs": "^3.0.2", "eslint": "file:.", "eslint-config-eslint": "file:packages/eslint-config-eslint", - "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-eslint-plugin": "^6.0.0", "eslint-plugin-internal-rules": "file:tools/internal-rules", - "eslint-plugin-jsdoc": "^46.9.0", - "eslint-plugin-n": "^16.6.0", - "eslint-plugin-unicorn": "^49.0.0", + "eslint-plugin-jsdoc": "^48.2.3", + "eslint-plugin-n": "^17.2.0", + "eslint-plugin-unicorn": "^52.0.0", "eslint-release": "^3.2.2", "eslump": "^3.0.0", "esprima": "^4.0.1", diff --git a/packages/eslint-config-eslint/eslintrc.js b/packages/eslint-config-eslint/eslintrc.js deleted file mode 100644 index 283e83fc1c1..00000000000 --- a/packages/eslint-config-eslint/eslintrc.js +++ /dev/null @@ -1,459 +0,0 @@ -/** - * TODO: the config will be removed in the future, please use the index config. - * @deprecated - * @fileoverview the eslintrc config - it's exported as ESLint VS Code extension - * expects eslintrc config files to be present to work correctly.. - * @author 唯然 - */ -"use strict"; - -module.exports = { - reportUnusedDisableDirectives: true, - extends: [ - "eslint:recommended", - "plugin:n/recommended", - "plugin:jsdoc/recommended", - "plugin:eslint-comments/recommended" - ], - plugins: ["unicorn"], - settings: { - jsdoc: { - mode: "typescript", - tagNamePreference: { - file: "fileoverview", - augments: "extends", - class: "constructor" - }, - preferredTypes: { - "*": { - message: - "Use a more precise type or if necessary use `any` or `ArbitraryCallbackResult`", - replacement: "any" - }, - Any: { - message: - "Use a more precise type or if necessary use `any` or `ArbitraryCallbackResult`", - replacement: "any" - }, - function: { - message: - "Point to a `@callback` namepath or `Function` if truly arbitrary in form", - replacement: "Function" - }, - Promise: { - message: - "Specify the specific Promise type, including, if necessary, the type `any`" - }, - ".<>": { - message: "Prefer type form without dot", - replacement: "<>" - }, - object: { - message: - "Use the specific object type or `Object` if truly arbitrary", - replacement: "Object" - }, - array: "Array" - } - } - }, - rules: { - "array-bracket-spacing": "error", - "array-callback-return": "error", - "arrow-body-style": ["error", "as-needed"], - "arrow-parens": ["error", "as-needed"], - "arrow-spacing": "error", - indent: [ - "error", - 4, - { - SwitchCase: 1 - } - ], - "block-spacing": "error", - "brace-style": ["error", "1tbs"], - camelcase: "error", - "class-methods-use-this": "error", - "comma-dangle": "error", - "comma-spacing": "error", - "comma-style": ["error", "last"], - "computed-property-spacing": "error", - "consistent-return": "error", - curly: ["error", "all"], - "default-case": "error", - "default-case-last": "error", - "default-param-last": "error", - "dot-location": ["error", "property"], - "dot-notation": [ - "error", - { - allowKeywords: true - } - ], - "eol-last": "error", - eqeqeq: "error", - "eslint-comments/disable-enable-pair": ["error"], - "eslint-comments/no-unused-disable": "error", - "eslint-comments/require-description": "error", - "func-call-spacing": "error", - "func-style": ["error", "declaration"], - "function-call-argument-newline": ["error", "consistent"], - "function-paren-newline": ["error", "consistent"], - "generator-star-spacing": "error", - "grouped-accessor-pairs": "error", - "guard-for-in": "error", - "jsdoc/check-syntax": "error", - "jsdoc/check-values": [ - "error", - { - allowedLicenses: true - } - ], - "jsdoc/no-bad-blocks": "error", - "jsdoc/no-defaults": "off", - "jsdoc/require-asterisk-prefix": "error", - "jsdoc/require-description": [ - "error", - { - checkConstructors: false - } - ], - "jsdoc/require-hyphen-before-param-description": ["error", "never"], - "jsdoc/require-returns": [ - "error", - { - forceRequireReturn: true, - forceReturnsWithAsync: true - } - ], - "jsdoc/require-throws": "error", - "jsdoc/tag-lines": [ - "error", - "never", - { - tags: { - example: { - lines: "always" - }, - fileoverview: { - lines: "any" - } - }, - startLines: 0 - } - - ], - "jsdoc/no-undefined-types": "off", - "jsdoc/require-yields": "off", - "jsdoc/check-access": "error", - "jsdoc/check-alignment": "error", - "jsdoc/check-param-names": "error", - "jsdoc/check-property-names": "error", - "jsdoc/check-tag-names": "error", - "jsdoc/check-types": "error", - "jsdoc/empty-tags": "error", - "jsdoc/implements-on-classes": "error", - "jsdoc/multiline-blocks": "error", - "jsdoc/no-multi-asterisks": ["error", { allowWhitespace: true }], - "jsdoc/require-jsdoc": [ - "error", - { - require: { - ClassDeclaration: true - } - } - ], - "jsdoc/require-param": "error", - "jsdoc/require-param-description": "error", - "jsdoc/require-param-name": "error", - "jsdoc/require-param-type": "error", - "jsdoc/require-property": "error", - "jsdoc/require-property-description": "error", - "jsdoc/require-property-name": "error", - "jsdoc/require-property-type": "error", - "jsdoc/require-returns-check": "error", - "jsdoc/require-returns-description": "error", - "jsdoc/require-returns-type": "error", - "jsdoc/require-yields-check": "error", - "jsdoc/valid-types": "error", - "key-spacing": [ - "error", - { - beforeColon: false, - afterColon: true - } - ], - "keyword-spacing": "error", - "lines-around-comment": [ - "error", - { - beforeBlockComment: true, - afterBlockComment: false, - beforeLineComment: true, - afterLineComment: false - } - ], - "max-len": [ - "error", - 160, - { - ignoreComments: true, - ignoreUrls: true, - ignoreStrings: true, - ignoreTemplateLiterals: true, - ignoreRegExpLiterals: true - } - ], - "max-statements-per-line": "error", - "new-cap": "error", - "new-parens": "error", - "no-alert": "error", - "no-array-constructor": "error", - "no-caller": "error", - "no-confusing-arrow": "error", - "no-console": "error", - "no-constant-binary-expression": "error", - "no-constructor-return": "error", - "no-else-return": [ - "error", - { - allowElseIf: false - } - ], - "no-eval": "error", - "no-extend-native": "error", - "no-extra-bind": "error", - "no-extra-semi": "error", - "no-floating-decimal": "error", - "no-implied-eval": "error", - "no-inner-declarations": "error", - "no-invalid-this": "error", - "no-iterator": "error", - "no-label-var": "error", - "no-labels": "error", - "no-lone-blocks": "error", - "no-loop-func": "error", - "no-mixed-spaces-and-tabs": ["error", false], - "no-multi-spaces": "error", - "no-multi-str": "error", - "no-multiple-empty-lines": [ - "error", - { - max: 2, - maxBOF: 0, - maxEOF: 0 - } - ], - "no-nested-ternary": "error", - "no-new": "error", - "no-new-func": "error", - "no-new-object": "error", - "no-new-wrappers": "error", - "no-octal-escape": "error", - "no-param-reassign": "error", - "no-proto": "error", - "no-process-exit": "off", - "no-restricted-properties": [ - "error", - { - property: "substring", - message: "Use .slice instead of .substring." - }, - { - property: "substr", - message: "Use .slice instead of .substr." - }, - { - object: "assert", - property: "equal", - message: "Use assert.strictEqual instead of assert.equal." - }, - { - object: "assert", - property: "notEqual", - message: - "Use assert.notStrictEqual instead of assert.notEqual." - }, - { - object: "assert", - property: "deepEqual", - message: - "Use assert.deepStrictEqual instead of assert.deepEqual." - }, - { - object: "assert", - property: "notDeepEqual", - message: - "Use assert.notDeepStrictEqual instead of assert.notDeepEqual." - } - ], - "no-return-assign": "error", - "no-script-url": "error", - "no-self-compare": "error", - "no-sequences": "error", - "no-shadow": "error", - "no-tabs": "error", - "no-throw-literal": "error", - "no-trailing-spaces": "error", - "no-undef": [ - "error", - { - typeof: true - } - ], - "no-undef-init": "error", - "no-undefined": "error", - "no-underscore-dangle": [ - "error", - { - allowAfterThis: true - } - ], - "no-unmodified-loop-condition": "error", - "no-unneeded-ternary": "error", - "no-unreachable-loop": "error", - "no-unused-expressions": "error", - "no-unused-vars": [ - "error", - { - vars: "all", - args: "after-used", - caughtErrors: "all" - } - ], - "no-use-before-define": "error", - "no-useless-assignment": "error", - "no-useless-call": "error", - "no-useless-computed-key": "error", - "no-useless-concat": "error", - "no-useless-constructor": "error", - "no-useless-rename": "error", - "no-useless-return": "error", - "no-whitespace-before-property": "error", - "no-var": "error", - "n/callback-return": ["error", ["cb", "callback", "next"]], - "n/handle-callback-err": ["error", "err"], - "n/no-deprecated-api": "error", - "n/no-mixed-requires": "error", - "n/no-new-require": "error", - "n/no-path-concat": "error", - "object-curly-newline": [ - "error", - { - consistent: true, - multiline: true - } - ], - "object-curly-spacing": ["error", "always"], - "object-property-newline": [ - "error", - { - allowAllPropertiesOnSameLine: true - } - ], - "object-shorthand": [ - "error", - "always", - { - avoidExplicitReturnArrows: true - } - ], - "one-var-declaration-per-line": "error", - "operator-assignment": "error", - "operator-linebreak": "error", - "padding-line-between-statements": [ - "error", - { - blankLine: "always", - prev: ["const", "let", "var"], - next: "*" - }, - { - blankLine: "any", - prev: ["const", "let", "var"], - next: ["const", "let", "var"] - } - ], - "prefer-arrow-callback": "error", - "prefer-const": "error", - "prefer-exponentiation-operator": "error", - "prefer-numeric-literals": "error", - "prefer-object-has-own": "error", - "prefer-promise-reject-errors": "error", - "prefer-regex-literals": "error", - "prefer-rest-params": "error", - "prefer-spread": "error", - "prefer-template": "error", - quotes: [ - "error", - "double", - { - avoidEscape: true - } - ], - "quote-props": ["error", "as-needed"], - radix: "error", - "require-unicode-regexp": "error", - "rest-spread-spacing": "error", - semi: "error", - "semi-spacing": [ - "error", - { - before: false, - after: true - } - ], - "semi-style": "error", - "space-before-blocks": "error", - "space-before-function-paren": [ - "error", - { - anonymous: "never", - named: "never", - asyncArrow: "always" - } - ], - "space-in-parens": "error", - "space-infix-ops": "error", - "space-unary-ops": [ - "error", - { - words: true, - nonwords: false - } - ], - "spaced-comment": [ - "error", - "always", - { - exceptions: ["-"] - } - ], - strict: ["error", "global"], - "switch-colon-spacing": "error", - "symbol-description": "error", - "template-curly-spacing": ["error", "never"], - "template-tag-spacing": "error", - "unicode-bom": "error", - "unicorn/prefer-array-find": "error", - "unicorn/prefer-array-flat-map": "error", - "unicorn/prefer-array-flat": "error", - "unicorn/prefer-array-index-of": "error", - "unicorn/prefer-array-some": "error", - "unicorn/prefer-at": "error", - "unicorn/prefer-includes": "error", - "unicorn/prefer-set-has": "error", - "unicorn/prefer-string-slice": "error", - "unicorn/prefer-string-starts-ends-with": "error", - "unicorn/prefer-string-trim-start-end": "error", - "wrap-iife": "error", - "yield-star-spacing": "error", - yoda: [ - "error", - "never", - { - exceptRange: true - } - ] - } -}; diff --git a/packages/eslint-config-eslint/package.json b/packages/eslint-config-eslint/package.json index d5c7d970879..7373e874d5b 100644 --- a/packages/eslint-config-eslint/package.json +++ b/packages/eslint-config-eslint/package.json @@ -7,8 +7,7 @@ "./package.json": "./package.json", ".": "./index.js", "./base": "./base.js", - "./cjs": "./cjs.js", - "./eslintrc": "./eslintrc.js" + "./cjs": "./cjs.js" }, "scripts": { "test": "node ./index.js && node ./cjs.js", From 0f5df509a4bc00cff2c62b90fab184bdf0231322 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Sat, 27 Apr 2024 08:07:12 +0000 Subject: [PATCH 113/166] docs: Update README --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 28af2b81000..5e5a0806f1f 100644 --- a/README.md +++ b/README.md @@ -253,11 +253,6 @@ Nitin Kumar The people who review and fix bugs and help triage issues.
- -Bryan Mishkin's Avatar
-Bryan Mishkin -
-
Josh Goldberg ✨'s Avatar
Josh Goldberg ✨ From 284722ca8375c9a9e4f741bfdd78e765542da61f Mon Sep 17 00:00:00 2001 From: Jenkins Date: Mon, 29 Apr 2024 20:30:28 +0000 Subject: [PATCH 114/166] chore: package.json update for eslint-config-eslint release --- packages/eslint-config-eslint/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-config-eslint/package.json b/packages/eslint-config-eslint/package.json index 7373e874d5b..3ce048d19a0 100644 --- a/packages/eslint-config-eslint/package.json +++ b/packages/eslint-config-eslint/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-eslint", - "version": "9.0.0", + "version": "10.0.0", "author": "Nicholas C. Zakas ", "description": "Default ESLint configuration for ESLint projects.", "exports": { From c4c18e05fc866b73218dbe58b760546f39a2a620 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 3 May 2024 19:24:37 +0000 Subject: [PATCH 115/166] chore: package.json update for @eslint/js release --- packages/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/package.json b/packages/js/package.json index 2c9dbf1eec4..7c0aaf1ae8a 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "9.1.1", + "version": "9.2.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From b3466052802a1586560ad56a8128d603284d58c2 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 3 May 2024 21:34:36 +0200 Subject: [PATCH 116/166] chore: upgrade @eslint/js@9.2.0 (#18413) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7e70b4e1386..98df2240341 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^3.0.2", - "@eslint/js": "9.1.1", + "@eslint/js": "9.2.0", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.2.3", From 989ac9d1ba34cf8c11ccf8d675f13c3213de4c2e Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 3 May 2024 19:45:41 +0000 Subject: [PATCH 117/166] Build: changelog update for 9.2.0 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e815421a60..d06bf41ec74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +v9.2.0 - May 3, 2024 + +* [`b346605`](https://github.com/eslint/eslint/commit/b3466052802a1586560ad56a8128d603284d58c2) chore: upgrade @eslint/js@9.2.0 (#18413) (Milos Djermanovic) +* [`c4c18e0`](https://github.com/eslint/eslint/commit/c4c18e05fc866b73218dbe58b760546f39a2a620) chore: package.json update for @eslint/js release (Jenkins) +* [`284722c`](https://github.com/eslint/eslint/commit/284722ca8375c9a9e4f741bfdd78e765542da61f) chore: package.json update for eslint-config-eslint release (Jenkins) +* [`0f5df50`](https://github.com/eslint/eslint/commit/0f5df509a4bc00cff2c62b90fab184bdf0231322) docs: Update README (GitHub Actions Bot) +* [`347d44f`](https://github.com/eslint/eslint/commit/347d44f96b3d9d690e4f7380029e8a5a60b2fdc7) chore: remove eslintrc export from eslint-config-eslint (#18400) (Milos Djermanovic) +* [`8485d76`](https://github.com/eslint/eslint/commit/8485d76134bdbd29230780fadc284c482cd1d963) feat: `no-case-declarations` add suggestions (#18388) (Josh Goldberg ✨) +* [`a498f35`](https://github.com/eslint/eslint/commit/a498f35cef4df9c9f5387fafafaf482d913d5765) feat: update Unicode letter detection in capitalized-comments rule (#18375) (Francesco Trotta) +* [`1579ce0`](https://github.com/eslint/eslint/commit/1579ce05cbb523cb5b04ff77fab06ba1ecd18dce) docs: update wording regarding indirect eval (#18394) (Kirk Waiblinger) +* [`f316e20`](https://github.com/eslint/eslint/commit/f316e2009a8aa902fa447a49b6b5e560848f0711) ci: run tests in Node.js 22 (#18393) (Francesco Trotta) +* [`eeec413`](https://github.com/eslint/eslint/commit/eeec41346738afb491958fdbf0bcf45a302ca1b7) fix: do not throw when defining a global named __defineSetter__ (#18364) (唯然) +* [`f12a02c`](https://github.com/eslint/eslint/commit/f12a02c5749d31beefe46d2753a0d68b56f2281d) docs: update to eslint v9 in custom-rule-tutorial (#18383) (唯然) + v9.1.1 - April 22, 2024 * [`a26b402`](https://github.com/eslint/eslint/commit/a26b40279f283853717236b44602b27b57f0b627) fix: use @eslint/create-config latest (#18373) (唯然) From 271e7ab1adc45a7b2f66cfea55a54e6048d9749a Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 3 May 2024 19:45:42 +0000 Subject: [PATCH 118/166] 9.2.0 --- docs/package.json | 2 +- docs/src/_data/rules.json | 2 +- docs/src/_data/rules_meta.json | 3 ++- docs/src/_data/versions.json | 2 +- docs/src/use/formatters/html-formatter-example.html | 2 +- package.json | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/package.json b/docs/package.json index 4c5bfbe44a4..5316fecd52f 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "9.1.1", + "version": "9.2.0", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 79374b96435..0be8301486e 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -680,7 +680,7 @@ "description": "Disallow lexical declarations in case clauses", "recommended": true, "fixable": false, - "hasSuggestions": false + "hasSuggestions": true }, { "name": "no-console", diff --git a/docs/src/_data/rules_meta.json b/docs/src/_data/rules_meta.json index 647481b3fe5..67210b73fbb 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -810,7 +810,8 @@ "description": "Disallow lexical declarations in case clauses", "recommended": true, "url": "https://eslint.org/docs/latest/rules/no-case-declarations" - } + }, + "hasSuggestions": true }, "no-catch-shadow": { "type": "suggestion", diff --git a/docs/src/_data/versions.json b/docs/src/_data/versions.json index 02b8561e4cb..fe0175c97bc 100644 --- a/docs/src/_data/versions.json +++ b/docs/src/_data/versions.json @@ -6,7 +6,7 @@ "path": "/docs/head/" }, { - "version": "9.1.1", + "version": "9.2.0", "branch": "latest", "path": "/docs/latest/" }, diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index a2b2ac85b5f..9b3da63f5a2 100644 --- a/docs/src/use/formatters/html-formatter-example.html +++ b/docs/src/use/formatters/html-formatter-example.html @@ -118,7 +118,7 @@

ESLint Report

- 8 problems (4 errors, 4 warnings) - Generated on Mon Apr 22 2024 19:21:47 GMT+0000 (Coordinated Universal Time) + 8 problems (4 errors, 4 warnings) - Generated on Fri May 03 2024 19:45:43 GMT+0000 (Coordinated Universal Time)
diff --git a/package.json b/package.json index 98df2240341..0c56381651a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "9.1.1", + "version": "9.2.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 0de0909e001191a3464077d37e8c0b3f67e9a1cb Mon Sep 17 00:00:00 2001 From: Mike McCready <66998419+MikeMcC399@users.noreply.github.com> Date: Sun, 5 May 2024 21:57:14 +0200 Subject: [PATCH 119/166] docs: fix grammar in configuration file resolution (#18419) --- docs/src/use/configure/configuration-files.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/use/configure/configuration-files.md b/docs/src/use/configure/configuration-files.md index 1710937a3cd..b7bfac0e977 100644 --- a/docs/src/use/configure/configuration-files.md +++ b/docs/src/use/configure/configuration-files.md @@ -444,7 +444,7 @@ For more information on how to combine shareable configs with your preferences, ## Configuration File Resolution -When ESLint is run on the command line, it first checks the current working directory for `eslint.config.js`. If that file is found, then the search stops, otherwise it checks for `eslint.config.mjs`. If that file is found, then the search stops, otherwise it checks for `eslint.config.cjs`. If none of the files are not found, it checks the parent directory for each file. This search continues until either a config file is found or the root directory is reached. +When ESLint is run on the command line, it first checks the current working directory for `eslint.config.js`. If that file is found, then the search stops, otherwise it checks for `eslint.config.mjs`. If that file is found, then the search stops, otherwise it checks for `eslint.config.cjs`. If none of the files are found, it checks the parent directory for each file. This search continues until either a config file is found or the root directory is reached. You can prevent this search for `eslint.config.js` by using the `-c` or `--config` option on the command line to specify an alternate configuration file, such as: From db0b174c3ace60e29585bfc3520727c44cefcfc5 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Mon, 6 May 2024 13:10:31 -0600 Subject: [PATCH 120/166] feat: add `enforceForInnerExpressions` option to `no-extra-boolean-cast` (#18222) * fix: [no-extra-boolean-cast] inspect comma expressions and ?? expressions. Fixes #18186 * check ternaries recursively too * pr feedback * change docs * add option for recursive expression checks * better wording * format * enforceForInnerExpressions * feedback * nice, anyOf is clever about additionalProperties * test cov * test coverage * docs --- docs/src/rules/no-extra-boolean-cast.md | 57 +- lib/rules/no-extra-boolean-cast.js | 106 +- tests/lib/rules/no-extra-boolean-cast.js | 1510 +++++++++++++++++++--- 3 files changed, 1457 insertions(+), 216 deletions(-) diff --git a/docs/src/rules/no-extra-boolean-cast.md b/docs/src/rules/no-extra-boolean-cast.md index 2408d617174..e11394b2085 100644 --- a/docs/src/rules/no-extra-boolean-cast.md +++ b/docs/src/rules/no-extra-boolean-cast.md @@ -3,10 +3,6 @@ title: no-extra-boolean-cast rule_type: suggestion --- - - - - In contexts such as an `if` statement's test where the result of the expression will already be coerced to a Boolean, casting to a Boolean via double negation (`!!`) or a `Boolean` call is unnecessary. For example, these `if` statements are equivalent: ```js @@ -88,16 +84,18 @@ var foo = bar ? !!baz : !!bat; This rule has an object option: -* `"enforceForLogicalOperands"` when set to `true`, in addition to checking default contexts, checks whether the extra boolean cast is contained within a logical expression. Default is `false`, meaning that this rule by default does not warn about extra booleans cast inside logical expression. +* `"enforceForInnerExpressions"` when set to `true`, in addition to checking default contexts, checks whether extra boolean casts are present in expressions whose result is used in a boolean context. See examples below. Default is `false`, meaning that this rule by default does not warn about extra booleans cast inside inner expressions. + +**Deprecated:** The object property `enforceForLogicalOperands` is deprecated ([eslint#18222](https://github.com/eslint/eslint/pull/18222)). Please use `enforceForInnerExpressions` instead. -### enforceForLogicalOperands +### enforceForInnerExpressions -Examples of **incorrect** code for this rule with `"enforceForLogicalOperands"` option set to `true`: +Examples of **incorrect** code for this rule with `"enforceForInnerExpressions"` option set to `true`: ::: incorrect ```js -/*eslint no-extra-boolean-cast: ["error", {"enforceForLogicalOperands": true}]*/ +/*eslint no-extra-boolean-cast: ["error", {"enforceForInnerExpressions": true}]*/ if (!!foo || bar) { //... @@ -107,23 +105,38 @@ while (!!foo && bar) { //... } -if ((!!foo || bar) && baz) { +if ((!!foo || bar) && !!baz) { //... } -foo && Boolean(bar) ? baz : bat +var foo = new Boolean(!!bar || baz); -var foo = new Boolean(!!bar || baz) +foo && Boolean(bar) ? baz : bat; + +const ternaryBranches = Boolean(bar ? !!baz : bat); + +const nullishCoalescingOperator = Boolean(bar ?? Boolean(baz)); + +const commaOperator = Boolean((bar, baz, !!bat)); + +// another comma operator example +for (let i = 0; console.log(i), Boolean(i < 10); i++) { + // ... +} ``` ::: -Examples of **correct** code for this rule with `"enforceForLogicalOperands"` option set to `true`: +Examples of **correct** code for this rule with `"enforceForInnerExpressions"` option set to `true`: ::: correct ```js -/*eslint no-extra-boolean-cast: ["error", {"enforceForLogicalOperands": true}]*/ +/*eslint no-extra-boolean-cast: ["error", {"enforceForInnerExpressions": true}]*/ + +// Note that `||` and `&&` alone aren't a boolean context for either operand +// since the resultant value need not be a boolean without casting. +var foo = !!bar || baz; if (foo || bar) { //... @@ -137,11 +150,23 @@ if ((foo || bar) && baz) { //... } -foo && bar ? baz : bat +var foo = new Boolean(bar || baz); -var foo = new Boolean(bar || baz) +foo && bar ? baz : bat; -var foo = !!bar || baz; +const ternaryBranches = Boolean(bar ? baz : bat); + +const nullishCoalescingOperator = Boolean(bar ?? baz); + +const commaOperator = Boolean((bar, baz, bat)); + +// another comma operator example +for (let i = 0; console.log(i), i < 10; i++) { + // ... +} + +// comma operator in non-final position +Boolean((Boolean(bar), baz, bat)); ``` ::: diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js index f342533bfc9..012657b7ec8 100644 --- a/lib/rules/no-extra-boolean-cast.js +++ b/lib/rules/no-extra-boolean-cast.js @@ -30,14 +30,28 @@ module.exports = { }, schema: [{ - type: "object", - properties: { - enforceForLogicalOperands: { - type: "boolean", - default: false + anyOf: [ + { + type: "object", + properties: { + enforceForInnerExpressions: { + type: "boolean" + } + }, + additionalProperties: false + }, + + // deprecated + { + type: "object", + properties: { + enforceForLogicalOperands: { + type: "boolean" + } + }, + additionalProperties: false } - }, - additionalProperties: false + ] }], fixable: "code", @@ -49,6 +63,9 @@ module.exports = { create(context) { const sourceCode = context.sourceCode; + const enforceForLogicalOperands = context.options[0]?.enforceForLogicalOperands === true; + const enforceForInnerExpressions = context.options[0]?.enforceForInnerExpressions === true; + // Node types which have a test which will coerce values to booleans. const BOOLEAN_NODE_TYPES = new Set([ @@ -72,19 +89,6 @@ module.exports = { node.callee.name === "Boolean"; } - /** - * Checks whether the node is a logical expression and that the option is enabled - * @param {ASTNode} node the node - * @returns {boolean} if the node is a logical expression and option is enabled - */ - function isLogicalContext(node) { - return node.type === "LogicalExpression" && - (node.operator === "||" || node.operator === "&&") && - (context.options.length && context.options[0].enforceForLogicalOperands === true); - - } - - /** * Check if a node is in a context where its value would be coerced to a boolean at runtime. * @param {ASTNode} node The node @@ -115,12 +119,51 @@ module.exports = { return isInFlaggedContext(node.parent); } - return isInBooleanContext(node) || - (isLogicalContext(node.parent) && + /* + * legacy behavior - enforceForLogicalOperands will only recurse on + * logical expressions, not on other contexts. + * enforceForInnerExpressions will recurse on logical expressions + * as well as the other recursive syntaxes. + */ + + if (enforceForLogicalOperands || enforceForInnerExpressions) { + if (node.parent.type === "LogicalExpression") { + if (node.parent.operator === "||" || node.parent.operator === "&&") { + return isInFlaggedContext(node.parent); + } - // For nested logical statements - isInFlaggedContext(node.parent) - ); + // Check the right hand side of a `??` operator. + if (enforceForInnerExpressions && + node.parent.operator === "??" && + node.parent.right === node + ) { + return isInFlaggedContext(node.parent); + } + } + } + + if (enforceForInnerExpressions) { + if ( + node.parent.type === "ConditionalExpression" && + (node.parent.consequent === node || node.parent.alternate === node) + ) { + return isInFlaggedContext(node.parent); + } + + /* + * Check last expression only in a sequence, i.e. if ((1, 2, Boolean(3))) {}, since + * the others don't affect the result of the expression. + */ + if ( + node.parent.type === "SequenceExpression" && + node.parent.expressions.at(-1) === node + ) { + return isInFlaggedContext(node.parent); + } + + } + + return isInBooleanContext(node); } @@ -147,7 +190,6 @@ module.exports = { * Determines whether the given node needs to be parenthesized when replacing the previous node. * It assumes that `previousNode` is the node to be reported by this rule, so it has a limited list * of possible parent node types. By the same assumption, the node's role in a particular parent is already known. - * For example, if the parent is `ConditionalExpression`, `previousNode` must be its `test` child. * @param {ASTNode} previousNode Previous node. * @param {ASTNode} node The node to check. * @throws {Error} (Unreachable.) @@ -157,6 +199,7 @@ module.exports = { if (previousNode.parent.type === "ChainExpression") { return needsParens(previousNode.parent, node); } + if (isParenthesized(previousNode)) { // parentheses around the previous node will stay, so there is no need for an additional pair @@ -174,9 +217,18 @@ module.exports = { case "DoWhileStatement": case "WhileStatement": case "ForStatement": + case "SequenceExpression": return false; case "ConditionalExpression": - return precedence(node) <= precedence(parent); + if (previousNode === parent.test) { + return precedence(node) <= precedence(parent); + } + if (previousNode === parent.consequent || previousNode === parent.alternate) { + return precedence(node) < precedence({ type: "AssignmentExpression" }); + } + + /* c8 ignore next */ + throw new Error("Ternary child must be test, consequent, or alternate."); case "UnaryExpression": return precedence(node) < precedence(parent); case "LogicalExpression": diff --git a/tests/lib/rules/no-extra-boolean-cast.js b/tests/lib/rules/no-extra-boolean-cast.js index 27f30962543..70f4c66b30f 100644 --- a/tests/lib/rules/no-extra-boolean-cast.js +++ b/tests/lib/rules/no-extra-boolean-cast.js @@ -34,6 +34,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { "for(Boolean(foo);;) {}", "for(;; Boolean(foo)) {}", "if (new Boolean(foo)) {}", + "if ((Boolean(1), 2)) {}", { code: "var foo = bar || !!baz", options: [{ enforceForLogicalOperands: true }] @@ -111,7 +112,105 @@ ruleTester.run("no-extra-boolean-cast", rule, { code: "if (!!foo ?? bar) {}", options: [{ enforceForLogicalOperands: true }], languageOptions: { ecmaVersion: 2020 } - } + }, + { + code: "var foo = bar || !!baz", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "var foo = bar && !!baz", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "var foo = bar || (baz && !!bat)", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "function foo() { return (!!bar || baz); }", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "var foo = bar() ? (!!baz && bat) : (!!bat && qux)", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "for(!!(foo && bar);;) {}", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "for(;; !!(foo || bar)) {}", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "var foo = Boolean(bar) || baz;", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "var foo = bar || Boolean(baz);", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "var foo = Boolean(bar) || Boolean(baz);", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "function foo() { return (Boolean(bar) || baz); }", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "var foo = bar() ? Boolean(baz) || bat : Boolean(bat)", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "for(Boolean(foo) || bar;;) {}", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "for(;; Boolean(foo) || bar) {}", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "if (new Boolean(foo) || bar) {}", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "if (!!foo || bar) {}", + options: [{ enforceForInnerExpressions: false }] + }, + { + code: "if ((!!foo || bar) === baz) {}", + options: [{ enforceForInnerExpressions: true }] + }, + { + code: "if (!!foo ?? bar) {}", + options: [{ enforceForInnerExpressions: true }], + languageOptions: { ecmaVersion: 2020 } + }, + { + code: "if ((1, Boolean(2), 3)) {}", + options: [{ enforceForInnerExpressions: true }] + }, + + /* + * additional expressions should not be checked with option + * configurations other than `enforceForInnerExpressions: true`. + */ + ...[ + "Boolean((1, 2, Boolean(3)))", + "Boolean(foo ? Boolean(bar) : Boolean(baz))", + "Boolean(foo ?? Boolean(bar))" + ].flatMap(code => + [ + { code }, + { + code, + options: [{ enforceForLogicalOperands: true }] + }, + { + code, + options: [{ enforceForLogicalOperands: false }] + } + ]) ], invalid: [ @@ -1380,110 +1479,784 @@ ruleTester.run("no-extra-boolean-cast", rule, { }] }, - // test parentheses in autofix - { - code: "Boolean(!!(a, b))", - output: "Boolean((a, b))", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] - }, + // In Logical context { - code: "Boolean(Boolean((a, b)))", - output: "Boolean((a, b))", - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "if (!!foo || bar) {}", + output: "if (foo || bar) {}", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 5, + endColumn: 10 + }] }, { - code: "Boolean((!!(a, b)))", - output: "Boolean((a, b))", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "if (!!foo && bar) {}", + output: "if (foo && bar) {}", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 5, + endColumn: 10 + }] }, + { - code: "Boolean((Boolean((a, b))))", - output: "Boolean((a, b))", - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "if ((!!foo || bar) && bat) {}", + output: "if ((foo || bar) && bat) {}", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 6, + endColumn: 11 + }] }, { - code: "Boolean(!(!(a, b)))", - output: "Boolean((a, b))", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "if (foo && !!bar) {}", + output: "if (foo && bar) {}", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 12, + endColumn: 17 + }] }, { - code: "Boolean((!(!(a, b))))", - output: "Boolean((a, b))", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "do {} while (!!foo || bar)", + output: "do {} while (foo || bar)", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 14 + }] }, { - code: "Boolean(!!(a = b))", - output: "Boolean(a = b)", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "while (!!foo || bar) {}", + output: "while (foo || bar) {}", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 8 + }] }, { - code: "Boolean((!!(a = b)))", - output: "Boolean((a = b))", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "!!foo && bat ? bar : baz", + output: "foo && bat ? bar : baz", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 1 + }] }, { - code: "Boolean(Boolean(a = b))", - output: "Boolean(a = b)", - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "for (; !!foo || bar;) {}", + output: "for (; foo || bar;) {}", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 8 + }] }, { - code: "Boolean(Boolean((a += b)))", - output: "Boolean(a += b)", - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "!!!foo || bar", + output: "!foo || bar", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 2 + }] }, { - code: "Boolean(!!(a === b))", - output: "Boolean(a === b)", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "Boolean(!!foo || bar)", + output: "Boolean(foo || bar)", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 9 + }] }, { - code: "Boolean(!!((a !== b)))", - output: "Boolean(a !== b)", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "new Boolean(!!foo || bar)", + output: "new Boolean(foo || bar)", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 13 + }] }, { - code: "Boolean(!!a.b)", - output: "Boolean(a.b)", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "if (Boolean(foo) || bar) {}", + output: "if (foo || bar) {}", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] }, { - code: "Boolean(Boolean((a)))", - output: "Boolean(a)", - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "do {} while (Boolean(foo) || bar)", + output: "do {} while (foo || bar)", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] }, { - code: "Boolean((!!(a)))", - output: "Boolean((a))", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "while (Boolean(foo) || bar) {}", + output: "while (foo || bar) {}", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] }, - { - code: "new Boolean(!!(a, b))", - output: "new Boolean((a, b))", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "Boolean(foo) || bat ? bar : baz", + output: "foo || bat ? bar : baz", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] }, { - code: "new Boolean(Boolean((a, b)))", - output: "new Boolean((a, b))", - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "for (; Boolean(foo) || bar;) {}", + output: "for (; foo || bar;) {}", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] }, { - code: "new Boolean((!!(a, b)))", - output: "new Boolean((a, b))", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "!Boolean(foo) || bar", + output: "!foo || bar", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] }, { - code: "new Boolean((Boolean((a, b))))", - output: "new Boolean((a, b))", - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "!Boolean(foo && bar) || bat", + output: "!(foo && bar) || bat", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] }, { - code: "new Boolean(!(!(a, b)))", - output: "new Boolean((a, b))", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "!Boolean(foo + bar) || bat", + output: "!(foo + bar) || bat", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] }, { - code: "new Boolean((!(!(a, b))))", + code: "!Boolean(+foo) || bar", + output: "!+foo || bar", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo()) || bar", + output: "!foo() || bar", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo() || bar)", + output: "!(foo() || bar)", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo = bar) || bat", + output: "!(foo = bar) || bat", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(...foo) || bar;", + output: null, + options: [{ enforceForInnerExpressions: true }], + languageOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo, bar()) || bar;", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean((foo, bar()) || bat);", + output: "!((foo, bar()) || bat);", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean() || bar;", + output: "true || bar;", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!(Boolean()) || bar;", + output: "true || bar;", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if (!Boolean() || bar) { foo() }", + output: "if (true || bar) { foo() }", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "while (!Boolean() || bar) { foo() }", + output: "while (true || bar) { foo() }", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "var foo = Boolean() || bar ? bar() : baz()", + output: "var foo = false || bar ? bar() : baz()", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if (Boolean() || bar) { foo() }", + output: "if (false || bar) { foo() }", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "while (Boolean() || bar) { foo() }", + output: "while (false || bar) { foo() }", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + + + // Adjacent tokens tests + { + code: "function *foo() { yield(!!a || d) ? b : c }", + output: "function *foo() { yield(a || d) ? b : c }", + options: [{ enforceForInnerExpressions: true }], + languageOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "function *foo() { yield(!! a || d) ? b : c }", + output: "function *foo() { yield(a || d) ? b : c }", + options: [{ enforceForInnerExpressions: true }], + languageOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "function *foo() { yield(! !a || d) ? b : c }", + output: "function *foo() { yield(a || d) ? b : c }", + options: [{ enforceForInnerExpressions: true }], + languageOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "function *foo() { yield (!!a || d) ? b : c }", + output: "function *foo() { yield (a || d) ? b : c }", + options: [{ enforceForInnerExpressions: true }], + languageOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "function *foo() { yield/**/(!!a || d) ? b : c }", + output: "function *foo() { yield/**/(a || d) ? b : c }", + options: [{ enforceForInnerExpressions: true }], + languageOptions: { ecmaVersion: 2015 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "x=!!a || d ? b : c ", + output: "x=a || d ? b : c ", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "void(!Boolean() || bar)", + output: "void(true || bar)", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "void(! Boolean() || bar)", + output: "void(true || bar)", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "typeof(!Boolean() || bar)", + output: "typeof(true || bar)", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(!Boolean() || bar)", + output: "(true || bar)", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "void/**/(!Boolean() || bar)", + output: "void/**/(true || bar)", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + + // Comments tests + { + code: "!/**/(!!foo || bar)", + output: "!/**/(foo || bar)", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "!!/**/!foo || bar", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "!!!/**/foo || bar", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "!(!!foo || bar)/**/", + output: "!(foo || bar)/**/", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "if(!/**/!foo || bar);", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "(!!/**/foo || bar ? 1 : 2)", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression" + }] + }, + { + code: "!/**/(Boolean(foo) || bar)", + output: "!/**/(foo || bar)", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean/**/(foo) || bar", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(/**/foo) || bar", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(foo/**/) || bar", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!(Boolean(foo)|| bar)/**/", + output: "!(foo|| bar)/**/", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(Boolean/**/(foo) || bar);", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(Boolean(foo/**/)|| bar ? 1 : 2)", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "/**/!Boolean()|| bar", + output: "/**/true|| bar", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!/**/Boolean()|| bar", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean/**/()|| bar", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "!Boolean(/**/)|| bar", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(!Boolean()|| bar)/**/", + output: "(true|| bar)/**/", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(!/**/Boolean()|| bar);", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(!Boolean(/**/) || bar ? 1 : 2)", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(/**/Boolean()|| bar);", + output: "if(/**/false|| bar);", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(Boolean/**/()|| bar);", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(Boolean(/**/)|| bar);", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if(Boolean()|| bar/**/);", + output: "if(false|| bar/**/);", + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "(Boolean/**/()|| bar ? 1 : 2)", + output: null, + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedCall", + type: "CallExpression" + }] + }, + { + code: "if (a && !!(b ? c : d)){}", + output: "if (a && (b ? c : d)){}", + + options: [{ enforceForInnerExpressions: true }], + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 10, + endColumn: 23 + }] + }, + { + code: "function *foo() { yield!!a || d ? b : c }", + output: "function *foo() { yield a || d ? b : c }", + options: [{ enforceForInnerExpressions: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unexpectedNegation", + type: "UnaryExpression", + column: 24, + endColumn: 27 + }] + }, + + // test parentheses in autofix + { + code: "Boolean(!!(a, b))", + output: "Boolean((a, b))", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "Boolean(Boolean((a, b)))", + output: "Boolean((a, b))", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "Boolean((!!(a, b)))", + output: "Boolean((a, b))", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "Boolean((Boolean((a, b))))", + output: "Boolean((a, b))", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "Boolean(!(!(a, b)))", + output: "Boolean((a, b))", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "Boolean((!(!(a, b))))", + output: "Boolean((a, b))", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "Boolean(!!(a = b))", + output: "Boolean(a = b)", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "Boolean((!!(a = b)))", + output: "Boolean((a = b))", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "Boolean(Boolean(a = b))", + output: "Boolean(a = b)", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "Boolean(Boolean((a += b)))", + output: "Boolean(a += b)", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "Boolean(!!(a === b))", + output: "Boolean(a === b)", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "Boolean(!!((a !== b)))", + output: "Boolean(a !== b)", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "Boolean(!!a.b)", + output: "Boolean(a.b)", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "Boolean(Boolean((a)))", + output: "Boolean(a)", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "Boolean((!!(a)))", + output: "Boolean((a))", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + + { + code: "new Boolean(!!(a, b))", + output: "new Boolean((a, b))", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "new Boolean(Boolean((a, b)))", + output: "new Boolean((a, b))", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "new Boolean((!!(a, b)))", + output: "new Boolean((a, b))", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "new Boolean((Boolean((a, b))))", + output: "new Boolean((a, b))", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "new Boolean(!(!(a, b)))", + output: "new Boolean((a, b))", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "new Boolean((!(!(a, b))))", output: "new Boolean((a, b))", errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] }, @@ -2015,117 +2788,449 @@ ruleTester.run("no-extra-boolean-cast", rule, { errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] }, { - code: "async function f() { !!!(await a) }", - output: "async function f() { !await a }", - languageOptions: { ecmaVersion: 2017 }, - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "async function f() { !!!(await a) }", + output: "async function f() { !await a }", + languageOptions: { ecmaVersion: 2017 }, + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "async function f() { !Boolean(await a) }", + output: "async function f() { !await a }", + languageOptions: { ecmaVersion: 2017 }, + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "!!!!a", + output: "!!a", // Reports 2 errors. After the first fix, the second error will disappear. + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] + }, + { + code: "!!(!(!a))", + output: "!!a", // Reports 2 errors. After the first fix, the second error will disappear. + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] + }, + { + code: "!Boolean(!a)", + output: "!!a", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "!Boolean((!a))", + output: "!!a", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "!Boolean(!(a))", + output: "!!(a)", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "!(Boolean(!a))", + output: "!(!a)", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "!!!+a", + output: "!+a", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "!!!(+a)", + output: "!+a", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "!!(!+a)", + output: "!+a", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "!(!!+a)", + output: "!(+a)", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "!Boolean((-a))", + output: "!-a", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "!Boolean(-(a))", + output: "!-(a)", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "!!!(--a)", + output: "!--a", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "!Boolean(a++)", + output: "!a++", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "!!!f(a)", + output: "!f(a)", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "!!!(f(a))", + output: "!f(a)", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "!!!a", + output: "!a", + errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + }, + { + code: "!Boolean(a)", + output: "!a", + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + }, + { + code: "if (!!(a, b) || !!(c, d)) {}", + output: "if ((a, b) || (c, d)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] + }, + { + code: "if (Boolean((a, b)) || Boolean((c, d))) {}", + output: "if ((a, b) || (c, d)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedCall", type: "CallExpression" }, + { messageId: "unexpectedCall", type: "CallExpression" } + ] + }, + { + code: "if ((!!((a, b))) || (!!((c, d)))) {}", + output: "if ((a, b) || (c, d)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] + }, + { + code: "if (!!(a, b) && !!(c, d)) {}", + output: "if ((a, b) && (c, d)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] + }, + { + code: "if (Boolean((a, b)) && Boolean((c, d))) {}", + output: "if ((a, b) && (c, d)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedCall", type: "CallExpression" }, + { messageId: "unexpectedCall", type: "CallExpression" } + ] + }, + { + code: "if ((!!((a, b))) && (!!((c, d)))) {}", + output: "if ((a, b) && (c, d)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] + }, + { + code: "if (!!(a = b) || !!(c = d)) {}", + output: "if ((a = b) || (c = d)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] + }, + { + code: "if (Boolean(a /= b) || Boolean(c /= d)) {}", + output: "if ((a /= b) || (c /= d)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedCall", type: "CallExpression" }, + { messageId: "unexpectedCall", type: "CallExpression" } + ] + }, + { + code: "if (!!(a >>= b) && !!(c >>= d)) {}", + output: "if ((a >>= b) && (c >>= d)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] + }, + { + code: "if (Boolean(a **= b) && Boolean(c **= d)) {}", + output: "if ((a **= b) && (c **= d)) {}", + options: [{ enforceForLogicalOperands: true }], + languageOptions: { ecmaVersion: 2016 }, + errors: [ + { messageId: "unexpectedCall", type: "CallExpression" }, + { messageId: "unexpectedCall", type: "CallExpression" } + ] + }, + { + code: "if (!!(a ? b : c) || !!(d ? e : f)) {}", + output: "if ((a ? b : c) || (d ? e : f)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] + }, + { + code: "if (Boolean(a ? b : c) || Boolean(d ? e : f)) {}", + output: "if ((a ? b : c) || (d ? e : f)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedCall", type: "CallExpression" }, + { messageId: "unexpectedCall", type: "CallExpression" } + ] + }, + { + code: "if (!!(a ? b : c) && !!(d ? e : f)) {}", + output: "if ((a ? b : c) && (d ? e : f)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] + }, + { + code: "if (Boolean(a ? b : c) && Boolean(d ? e : f)) {}", + output: "if ((a ? b : c) && (d ? e : f)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedCall", type: "CallExpression" }, + { messageId: "unexpectedCall", type: "CallExpression" } + ] + }, + { + code: "if (!!(a || b) || !!(c || d)) {}", + output: "if (a || b || (c || d)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] + }, + { + code: "if (Boolean(a || b) || Boolean(c || d)) {}", + output: "if (a || b || (c || d)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedCall", type: "CallExpression" }, + { messageId: "unexpectedCall", type: "CallExpression" } + ] + }, + { + code: "if (!!(a || b) && !!(c || d)) {}", + output: "if ((a || b) && (c || d)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] }, { - code: "async function f() { !Boolean(await a) }", - output: "async function f() { !await a }", - languageOptions: { ecmaVersion: 2017 }, - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "if (Boolean(a || b) && Boolean(c || d)) {}", + output: "if ((a || b) && (c || d)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedCall", type: "CallExpression" }, + { messageId: "unexpectedCall", type: "CallExpression" } + ] }, { - code: "!!!!a", - output: "!!a", // Reports 2 errors. After the first fix, the second error will disappear. + code: "if (!!(a && b) || !!(c && d)) {}", + output: "if (a && b || c && d) {}", + options: [{ enforceForLogicalOperands: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } ] }, { - code: "!!(!(!a))", - output: "!!a", // Reports 2 errors. After the first fix, the second error will disappear. + code: "if (Boolean(a && b) || Boolean(c && d)) {}", + output: "if (a && b || c && d) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedCall", type: "CallExpression" }, + { messageId: "unexpectedCall", type: "CallExpression" } + ] + }, + { + code: "if (!!(a && b) && !!(c && d)) {}", + output: "if (a && b && (c && d)) {}", + options: [{ enforceForLogicalOperands: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } ] }, { - code: "!Boolean(!a)", - output: "!!a", - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "if (Boolean(a && b) && Boolean(c && d)) {}", + output: "if (a && b && (c && d)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedCall", type: "CallExpression" }, + { messageId: "unexpectedCall", type: "CallExpression" } + ] }, { - code: "!Boolean((!a))", - output: "!!a", - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "if (!!(a !== b) || !!(c !== d)) {}", + output: "if (a !== b || c !== d) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] }, { - code: "!Boolean(!(a))", - output: "!!(a)", - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "if (Boolean(a != b) || Boolean(c != d)) {}", + output: "if (a != b || c != d) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedCall", type: "CallExpression" }, + { messageId: "unexpectedCall", type: "CallExpression" } + ] }, { - code: "!(Boolean(!a))", - output: "!(!a)", - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "if (!!(a === b) && !!(c === d)) {}", + output: "if (a === b && c === d) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] }, { - code: "!!!+a", - output: "!+a", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "if (!!(a > b) || !!(c < d)) {}", + output: "if (a > b || c < d) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] }, { - code: "!!!(+a)", - output: "!+a", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "if (Boolean(!a) || Boolean(+b)) {}", + output: "if (!a || +b) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedCall", type: "CallExpression" }, + { messageId: "unexpectedCall", type: "CallExpression" } + ] }, { - code: "!!(!+a)", - output: "!+a", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "if (!!f(a) && !!b.c) {}", + output: "if (f(a) && b.c) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] }, { - code: "!(!!+a)", - output: "!(+a)", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "if (Boolean(a) || !!b) {}", + output: "if (a || b) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedCall", type: "CallExpression" }, + { messageId: "unexpectedNegation", type: "UnaryExpression" } + ] }, { - code: "!Boolean((-a))", - output: "!-a", - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "if (!!a && Boolean(b)) {}", + output: "if (a && b) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedCall", type: "CallExpression" } + ] }, { - code: "!Boolean(-(a))", - output: "!-(a)", + code: "if ((!!a) || (Boolean(b))) {}", + output: "if ((a) || (b)) {}", + options: [{ enforceForLogicalOperands: true }], + errors: [ + { messageId: "unexpectedNegation", type: "UnaryExpression" }, + { messageId: "unexpectedCall", type: "CallExpression" } + ] + }, + + { + code: "if (Boolean(a ?? b) || c) {}", + output: "if ((a ?? b) || c) {}", + options: [{ enforceForLogicalOperands: true }], + languageOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] }, + + // Optional chaining { - code: "!!!(--a)", - output: "!--a", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "if (Boolean?.(foo)) {};", + output: "if (foo) {};", + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpectedCall" }] }, { - code: "!Boolean(a++)", - output: "!a++", - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "if (Boolean?.(a ?? b) || c) {}", + output: "if ((a ?? b) || c) {}", + options: [{ enforceForLogicalOperands: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpectedCall" }] }, + + // https://github.com/eslint/eslint/issues/17173 { - code: "!!!f(a)", - output: "!f(a)", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "if (!Boolean(a as any)) { }", + output: "if (!(a as any)) { }", + languageOptions: { + parser: require(parser("typescript-parsers/boolean-cast-with-assertion")), + ecmaVersion: 2020 + }, + errors: [{ messageId: "unexpectedCall" }] }, { - code: "!!!(f(a))", - output: "!f(a)", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "if ((1, 2, Boolean(3))) {}", + output: "if ((1, 2, 3)) {}", + options: [{ enforceForInnerExpressions: true }], + errors: [{ messageId: "unexpectedCall" }] }, { - code: "!!!a", - output: "!a", - errors: [{ messageId: "unexpectedNegation", type: "UnaryExpression" }] + code: "if (a ?? Boolean(b)) {}", + output: "if (a ?? b) {}", + options: [{ enforceForInnerExpressions: true }], + errors: [{ messageId: "unexpectedCall" }] }, { - code: "!Boolean(a)", - output: "!a", - errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] + code: "if ((a, b, c ?? (d, e, f ?? Boolean(g)))) {}", + output: "if ((a, b, c ?? (d, e, f ?? g))) {}", + options: [{ enforceForInnerExpressions: true }], + errors: [{ messageId: "unexpectedCall" }] }, { code: "if (!!(a, b) || !!(c, d)) {}", output: "if ((a, b) || (c, d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2134,7 +3239,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (Boolean((a, b)) || Boolean((c, d))) {}", output: "if ((a, b) || (c, d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedCall", type: "CallExpression" }, { messageId: "unexpectedCall", type: "CallExpression" } @@ -2143,7 +3248,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if ((!!((a, b))) || (!!((c, d)))) {}", output: "if ((a, b) || (c, d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2152,7 +3257,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!(a, b) && !!(c, d)) {}", output: "if ((a, b) && (c, d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2161,7 +3266,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (Boolean((a, b)) && Boolean((c, d))) {}", output: "if ((a, b) && (c, d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedCall", type: "CallExpression" }, { messageId: "unexpectedCall", type: "CallExpression" } @@ -2170,7 +3275,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if ((!!((a, b))) && (!!((c, d)))) {}", output: "if ((a, b) && (c, d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2179,7 +3284,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!(a = b) || !!(c = d)) {}", output: "if ((a = b) || (c = d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2188,7 +3293,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (Boolean(a /= b) || Boolean(c /= d)) {}", output: "if ((a /= b) || (c /= d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedCall", type: "CallExpression" }, { messageId: "unexpectedCall", type: "CallExpression" } @@ -2197,7 +3302,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!(a >>= b) && !!(c >>= d)) {}", output: "if ((a >>= b) && (c >>= d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2206,7 +3311,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (Boolean(a **= b) && Boolean(c **= d)) {}", output: "if ((a **= b) && (c **= d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], languageOptions: { ecmaVersion: 2016 }, errors: [ { messageId: "unexpectedCall", type: "CallExpression" }, @@ -2216,7 +3321,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!(a ? b : c) || !!(d ? e : f)) {}", output: "if ((a ? b : c) || (d ? e : f)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2225,7 +3330,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (Boolean(a ? b : c) || Boolean(d ? e : f)) {}", output: "if ((a ? b : c) || (d ? e : f)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedCall", type: "CallExpression" }, { messageId: "unexpectedCall", type: "CallExpression" } @@ -2234,7 +3339,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!(a ? b : c) && !!(d ? e : f)) {}", output: "if ((a ? b : c) && (d ? e : f)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2243,7 +3348,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (Boolean(a ? b : c) && Boolean(d ? e : f)) {}", output: "if ((a ? b : c) && (d ? e : f)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedCall", type: "CallExpression" }, { messageId: "unexpectedCall", type: "CallExpression" } @@ -2252,7 +3357,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!(a || b) || !!(c || d)) {}", output: "if (a || b || (c || d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2261,7 +3366,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (Boolean(a || b) || Boolean(c || d)) {}", output: "if (a || b || (c || d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedCall", type: "CallExpression" }, { messageId: "unexpectedCall", type: "CallExpression" } @@ -2270,7 +3375,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!(a || b) && !!(c || d)) {}", output: "if ((a || b) && (c || d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2279,7 +3384,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (Boolean(a || b) && Boolean(c || d)) {}", output: "if ((a || b) && (c || d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedCall", type: "CallExpression" }, { messageId: "unexpectedCall", type: "CallExpression" } @@ -2288,7 +3393,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!(a && b) || !!(c && d)) {}", output: "if (a && b || c && d) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2297,7 +3402,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (Boolean(a && b) || Boolean(c && d)) {}", output: "if (a && b || c && d) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedCall", type: "CallExpression" }, { messageId: "unexpectedCall", type: "CallExpression" } @@ -2306,7 +3411,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!(a && b) && !!(c && d)) {}", output: "if (a && b && (c && d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2315,7 +3420,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (Boolean(a && b) && Boolean(c && d)) {}", output: "if (a && b && (c && d)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedCall", type: "CallExpression" }, { messageId: "unexpectedCall", type: "CallExpression" } @@ -2324,7 +3429,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!(a !== b) || !!(c !== d)) {}", output: "if (a !== b || c !== d) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2333,7 +3438,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (Boolean(a != b) || Boolean(c != d)) {}", output: "if (a != b || c != d) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedCall", type: "CallExpression" }, { messageId: "unexpectedCall", type: "CallExpression" } @@ -2342,7 +3447,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!(a === b) && !!(c === d)) {}", output: "if (a === b && c === d) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2351,7 +3456,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!(a > b) || !!(c < d)) {}", output: "if (a > b || c < d) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2360,7 +3465,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (Boolean(!a) || Boolean(+b)) {}", output: "if (!a || +b) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedCall", type: "CallExpression" }, { messageId: "unexpectedCall", type: "CallExpression" } @@ -2369,7 +3474,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!f(a) && !!b.c) {}", output: "if (f(a) && b.c) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2378,7 +3483,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (Boolean(a) || !!b) {}", output: "if (a || b) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedCall", type: "CallExpression" }, { messageId: "unexpectedNegation", type: "UnaryExpression" } @@ -2387,7 +3492,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (!!a && Boolean(b)) {}", output: "if (a && b) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedCall", type: "CallExpression" } @@ -2396,7 +3501,7 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if ((!!a) || (Boolean(b))) {}", output: "if ((a) || (b)) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], errors: [ { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedCall", type: "CallExpression" } @@ -2406,34 +3511,93 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if (Boolean(a ?? b) || c) {}", output: "if ((a ?? b) || c) {}", - options: [{ enforceForLogicalOperands: true }], + options: [{ enforceForInnerExpressions: true }], languageOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] }, - - // Optional chaining { - code: "if (Boolean?.(foo)) ;", - output: "if (foo) ;", + code: "if (Boolean?.(a ?? b) || c) {}", + output: "if ((a ?? b) || c) {}", + options: [{ enforceForInnerExpressions: true }], languageOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "unexpectedCall" }] }, { - code: "if (Boolean?.(a ?? b) || c) {}", - output: "if ((a ?? b) || c) {}", - options: [{ enforceForLogicalOperands: true }], - languageOptions: { ecmaVersion: 2020 }, + code: "if (a ? Boolean(b) : c) {}", + output: "if (a ? b : c) {}", + options: [{ enforceForInnerExpressions: true }], errors: [{ messageId: "unexpectedCall" }] }, - - // https://github.com/eslint/eslint/issues/17173 { - code: "if (!Boolean(a as any)) { }", - output: "if (!(a as any)) { }", - languageOptions: { - parser: require(parser("typescript-parsers/boolean-cast-with-assertion")), - ecmaVersion: 2020 - }, + code: "if (a ? b : Boolean(c)) {}", + output: "if (a ? b : c) {}", + options: [{ enforceForInnerExpressions: true }], + errors: [{ messageId: "unexpectedCall" }] + }, + { + code: "if (a ? b : Boolean(c ? d : e)) {}", + output: "if (a ? b : c ? d : e) {}", + options: [{ enforceForInnerExpressions: true }], + errors: [{ messageId: "unexpectedCall" }] + }, + { + code: "const ternary = Boolean(bar ? !!baz : bat);", + output: "const ternary = Boolean(bar ? baz : bat);", + options: [{ enforceForInnerExpressions: true }], + errors: [{ messageId: "unexpectedNegation" }] + }, + { + code: "const commaOperator = Boolean((bar, baz, !!bat));", + output: "const commaOperator = Boolean((bar, baz, bat));", + options: [{ enforceForInnerExpressions: true }], + errors: [{ messageId: "unexpectedNegation" }] + }, + { + code: ` +for (let i = 0; (console.log(i), Boolean(i < 10)); i++) { + // ... +}`, + output: ` +for (let i = 0; (console.log(i), i < 10); i++) { + // ... +}`, + options: [{ enforceForInnerExpressions: true }], + errors: [{ messageId: "unexpectedCall" }] + }, + { + code: "const nullishCoalescingOperator = Boolean(bar ?? Boolean(baz));", + output: "const nullishCoalescingOperator = Boolean(bar ?? baz);", + options: [{ enforceForInnerExpressions: true }], + errors: [{ messageId: "unexpectedCall" }] + }, + { + code: "if (a ? Boolean(b = c) : Boolean(d = e));", + output: "if (a ? b = c : d = e);", + options: [{ enforceForInnerExpressions: true }], + errors: [{ messageId: "unexpectedCall" }, { messageId: "unexpectedCall" }] + }, + { + code: "if (a ? Boolean((b, c)) : Boolean((d, e)));", + output: "if (a ? (b, c) : (d, e));", + options: [{ enforceForInnerExpressions: true }], + errors: [{ messageId: "unexpectedCall" }, { messageId: "unexpectedCall" }] + }, + { + code: ` +function * generator() { + if (a ? Boolean(yield y) : x) { + return a; + }; +} +`, + output: ` +function * generator() { + if (a ? yield y : x) { + return a; + }; +} +`, + options: [{ enforceForInnerExpressions: true }], errors: [{ messageId: "unexpectedCall" }] } ] From ac7f718de66131187302387fc26907c4c93196f9 Mon Sep 17 00:00:00 2001 From: Peter Briggs <146312751+pwbriggs@users.noreply.github.com> Date: Mon, 6 May 2024 12:19:37 -0700 Subject: [PATCH 121/166] docs: reflect release of v9 in config migration guide (#18412) Update the eslintrc-to-flat config migration guide with the release of 9.0.0. --- docs/src/use/configure/migration-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/use/configure/migration-guide.md b/docs/src/use/configure/migration-guide.md index f6f6669a631..dd09e9b15d8 100644 --- a/docs/src/use/configure/migration-guide.md +++ b/docs/src/use/configure/migration-guide.md @@ -18,7 +18,7 @@ For reference information on these configuration formats, refer to the following ## Start Using Flat Config Files -Starting with ESLint v9.0.0, the flat config file format will be the default configuration file format. Once ESLint v9.0.0 is released, you can start using the flat config file format without any additional configuration. +The flat config file format has been the default configuration file format since ESLint v9.0.0. You can start using the flat config file format without any additional configuration. To use flat config with ESLint v8, place a `eslint.config.js` file in the root of your project **or** set the `ESLINT_USE_FLAT_CONFIG` environment variable to `true`. From 27e3060f7519d84501a11218343c34df4947b303 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 6 May 2024 15:27:17 -0400 Subject: [PATCH 122/166] chore: Disable documentation label (#18423) --- .github/labeler.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 4fa6f8f24a7..ea024d8f784 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,7 +1,8 @@ -documentation: -- any: - - changed-files: - - all-globs-to-all-files: ['docs/**', '!lib/rules/**'] +# Skipping this due to https://github.com/actions/labeler/issues/763 +#documentation: +#- any: +# - changed-files: +# - all-globs-to-all-files: ['docs/**', '!lib/rules/**'] rule: - any: From c18ad252c280443e85f788c70ce597e1941f8ff5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 12:37:03 +0800 Subject: [PATCH 123/166] chore: update actions/upload-artifact action to v4 (#18427) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ddad354441..5ad09d4a612 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,7 +82,7 @@ jobs: run: node Makefile wdio - name: Fuzz Test run: node Makefile fuzz - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: failure() with: name: logs From f47847c1b45ef1ac5f05f3a37f5f8c46b860c57f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 12:38:02 +0800 Subject: [PATCH 124/166] chore: update actions/stale action to v9 (#18426) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 9e7355d34b9..e33d9db75aa 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -18,7 +18,7 @@ jobs: pull-requests: write steps: - - uses: actions/stale@v8 + - uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-issue-stale: 30 From 040700a7a19726bb9568fc190bff95e88fb87269 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 12:38:24 +0800 Subject: [PATCH 125/166] chore: update dependency markdownlint-cli to ^0.40.0 (#18425) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0c56381651a..5b5d814bdd2 100644 --- a/package.json +++ b/package.json @@ -142,7 +142,7 @@ "markdown-it": "^12.2.0", "markdown-it-container": "^3.0.0", "markdownlint": "^0.34.0", - "markdownlint-cli": "^0.39.0", + "markdownlint-cli": "^0.40.0", "marked": "^4.0.8", "metascraper": "^5.25.7", "metascraper-description": "^5.25.7", From 37eba48d6f2d3c99c5ecf2fc3967e428a6051dbb Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 7 May 2024 08:33:44 +0200 Subject: [PATCH 126/166] fix: don't crash when `fs.readFile` returns promise from another realm (#18416) * fix: don't crash when `fs.readFile` returns promise from another realm Fixes #18407 * update test case title --- lib/eslint/eslint.js | 3 +- package.json | 2 +- tests/lib/eslint/eslint.js | 76 +++++++++++++++++++++++++++++++------- 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index d3875425e01..900353385cf 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -9,8 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -// Note: Node.js 12 does not support fs/promises. -const fs = require("fs").promises; +const fs = require("fs/promises"); const { existsSync } = require("fs"); const path = require("path"); const findUp = require("find-up"); diff --git a/package.json b/package.json index 5b5d814bdd2..54290f00d54 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@eslint/js": "9.2.0", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.2.3", + "@humanwhocodes/retry": "^0.2.4", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", "chalk": "^4.0.0", diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 0e7bcfc4a26..53a43fe19d9 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -13,7 +13,7 @@ const assert = require("assert"); const util = require("util"); const fs = require("fs"); -const fsp = fs.promises; +const fsp = require("fs/promises"); const os = require("os"); const path = require("path"); const timers = require("node:timers/promises"); @@ -1057,6 +1057,61 @@ describe("ESLint", () => { await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Expected object with parse\(\) or parseForESLint\(\) method/u); }); + // https://github.com/eslint/eslint/issues/18407 + it("should work in case when `fsp.readFile()` returns an object that is not an instance of Promise from this realm", async () => { + + /** + * Promise wrapper + */ + class PromiseLike { + constructor(promise) { + this.promise = promise; + } + then(...args) { + return new PromiseLike(this.promise.then(...args)); + } + catch(...args) { + return new PromiseLike(this.promise.catch(...args)); + } + finally(...args) { + return new PromiseLike(this.promise.finally(...args)); + } + } + + const spy = sinon.spy( + (...args) => new PromiseLike(fsp.readFile(...args)) + ); + + const { ESLint: LocalESLint } = proxyquire("../../../lib/eslint/eslint", { + "fs/promises": { + readFile: spy, + "@noCallThru": false // allows calling other methods of `fs/promises` + } + }); + + const testDir = "tests/fixtures/simple-valid-project"; + const expectedLintedFiles = [ + path.resolve(testDir, "foo.js"), + path.resolve(testDir, "src", "foobar.js") + ]; + + eslint = new LocalESLint({ + cwd: originalDir, + overrideConfigFile: path.resolve(testDir, "eslint.config.js") + }); + + const results = await eslint.lintFiles([`${testDir}/**/foo*.js`]); + + assert.strictEqual(results.length, expectedLintedFiles.length); + + expectedLintedFiles.forEach((file, index) => { + assert(spy.calledWith(file), `Spy was not called with ${file}`); + assert.strictEqual(results[index].filePath, file); + assert.strictEqual(results[index].messages.length, 0); + assert.strictEqual(results[index].suppressedMessages.length, 0); + }); + }); + describe("Invalid inputs", () => { [ @@ -5513,13 +5568,10 @@ describe("ESLint", () => { }); it("should call fs.writeFile() for each result with output", async () => { - const fakeFS = { - writeFile: sinon.spy(() => Promise.resolve()) - }; - const spy = fakeFS.writeFile; + const spy = sinon.spy(() => Promise.resolve()); const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", { - fs: { - promises: fakeFS + "fs/promises": { + writeFile: spy } }); @@ -5542,15 +5594,13 @@ describe("ESLint", () => { }); it("should call fs.writeFile() for each result with output and not at all for a result without output", async () => { - const fakeFS = { - writeFile: sinon.spy(() => Promise.resolve()) - }; - const spy = fakeFS.writeFile; + const spy = sinon.spy(() => Promise.resolve()); const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", { - fs: { - promises: fakeFS + "fs/promises": { + writeFile: spy } }); + const results = [ { filePath: path.resolve("foo.js"), From e7635126f36145b47fe5d135ab258af43b2715c9 Mon Sep 17 00:00:00 2001 From: MaoShizhong <122839503+MaoShizhong@users.noreply.github.com> Date: Wed, 8 May 2024 15:04:51 +0100 Subject: [PATCH 127/166] docs: Link global ignores section in config object property list (#18430) * Link global ignores section in config object property list * Update docs/src/use/configure/configuration-files.md --------- Co-authored-by: Nicholas C. Zakas --- docs/src/use/configure/configuration-files.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/use/configure/configuration-files.md b/docs/src/use/configure/configuration-files.md index b7bfac0e977..a64db7d1934 100644 --- a/docs/src/use/configure/configuration-files.md +++ b/docs/src/use/configure/configuration-files.md @@ -58,7 +58,7 @@ Each configuration object contains all of the information ESLint needs to execut * `name` - A name for the configuration object. This is used in error messages and config inspector to help identify which configuration object is being used. ([Naming Convention](#configuration-naming-conventions)) * `files` - An array of glob patterns indicating the files that the configuration object should apply to. If not specified, the configuration object applies to all files matched by any other configuration object. -* `ignores` - An array of glob patterns indicating the files that the configuration object should not apply to. If not specified, the configuration object applies to all files matched by `files`. +* `ignores` - An array of glob patterns indicating the files that the configuration object should not apply to. If not specified, the configuration object applies to all files matched by `files`. If `ignores` is used without any other keys in the configuration object, then the patterns act as [global ignores](#globally-ignoring-files-with-ignores). * `languageOptions` - An object containing settings related to how JavaScript is configured for linting. * `ecmaVersion` - The version of ECMAScript to support. May be any year (i.e., `2022`) or version (i.e., `5`). Set to `"latest"` for the most recent supported version. (default: `"latest"`) * `sourceType` - The type of JavaScript source code. Possible values are `"script"` for traditional script files, `"module"` for ECMAScript modules (ESM), and `"commonjs"` for CommonJS files. (default: `"module"` for `.js` and `.mjs` files; `"commonjs"` for `.cjs` files) From 04e7c6e0a24bd2d7691ae641e2dc0e6d538dcdfd Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Wed, 8 May 2024 22:10:34 +0530 Subject: [PATCH 128/166] docs: update deprecation notice of `no-return-await` (#18433) * docs: update deprecation notice in no-return-await * update code * update promises --- docs/src/rules/no-return-await.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/rules/no-return-await.md b/docs/src/rules/no-return-await.md index 2ee5a8b3659..bb4be5fbf6f 100644 --- a/docs/src/rules/no-return-await.md +++ b/docs/src/rules/no-return-await.md @@ -7,7 +7,7 @@ further_reading: - https://jakearchibald.com/2017/await-vs-return-vs-return-await/ --- -This rule was **deprecated** in ESLint v8.46.0 with no replacement. The original intent of this rule no longer applies due to the fact JavaScript now handles native `Promises` differently. It can now be slower to remove `await` rather than keeping it. More technical information can be found in [this V8 blog entry](https://v8.dev/blog/fast-async). +This rule was **deprecated** in ESLint v8.46.0 with no replacement. The original intent of this rule was to discourage the use of `return await`, to avoid an extra microtask. However, due to the fact that JavaScript now handles native `Promise`s differently, there is no longer an extra microtask. More technical information can be found in [this V8 blog entry](https://v8.dev/blog/fast-async). Using `return await` inside an `async function` keeps the current function in the call stack until the Promise that is being awaited has resolved, at the cost of an extra microtask before resolving the outer Promise. `return await` can also be used in a try/catch statement to catch errors from another function that returns a Promise. From a63ed722a64040d2be90f36e45f1f5060a9fe28e Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Thu, 9 May 2024 10:26:05 +0200 Subject: [PATCH 129/166] refactor: Use `node:` protocol for built-in Node.js modules (#18434) --- .eslintrc.js | 2 +- Makefile.js | 8 ++++---- bin/eslint.js | 2 +- docs/.eleventy.js | 2 +- docs/src/_data/site.js | 4 ++-- docs/tools/validate-links.js | 2 +- eslint.config.js | 2 +- lib/cli-engine/cli-engine.js | 4 ++-- lib/cli-engine/file-enumerator.js | 4 ++-- lib/cli-engine/lint-result-cache.js | 4 ++-- lib/cli-engine/load-rules.js | 4 ++-- lib/cli.js | 6 +++--- lib/eslint/eslint-helpers.js | 4 ++-- lib/eslint/eslint.js | 8 ++++---- lib/eslint/legacy-eslint.js | 6 +++--- .../code-path-analysis/code-path-analyzer.js | 2 +- lib/linter/code-path-analysis/fork-context.js | 2 +- lib/linter/linter.js | 2 +- lib/linter/report-translator.js | 2 +- lib/rule-tester/rule-tester.js | 6 +++--- lib/shared/runtime-info.js | 4 ++-- lib/source-code/token-store/index.js | 2 +- packages/eslint-config-eslint/nodejs.js | 3 ++- tests/_utils/test-lazy-loading-rules.js | 4 ++-- tests/bin/eslint.js | 6 +++--- tests/lib/cli-engine/cli-engine.js | 12 ++++++------ tests/lib/cli-engine/file-enumerator.js | 6 +++--- tests/lib/cli-engine/lint-result-cache.js | 4 ++-- tests/lib/cli.js | 8 ++++---- tests/lib/eslint/eslint.js | 18 +++++++++--------- tests/lib/eslint/legacy-eslint.js | 16 ++++++++-------- .../code-path-analysis/code-path-analyzer.js | 6 +++--- .../lib/linter/code-path-analysis/code-path.js | 2 +- tests/lib/linter/node-event-generator.js | 2 +- tests/lib/rule-tester/no-test-runners.js | 2 +- tests/lib/rule-tester/rule-tester.js | 4 ++-- tests/lib/rules/array-bracket-spacing.js | 2 +- tests/lib/rules/comma-dangle.js | 2 +- tests/lib/rules/indent-legacy.js | 4 ++-- tests/lib/rules/indent.js | 4 ++-- tests/lib/rules/object-curly-newline.js | 2 +- tests/lib/rules/object-curly-spacing.js | 2 +- tests/lib/rules/utils/ast-utils.js | 2 +- tests/lib/shared/runtime-info.js | 2 +- tests/lib/source-code/source-code.js | 4 ++-- tests/tools/check-rule-examples.js | 6 +++--- tools/check-emfile-handling.js | 8 ++++---- tools/check-rule-examples.js | 2 +- tools/code-sample-minimizer.js | 2 +- tools/eslint-fuzzer.js | 2 +- tools/fetch-docs-links.js | 4 ++-- tools/internal-rules/index.js | 4 ++-- .../internal-testers/event-generator-tester.js | 2 +- tools/update-eslint-all.js | 2 +- tools/update-readme.js | 2 +- wdio.conf.js | 2 +- webpack.config.js | 8 ++++++++ 57 files changed, 126 insertions(+), 117 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index c51fc72691a..df8d4a83d35 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,7 +16,7 @@ "use strict"; -const path = require("path"); +const path = require("node:path"); const INTERNAL_FILES = { CLI_ENGINE_PATTERN: "lib/cli-engine/**/*", diff --git a/Makefile.js b/Makefile.js index 6d37023f79a..9641d8e5697 100644 --- a/Makefile.js +++ b/Makefile.js @@ -12,12 +12,12 @@ const checker = require("npm-license"), ReleaseOps = require("eslint-release"), - fs = require("fs"), + fs = require("node:fs"), glob = require("glob"), marked = require("marked"), matter = require("gray-matter"), - os = require("os"), - path = require("path"), + os = require("node:os"), + path = require("node:path"), semver = require("semver"), ejs = require("ejs"), loadPerf = require("load-perf"), @@ -885,7 +885,7 @@ target.checkRuleFiles = function() { }; target.checkRuleExamples = function() { - const { execFileSync } = require("child_process"); + const { execFileSync } = require("node:child_process"); // We don't need the stack trace of execFileSync if the command fails. try { diff --git a/bin/eslint.js b/bin/eslint.js index 5f8eaa9b451..fa3960e23b7 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -64,7 +64,7 @@ function readStdin() { function getErrorMessage(error) { // Lazy loading because this is used only if an error happened. - const util = require("util"); + const util = require("node:util"); // Foolproof -- third-party module might throw non-object. if (typeof error !== "object" || error === null) { diff --git a/docs/.eleventy.js b/docs/.eleventy.js index 629f98bb585..c526abae509 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -7,7 +7,7 @@ const pluginTOC = require("eleventy-plugin-nesting-toc"); const markdownItAnchor = require("markdown-it-anchor"); const markdownItContainer = require("markdown-it-container"); const Image = require("@11ty/eleventy-img"); -const path = require("path"); +const path = require("node:path"); const { slug } = require("github-slugger"); const yaml = require("js-yaml"); const { highlighter, lineNumberPlugin } = require("./src/_plugins/md-syntax-highlighter"); diff --git a/docs/src/_data/site.js b/docs/src/_data/site.js index 62f17da93dd..95cd46aac54 100644 --- a/docs/src/_data/site.js +++ b/docs/src/_data/site.js @@ -9,8 +9,8 @@ // Requirements //----------------------------------------------------------------------------- -const path = require("path"); -const fs = require("fs"); +const path = require("node:path"); +const fs = require("node:fs"); const yaml = require("js-yaml"); //----------------------------------------------------------------------------- diff --git a/docs/tools/validate-links.js b/docs/tools/validate-links.js index 091dd2fe640..1ca0dd27186 100644 --- a/docs/tools/validate-links.js +++ b/docs/tools/validate-links.js @@ -1,6 +1,6 @@ "use strict"; -const path = require("path"); +const path = require("node:path"); const TapRender = require("@munter/tap-render"); const spot = require("tap-spot"); const hyperlink = require("hyperlink"); diff --git a/eslint.config.js b/eslint.config.js index 2d7b781f661..0e862e343e1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -25,7 +25,7 @@ // Requirements //----------------------------------------------------------------------------- -const path = require("path"); +const path = require("node:path"); const internalPlugin = require("eslint-plugin-internal-rules"); const eslintPluginRulesRecommendedConfig = require("eslint-plugin-eslint-plugin/configs/rules-recommended"); const eslintPluginTestsRecommendedConfig = require("eslint-plugin-eslint-plugin/configs/tests-recommended"); diff --git a/lib/cli-engine/cli-engine.js b/lib/cli-engine/cli-engine.js index 4fe54843e88..43fcc73d029 100644 --- a/lib/cli-engine/cli-engine.js +++ b/lib/cli-engine/cli-engine.js @@ -15,8 +15,8 @@ // Requirements //------------------------------------------------------------------------------ -const fs = require("fs"); -const path = require("path"); +const fs = require("node:fs"); +const path = require("node:path"); const defaultOptions = require("../../conf/default-cli-options"); const pkg = require("../../package.json"); diff --git a/lib/cli-engine/file-enumerator.js b/lib/cli-engine/file-enumerator.js index 5dff8d09ccd..da5d1039da7 100644 --- a/lib/cli-engine/file-enumerator.js +++ b/lib/cli-engine/file-enumerator.js @@ -34,8 +34,8 @@ // Requirements //------------------------------------------------------------------------------ -const fs = require("fs"); -const path = require("path"); +const fs = require("node:fs"); +const path = require("node:path"); const getGlobParent = require("glob-parent"); const isGlob = require("is-glob"); const escapeRegExp = require("escape-string-regexp"); diff --git a/lib/cli-engine/lint-result-cache.js b/lib/cli-engine/lint-result-cache.js index fcdf4ca1e5d..75e770f4b28 100644 --- a/lib/cli-engine/lint-result-cache.js +++ b/lib/cli-engine/lint-result-cache.js @@ -8,8 +8,8 @@ // Requirements //----------------------------------------------------------------------------- -const assert = require("assert"); -const fs = require("fs"); +const assert = require("node:assert"); +const fs = require("node:fs"); const fileEntryCache = require("file-entry-cache"); const stringify = require("json-stable-stringify-without-jsonify"); const pkg = require("../../package.json"); diff --git a/lib/cli-engine/load-rules.js b/lib/cli-engine/load-rules.js index 81bab63fab6..a58f954e868 100644 --- a/lib/cli-engine/load-rules.js +++ b/lib/cli-engine/load-rules.js @@ -9,8 +9,8 @@ // Requirements //------------------------------------------------------------------------------ -const fs = require("fs"), - path = require("path"); +const fs = require("node:fs"), + path = require("node:path"); const rulesDirCache = {}; diff --git a/lib/cli.js b/lib/cli.js index 4a60d457a72..d07f81b28f2 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -15,9 +15,9 @@ // Requirements //------------------------------------------------------------------------------ -const fs = require("fs"), - path = require("path"), - { promisify } = require("util"), +const fs = require("node:fs"), + path = require("node:path"), + { promisify } = require("node:util"), { LegacyESLint } = require("./eslint"), { ESLint, shouldUseFlatConfig, locateConfigFileToUse } = require("./eslint/eslint"), createCLIOptions = require("./options"), diff --git a/lib/eslint/eslint-helpers.js b/lib/eslint/eslint-helpers.js index 97c8c99dd8b..65dfbfb83f4 100644 --- a/lib/eslint/eslint-helpers.js +++ b/lib/eslint/eslint-helpers.js @@ -9,8 +9,8 @@ // Requirements //----------------------------------------------------------------------------- -const path = require("path"); -const fs = require("fs"); +const path = require("node:path"); +const fs = require("node:fs"); const fsp = fs.promises; const isGlob = require("is-glob"); const hash = require("../cli-engine/hash"); diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 900353385cf..29fabca6a34 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -9,9 +9,9 @@ // Requirements //------------------------------------------------------------------------------ -const fs = require("fs/promises"); -const { existsSync } = require("fs"); -const path = require("path"); +const fs = require("node:fs/promises"); +const { existsSync } = require("node:fs"); +const path = require("node:path"); const findUp = require("find-up"); const { version } = require("../../package.json"); const { Linter } = require("../linter"); @@ -38,7 +38,7 @@ const { processOptions } = require("./eslint-helpers"); -const { pathToFileURL } = require("url"); +const { pathToFileURL } = require("node:url"); const { FlatConfigArray } = require("../config/flat-config-array"); const LintResultCache = require("../cli-engine/lint-result-cache"); const { Retrier } = require("@humanwhocodes/retry"); diff --git a/lib/eslint/legacy-eslint.js b/lib/eslint/legacy-eslint.js index 9c86163ef63..a781fe9c86c 100644 --- a/lib/eslint/legacy-eslint.js +++ b/lib/eslint/legacy-eslint.js @@ -10,9 +10,9 @@ // Requirements //------------------------------------------------------------------------------ -const path = require("path"); -const fs = require("fs"); -const { promisify } = require("util"); +const path = require("node:path"); +const fs = require("node:fs"); +const { promisify } = require("node:util"); const { CLIEngine, getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); const BuiltinRules = require("../rules"); const { diff --git a/lib/linter/code-path-analysis/code-path-analyzer.js b/lib/linter/code-path-analysis/code-path-analyzer.js index f5f26d0f470..16e9d18aa64 100644 --- a/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/lib/linter/code-path-analysis/code-path-analyzer.js @@ -9,7 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"), +const assert = require("node:assert"), { breakableTypePattern } = require("../../shared/ast-utils"), CodePath = require("./code-path"), CodePathSegment = require("./code-path-segment"), diff --git a/lib/linter/code-path-analysis/fork-context.js b/lib/linter/code-path-analysis/fork-context.js index 695d3bfa7e2..f5783b0d02c 100644 --- a/lib/linter/code-path-analysis/fork-context.js +++ b/lib/linter/code-path-analysis/fork-context.js @@ -13,7 +13,7 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"), +const assert = require("node:assert"), CodePathSegment = require("./code-path-segment"); //------------------------------------------------------------------------------ diff --git a/lib/linter/linter.js b/lib/linter/linter.js index 9154fd703e5..a1e5bf91e28 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -11,7 +11,7 @@ //------------------------------------------------------------------------------ const - path = require("path"), + path = require("node:path"), eslintScope = require("eslint-scope"), evk = require("eslint-visitor-keys"), espree = require("espree"), diff --git a/lib/linter/report-translator.js b/lib/linter/report-translator.js index c4a159a993e..4a44babe1d9 100644 --- a/lib/linter/report-translator.js +++ b/lib/linter/report-translator.js @@ -9,7 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"); +const assert = require("node:assert"); const ruleFixer = require("./rule-fixer"); const { interpolate } = require("./interpolate"); diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index f965fbbe472..a22888c83d3 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -11,9 +11,9 @@ //------------------------------------------------------------------------------ const - assert = require("assert"), - util = require("util"), - path = require("path"), + assert = require("node:assert"), + util = require("node:util"), + path = require("node:path"), equal = require("fast-deep-equal"), Traverser = require("../shared/traverser"), { getRuleOptionsSchema } = require("../config/flat-config-helpers"), diff --git a/lib/shared/runtime-info.js b/lib/shared/runtime-info.js index e172e5b3572..29de6fc8d84 100644 --- a/lib/shared/runtime-info.js +++ b/lib/shared/runtime-info.js @@ -9,9 +9,9 @@ // Requirements //------------------------------------------------------------------------------ -const path = require("path"); +const path = require("node:path"); const spawn = require("cross-spawn"); -const os = require("os"); +const os = require("node:os"); const log = require("../shared/logging"); const packageJson = require("../../package.json"); diff --git a/lib/source-code/token-store/index.js b/lib/source-code/token-store/index.js index d222c87bbf4..8f8d6de769f 100644 --- a/lib/source-code/token-store/index.js +++ b/lib/source-code/token-store/index.js @@ -8,7 +8,7 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"); +const assert = require("node:assert"); const { isCommentToken } = require("@eslint-community/eslint-utils"); const cursors = require("./cursors"); const ForwardTokenCursor = require("./forward-token-cursor"); diff --git a/packages/eslint-config-eslint/nodejs.js b/packages/eslint-config-eslint/nodejs.js index d61c1ee7600..64102c313a5 100644 --- a/packages/eslint-config-eslint/nodejs.js +++ b/packages/eslint-config-eslint/nodejs.js @@ -5,7 +5,8 @@ const recommendedModuleConfig = require("eslint-plugin-n/configs/recommended-mod const sharedRules = { "n/callback-return": ["error", ["cb", "callback", "next"]], - "n/handle-callback-err": ["error", "err"] + "n/handle-callback-err": ["error", "err"], + "n/prefer-node-protocol": "error" }; const cjsConfigs = [ diff --git a/tests/_utils/test-lazy-loading-rules.js b/tests/_utils/test-lazy-loading-rules.js index 66a4a661376..6b51cf862af 100644 --- a/tests/_utils/test-lazy-loading-rules.js +++ b/tests/_utils/test-lazy-loading-rules.js @@ -11,8 +11,8 @@ "use strict"; -const path = require("path"); -const assert = require("assert"); +const path = require("node:path"); +const assert = require("node:assert"); const { addHook } = require("pirates"); const { diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js index 44e5bfb22ed..3197506ff8b 100644 --- a/tests/bin/eslint.js +++ b/tests/bin/eslint.js @@ -9,10 +9,10 @@ // Requirements //----------------------------------------------------------------------------- -const childProcess = require("child_process"); -const fs = require("fs"); +const childProcess = require("node:child_process"); +const fs = require("node:fs"); const assert = require("chai").assert; -const path = require("path"); +const path = require("node:path"); //------------------------------------------------------------------------------ // Data diff --git a/tests/lib/cli-engine/cli-engine.js b/tests/lib/cli-engine/cli-engine.js index f7b30083dca..4491b428cc4 100644 --- a/tests/lib/cli-engine/cli-engine.js +++ b/tests/lib/cli-engine/cli-engine.js @@ -10,11 +10,11 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - path = require("path"), + path = require("node:path"), sinon = require("sinon"), shell = require("shelljs"), - fs = require("fs"), - os = require("os"), + fs = require("node:fs"), + os = require("node:os"), hash = require("../../../lib/cli-engine/hash"), { Legacy: { @@ -787,7 +787,7 @@ describe("CLIEngine", () => { // @scope for @scope/eslint-plugin describe("(plugin shorthand)", () => { - const Module = require("module"); + const Module = require("node:module"); let originalFindPath = null; /* eslint-disable no-underscore-dangle -- Private Node API overriding */ @@ -5123,7 +5123,7 @@ describe("CLIEngine", () => { writeFileSync() {} }, localCLIEngine = proxyquire("../../../lib/cli-engine/cli-engine", { - fs: fakeFS + "node:fs": fakeFS }).CLIEngine, report = { results: [ @@ -5153,7 +5153,7 @@ describe("CLIEngine", () => { writeFileSync() {} }, localCLIEngine = proxyquire("../../../lib/cli-engine/cli-engine", { - fs: fakeFS + "node:fs": fakeFS }).CLIEngine, report = { results: [ diff --git a/tests/lib/cli-engine/file-enumerator.js b/tests/lib/cli-engine/file-enumerator.js index 88dfce6bd7e..0cf78aa7425 100644 --- a/tests/lib/cli-engine/file-enumerator.js +++ b/tests/lib/cli-engine/file-enumerator.js @@ -8,9 +8,9 @@ // Requirements //------------------------------------------------------------------------------ -const fs = require("fs"); -const path = require("path"); -const os = require("os"); +const fs = require("node:fs"); +const path = require("node:path"); +const os = require("node:os"); const { assert } = require("chai"); const sh = require("shelljs"); const sinon = require("sinon"); diff --git a/tests/lib/cli-engine/lint-result-cache.js b/tests/lib/cli-engine/lint-result-cache.js index 41c72308f42..04dd49ca84d 100644 --- a/tests/lib/cli-engine/lint-result-cache.js +++ b/tests/lib/cli-engine/lint-result-cache.js @@ -10,8 +10,8 @@ const assert = require("chai").assert, { CLIEngine } = require("../../../lib/cli-engine"), - fs = require("fs"), - path = require("path"), + fs = require("node:fs"), + path = require("node:path"), proxyquire = require("proxyquire"), sinon = require("sinon"); diff --git a/tests/lib/cli.js b/tests/lib/cli.js index 4c91be3665b..3fcf6fa66a2 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -15,13 +15,13 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - stdAssert = require("assert"), + stdAssert = require("node:assert"), { ESLint, LegacyESLint } = require("../../lib/eslint"), BuiltinRules = require("../../lib/rules"), - path = require("path"), + path = require("node:path"), sinon = require("sinon"), - fs = require("fs"), - os = require("os"), + fs = require("node:fs"), + os = require("node:os"), sh = require("shelljs"); const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 53a43fe19d9..708454fb248 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -10,12 +10,12 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"); -const util = require("util"); -const fs = require("fs"); -const fsp = require("fs/promises"); -const os = require("os"); -const path = require("path"); +const assert = require("node:assert"); +const util = require("node:util"); +const fs = require("node:fs"); +const fsp = require("node:fs/promises"); +const os = require("node:os"); +const path = require("node:path"); const timers = require("node:timers/promises"); const escapeStringRegExp = require("escape-string-regexp"); const fCache = require("file-entry-cache"); @@ -1083,7 +1083,7 @@ describe("ESLint", () => { ); const { ESLint: LocalESLint } = proxyquire("../../../lib/eslint/eslint", { - "fs/promises": { + "node:fs/promises": { readFile: spy, "@noCallThru": false // allows calling other methods of `fs/promises` } @@ -5570,7 +5570,7 @@ describe("ESLint", () => { it("should call fs.writeFile() for each result with output", async () => { const spy = sinon.spy(() => Promise.resolve()); const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", { - "fs/promises": { + "node:fs/promises": { writeFile: spy } }); @@ -5596,7 +5596,7 @@ describe("ESLint", () => { it("should call fs.writeFile() for each result with output and not at all for a result without output", async () => { const spy = sinon.spy(() => Promise.resolve()); const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", { - "fs/promises": { + "node:fs/promises": { writeFile: spy } }); diff --git a/tests/lib/eslint/legacy-eslint.js b/tests/lib/eslint/legacy-eslint.js index 81a08ff8c5f..dd2dbdfc5c0 100644 --- a/tests/lib/eslint/legacy-eslint.js +++ b/tests/lib/eslint/legacy-eslint.js @@ -10,10 +10,10 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"); -const fs = require("fs"); -const os = require("os"); -const path = require("path"); +const assert = require("node:assert"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); const escapeStringRegExp = require("escape-string-regexp"); const fCache = require("file-entry-cache"); const sinon = require("sinon"); @@ -27,7 +27,7 @@ const { const hash = require("../../../lib/cli-engine/hash"); const { unIndent, createCustomTeardown } = require("../../_utils"); const coreRules = require("../../../lib/rules"); -const childProcess = require("child_process"); +const childProcess = require("node:child_process"); //------------------------------------------------------------------------------ // Tests @@ -912,7 +912,7 @@ describe("LegacyESLint", () => { }); describe('plugin shorthand notation ("@scope" for "@scope/eslint-plugin")', () => { - const Module = require("module"); + const Module = require("node:module"); let originalFindPath = null; /* eslint-disable no-underscore-dangle -- Override Node API */ @@ -5836,7 +5836,7 @@ describe("LegacyESLint", () => { }; const spy = fakeFS.writeFile; const { LegacyESLint: localESLint } = proxyquire("../../../lib/eslint/legacy-eslint", { - fs: fakeFS + "node:fs": fakeFS }); const results = [ @@ -5863,7 +5863,7 @@ describe("LegacyESLint", () => { }; const spy = fakeFS.writeFile; const { LegacyESLint: localESLint } = proxyquire("../../../lib/eslint/legacy-eslint", { - fs: fakeFS + "node:fs": fakeFS }); const results = [ { diff --git a/tests/lib/linter/code-path-analysis/code-path-analyzer.js b/tests/lib/linter/code-path-analysis/code-path-analyzer.js index c85ef35638b..dde4bef4198 100644 --- a/tests/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/tests/lib/linter/code-path-analysis/code-path-analyzer.js @@ -9,9 +9,9 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"), - fs = require("fs"), - path = require("path"), +const assert = require("node:assert"), + fs = require("node:fs"), + path = require("node:path"), vk = require("eslint-visitor-keys"), { Linter } = require("../../../../lib/linter"), EventGeneratorTester = require("../../../../tools/internal-testers/event-generator-tester"), diff --git a/tests/lib/linter/code-path-analysis/code-path.js b/tests/lib/linter/code-path-analysis/code-path.js index f7613cd99ee..aefc913aa45 100644 --- a/tests/lib/linter/code-path-analysis/code-path.js +++ b/tests/lib/linter/code-path-analysis/code-path.js @@ -9,7 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"), +const assert = require("node:assert"), { Linter } = require("../../../../lib/linter"); const linter = new Linter({ configType: "eslintrc" }); diff --git a/tests/lib/linter/node-event-generator.js b/tests/lib/linter/node-event-generator.js index 2719bf30e30..004ba64452e 100644 --- a/tests/lib/linter/node-event-generator.js +++ b/tests/lib/linter/node-event-generator.js @@ -8,7 +8,7 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"), +const assert = require("node:assert"), sinon = require("sinon"), espree = require("espree"), vk = require("eslint-visitor-keys"), diff --git a/tests/lib/rule-tester/no-test-runners.js b/tests/lib/rule-tester/no-test-runners.js index 8e5785cef98..4c59dd0ee15 100644 --- a/tests/lib/rule-tester/no-test-runners.js +++ b/tests/lib/rule-tester/no-test-runners.js @@ -5,7 +5,7 @@ */ "use strict"; -const assert = require("assert"); +const assert = require("node:assert"); const { RuleTester } = require("../../../lib/rule-tester"); const tmpIt = it; const tmpDescribe = describe; diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js index 4cb34b96dfe..389092673fc 100644 --- a/tests/lib/rule-tester/rule-tester.js +++ b/tests/lib/rule-tester/rule-tester.js @@ -8,10 +8,10 @@ // Requirements //------------------------------------------------------------------------------ const sinon = require("sinon"), - EventEmitter = require("events"), + EventEmitter = require("node:events"), { RuleTester } = require("../../../lib/rule-tester"), assert = require("chai").assert, - nodeAssert = require("assert"); + nodeAssert = require("node:assert"); //----------------------------------------------------------------------------- // Helpers diff --git a/tests/lib/rules/array-bracket-spacing.js b/tests/lib/rules/array-bracket-spacing.js index b93595fbe1e..003e7a6f2dd 100644 --- a/tests/lib/rules/array-bracket-spacing.js +++ b/tests/lib/rules/array-bracket-spacing.js @@ -8,7 +8,7 @@ // Requirements //------------------------------------------------------------------------------ -const path = require("path"), +const path = require("node:path"), rule = require("../../../lib/rules/array-bracket-spacing"), RuleTester = require("../../../lib/rule-tester/rule-tester"); diff --git a/tests/lib/rules/comma-dangle.js b/tests/lib/rules/comma-dangle.js index 19c963b3296..9f0140f669c 100644 --- a/tests/lib/rules/comma-dangle.js +++ b/tests/lib/rules/comma-dangle.js @@ -9,7 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -const path = require("path"), +const path = require("node:path"), { unIndent } = require("../../_utils"), rule = require("../../../lib/rules/comma-dangle"), RuleTester = require("../../../lib/rule-tester/rule-tester"); diff --git a/tests/lib/rules/indent-legacy.js b/tests/lib/rules/indent-legacy.js index 7a1deaa1c1f..6d3ec2eea8b 100644 --- a/tests/lib/rules/indent-legacy.js +++ b/tests/lib/rules/indent-legacy.js @@ -11,8 +11,8 @@ const rule = require("../../../lib/rules/indent-legacy"), RuleTester = require("../../../lib/rule-tester/rule-tester"); -const fs = require("fs"); -const path = require("path"); +const fs = require("node:fs"); +const path = require("node:path"); //------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js index 044772945e7..8afbcc0ec9f 100644 --- a/tests/lib/rules/indent.js +++ b/tests/lib/rules/indent.js @@ -11,8 +11,8 @@ const rule = require("../../../lib/rules/indent"), RuleTester = require("../../../lib/rule-tester/rule-tester"); -const fs = require("fs"); -const path = require("path"); +const fs = require("node:fs"); +const path = require("node:path"); //------------------------------------------------------------------------------ // Helpers diff --git a/tests/lib/rules/object-curly-newline.js b/tests/lib/rules/object-curly-newline.js index 28f5306a7b4..fc249d99436 100644 --- a/tests/lib/rules/object-curly-newline.js +++ b/tests/lib/rules/object-curly-newline.js @@ -9,7 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -const resolvePath = require("path").resolve, +const resolvePath = require("node:path").resolve, rule = require("../../../lib/rules/object-curly-newline"), RuleTester = require("../../../lib/rule-tester/rule-tester"); diff --git a/tests/lib/rules/object-curly-spacing.js b/tests/lib/rules/object-curly-spacing.js index c0755dd62e2..44159555854 100644 --- a/tests/lib/rules/object-curly-spacing.js +++ b/tests/lib/rules/object-curly-spacing.js @@ -8,7 +8,7 @@ // Requirements //------------------------------------------------------------------------------ -const resolvePath = require("path").resolve, +const resolvePath = require("node:path").resolve, rule = require("../../../lib/rules/object-curly-spacing"), RuleTester = require("../../../lib/rule-tester/rule-tester"); diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js index 7700f2ce7e9..88c1e4a7b6c 100644 --- a/tests/lib/rules/utils/ast-utils.js +++ b/tests/lib/rules/utils/ast-utils.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - util = require("util"), + util = require("node:util"), espree = require("espree"), astUtils = require("../../../../lib/rules/utils/ast-utils"), { Linter } = require("../../../../lib/linter"), diff --git a/tests/lib/shared/runtime-info.js b/tests/lib/shared/runtime-info.js index ac549c0d001..d7334eff20b 100644 --- a/tests/lib/shared/runtime-info.js +++ b/tests/lib/shared/runtime-info.js @@ -12,7 +12,7 @@ const assert = require("chai").assert; const sinon = require("sinon"); const spawn = require("cross-spawn"); -const os = require("os"); +const os = require("node:os"); const { unIndent } = require("../../_utils"); const RuntimeInfo = require("../../../lib/shared/runtime-info"); const log = require("../../../lib/shared/logging"); diff --git a/tests/lib/source-code/source-code.js b/tests/lib/source-code/source-code.js index 555acfadca7..37913288646 100644 --- a/tests/lib/source-code/source-code.js +++ b/tests/lib/source-code/source-code.js @@ -8,8 +8,8 @@ // Requirements //------------------------------------------------------------------------------ -const fs = require("fs"), - path = require("path"), +const fs = require("node:fs"), + path = require("node:path"), assert = require("chai").assert, espree = require("espree"), eslintScope = require("eslint-scope"), diff --git a/tests/tools/check-rule-examples.js b/tests/tools/check-rule-examples.js index 442d0bedc1f..04d0331b4ca 100644 --- a/tests/tools/check-rule-examples.js +++ b/tests/tools/check-rule-examples.js @@ -4,9 +4,9 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"); -const { execFile } = require("child_process"); -const { promisify } = require("util"); +const assert = require("node:assert"); +const { execFile } = require("node:child_process"); +const { promisify } = require("node:util"); const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version"); //------------------------------------------------------------------------------ diff --git a/tools/check-emfile-handling.js b/tools/check-emfile-handling.js index be0fd262ebc..a7ed695f3de 100644 --- a/tools/check-emfile-handling.js +++ b/tools/check-emfile-handling.js @@ -9,10 +9,10 @@ // Requirements //------------------------------------------------------------------------------ -const fs = require("fs"); -const { readFile } = require("fs/promises"); -const { execSync } = require("child_process"); -const os = require("os"); +const fs = require("node:fs"); +const { readFile } = require("node:fs/promises"); +const { execSync } = require("node:child_process"); +const os = require("node:os"); //------------------------------------------------------------------------------ // Helpers diff --git a/tools/check-rule-examples.js b/tools/check-rule-examples.js index 224a8c2b42b..69708e758d4 100644 --- a/tools/check-rule-examples.js +++ b/tools/check-rule-examples.js @@ -5,7 +5,7 @@ //------------------------------------------------------------------------------ const { parse } = require("espree"); -const { readFile } = require("fs").promises; +const { readFile } = require("node:fs").promises; const { glob } = require("glob"); const matter = require("gray-matter"); const markdownIt = require("markdown-it"); diff --git a/tools/code-sample-minimizer.js b/tools/code-sample-minimizer.js index e1eb4995cc0..4b354680fa2 100644 --- a/tools/code-sample-minimizer.js +++ b/tools/code-sample-minimizer.js @@ -3,7 +3,7 @@ const evk = require("eslint-visitor-keys"); const recast = require("recast"); const espree = require("espree"); -const assert = require("assert"); +const assert = require("node:assert"); /** * Determines whether an AST node could be an expression, based on the type diff --git a/tools/eslint-fuzzer.js b/tools/eslint-fuzzer.js index d951ad3377c..cb49a7ebd6a 100644 --- a/tools/eslint-fuzzer.js +++ b/tools/eslint-fuzzer.js @@ -9,7 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"); +const assert = require("node:assert"); const eslump = require("eslump"); const espree = require("espree"); const SourceCodeFixer = require("../lib/linter/source-code-fixer"); diff --git a/tools/fetch-docs-links.js b/tools/fetch-docs-links.js index 9cdd05aab1a..930a585cbf6 100644 --- a/tools/fetch-docs-links.js +++ b/tools/fetch-docs-links.js @@ -27,8 +27,8 @@ const metascraper = require("metascraper")([ require("metascraper-description")() ]); const got = require("got"); -const path = require("path"); -const fs = require("fs").promises; +const path = require("node:path"); +const fs = require("node:fs").promises; const glob = require("fast-glob"); //----------------------------------------------------------------------------- diff --git a/tools/internal-rules/index.js b/tools/internal-rules/index.js index a3a3cd19971..1b40f7763df 100644 --- a/tools/internal-rules/index.js +++ b/tools/internal-rules/index.js @@ -1,7 +1,7 @@ "use strict"; -const fs = require("fs"); -const path = require("path"); +const fs = require("node:fs"); +const path = require("node:path"); module.exports = { rules: Object.assign( diff --git a/tools/internal-testers/event-generator-tester.js b/tools/internal-testers/event-generator-tester.js index 46cbafb0ffe..454c221e56a 100644 --- a/tools/internal-testers/event-generator-tester.js +++ b/tools/internal-testers/event-generator-tester.js @@ -10,7 +10,7 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"); +const assert = require("node:assert"); //------------------------------------------------------------------------------ // Public Interface diff --git a/tools/update-eslint-all.js b/tools/update-eslint-all.js index f6a802de1b7..e6cf4af1638 100644 --- a/tools/update-eslint-all.js +++ b/tools/update-eslint-all.js @@ -9,7 +9,7 @@ // Requirements //----------------------------------------------------------------------------- -const fs = require("fs"); +const fs = require("node:fs"); const builtInRules = require("../lib/rules"); //------------------------------------------------------------------------------ diff --git a/tools/update-readme.js b/tools/update-readme.js index 167db70b379..8918ecbc1cc 100644 --- a/tools/update-readme.js +++ b/tools/update-readme.js @@ -13,7 +13,7 @@ // Requirements //----------------------------------------------------------------------------- -const fs = require("fs"); +const fs = require("node:fs"); const { stripIndents } = require("common-tags"); const ejs = require("ejs"); const got = require("got"); diff --git a/wdio.conf.js b/wdio.conf.js index f32d757133e..8703052e88f 100644 --- a/wdio.conf.js +++ b/wdio.conf.js @@ -1,6 +1,6 @@ "use strict"; -const path = require("path"); +const path = require("node:path"); const commonjs = require("vite-plugin-commonjs").default; exports.config = { diff --git a/webpack.config.js b/webpack.config.js index a22c99bf861..fb426465ef1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,6 @@ "use strict"; + +const webpack = require("webpack"); const NodePolyfillPlugin = require("node-polyfill-webpack-plugin"); /** @type {import("webpack").Configuration} */ @@ -44,6 +46,12 @@ module.exports = { ] }, plugins: [ + new webpack.NormalModuleReplacementPlugin( + /^node:/u, + resource => { + resource.request = resource.request.replace(/^node:/u, ""); + } + ), new NodePolyfillPlugin() ], resolve: { From 05ef92dd15949014c0735125c89b7bd70dec58c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Thu, 9 May 2024 22:53:15 +0800 Subject: [PATCH 130/166] feat: deprecate `multiline-comment-style` & `line-comment-position` (#18435) * feat: deprecate multiline-comment-style & line-comment-position fixes #17681 * fix: add @deprecated tag * fix: deprecated tags * docs: add rule links to the stylistic plugin --- docs/src/_data/rules.json | 26 +++++++++++------------ docs/src/_data/rules_meta.json | 4 ++++ docs/src/rules/line-comment-position.md | 1 + docs/src/rules/multiline-comment-style.md | 2 +- lib/rules/line-comment-position.js | 3 +++ lib/rules/multiline-comment-style.js | 4 +++- packages/js/src/configs/eslint-all.js | 2 -- 7 files changed, 24 insertions(+), 18 deletions(-) diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 0be8301486e..646a4751e46 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -633,13 +633,6 @@ "fixable": false, "hasSuggestions": false }, - { - "name": "multiline-comment-style", - "description": "Enforce a particular style for multiline comments", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, { "name": "new-cap", "description": "Require constructor names to begin with a capital letter", @@ -1384,13 +1377,6 @@ } ], "layout": [ - { - "name": "line-comment-position", - "description": "Enforce position of line comments", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, { "name": "unicode-bom", "description": "Require or disallow Unicode byte order mark (BOM)", @@ -1567,6 +1553,12 @@ "fixable": true, "hasSuggestions": false }, + { + "name": "line-comment-position", + "replacedBy": [], + "fixable": false, + "hasSuggestions": false + }, { "name": "linebreak-style", "replacedBy": [], @@ -1605,6 +1597,12 @@ "fixable": false, "hasSuggestions": false }, + { + "name": "multiline-comment-style", + "replacedBy": [], + "fixable": true, + "hasSuggestions": false + }, { "name": "multiline-ternary", "replacedBy": [], diff --git a/docs/src/_data/rules_meta.json b/docs/src/_data/rules_meta.json index 67210b73fbb..63a294a32c2 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -529,6 +529,8 @@ "fixable": "whitespace" }, "line-comment-position": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce position of line comments", @@ -669,6 +671,8 @@ } }, "multiline-comment-style": { + "deprecated": true, + "replacedBy": [], "type": "suggestion", "docs": { "description": "Enforce a particular style for multiline comments", diff --git a/docs/src/rules/line-comment-position.md b/docs/src/rules/line-comment-position.md index d032ecca8e9..e43065a59ae 100644 --- a/docs/src/rules/line-comment-position.md +++ b/docs/src/rules/line-comment-position.md @@ -3,6 +3,7 @@ title: line-comment-position rule_type: layout --- +This rule was **deprecated** in ESLint v9.3.0. Please use the [corresponding rule](https://eslint.style/rules/js/line-comment-position) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Line comments can be positioned above or beside code. This rule helps teams maintain a consistent style. diff --git a/docs/src/rules/multiline-comment-style.md b/docs/src/rules/multiline-comment-style.md index 28e6648ad03..cb4a847504f 100644 --- a/docs/src/rules/multiline-comment-style.md +++ b/docs/src/rules/multiline-comment-style.md @@ -3,7 +3,7 @@ title: multiline-comment-style rule_type: suggestion --- - +This rule was **deprecated** in ESLint v9.3.0. Please use the [corresponding rule](https://eslint.style/rules/js/multiline-comment-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Many style guides require a particular style for comments that span multiple lines. For example, some style guides prefer the use of a single block comment for multiline comments, whereas other style guides prefer consecutive line comments. diff --git a/lib/rules/line-comment-position.js b/lib/rules/line-comment-position.js index ce7e35da57c..e7a7788ca9f 100644 --- a/lib/rules/line-comment-position.js +++ b/lib/rules/line-comment-position.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to enforce the position of line comments * @author Alberto Rodríguez + * @deprecated in ESLint v9.3.0 */ "use strict"; @@ -13,6 +14,8 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/multiline-comment-style.js b/lib/rules/multiline-comment-style.js index 19caa8384af..098e054b9f3 100644 --- a/lib/rules/multiline-comment-style.js +++ b/lib/rules/multiline-comment-style.js @@ -1,6 +1,7 @@ /** * @fileoverview enforce a particular style for multiline comments * @author Teddy Katz + * @deprecated in ESLint v9.3.0 */ "use strict"; @@ -13,8 +14,9 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "suggestion", - docs: { description: "Enforce a particular style for multiline comments", recommended: false, diff --git a/packages/js/src/configs/eslint-all.js b/packages/js/src/configs/eslint-all.js index e0753003ed2..e7f4e0e3a11 100644 --- a/packages/js/src/configs/eslint-all.js +++ b/packages/js/src/configs/eslint-all.js @@ -44,7 +44,6 @@ module.exports = Object.freeze({ "id-length": "error", "id-match": "error", "init-declarations": "error", - "line-comment-position": "error", "logical-assignment-operators": "error", "max-classes-per-file": "error", "max-depth": "error", @@ -53,7 +52,6 @@ module.exports = Object.freeze({ "max-nested-callbacks": "error", "max-params": "error", "max-statements": "error", - "multiline-comment-style": "error", "new-cap": "error", "no-alert": "error", "no-array-constructor": "error", From 70651968beb0f907c9689c2477721c0b991acc4a Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Fri, 10 May 2024 08:07:40 +0000 Subject: [PATCH 131/166] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e5a0806f1f..5e7268339d1 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

JetBrains Liftoff American Express Workleap

Bronze Sponsors

-

notion Anagram Solver Icons8 Discord Transloadit Ignition Nx HeroCoders Nextbase Starter Kit

+

notion Anagram Solver Icons8 Discord Ignition Nx HeroCoders Nextbase Starter Kit

## Technology Sponsors From 069aa680c78b8516b9a1b568519f1d01e74fb2a2 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 10 May 2024 16:25:53 +0200 Subject: [PATCH 132/166] feat: add option `allowEscape` to `no-misleading-character-class` rule (#18208) * feat: add option `allowEscape` to `no-misleading-character-class` rule * fix and clarify docs * add `incorrect` / `correct` tags * change a test case * don't allow unescaped combining marks --- .../rules/no-misleading-character-class.md | 53 ++++++- lib/rules/no-misleading-character-class.js | 141 ++++++++++++++---- .../rules/no-misleading-character-class.js | 128 +++++++++++++++- 3 files changed, 286 insertions(+), 36 deletions(-) diff --git a/docs/src/rules/no-misleading-character-class.md b/docs/src/rules/no-misleading-character-class.md index 792760f2641..d3134a83737 100644 --- a/docs/src/rules/no-misleading-character-class.md +++ b/docs/src/rules/no-misleading-character-class.md @@ -7,10 +7,10 @@ rule_type: problem -Unicode includes the characters which are made with multiple code points. -RegExp character class syntax (`/[abc]/`) cannot handle characters which are made by multiple code points as a character; those characters will be dissolved to each code point. For example, `❇️` is made by `❇` (`U+2747`) and VARIATION SELECTOR-16 (`U+FE0F`). If this character is in RegExp character class, it will match to either `❇` (`U+2747`) or VARIATION SELECTOR-16 (`U+FE0F`) rather than `❇️`. +Unicode includes characters which are made by multiple code points. +RegExp character class syntax (`/[abc]/`) cannot handle characters which are made by multiple code points as a character; those characters will be dissolved to each code point. For example, `❇️` is made by `❇` (`U+2747`) and VARIATION SELECTOR-16 (`U+FE0F`). If this character is in a RegExp character class, it will match either `❇` (`U+2747`) or VARIATION SELECTOR-16 (`U+FE0F`) rather than `❇️`. -This rule reports the regular expressions which include multiple code point characters in character class syntax. This rule considers the following characters as multiple code point characters. +This rule reports regular expressions which include multiple code point characters in character class syntax. This rule considers the following characters as multiple code point characters. **A character with combining characters:** @@ -51,7 +51,7 @@ The combining characters are characters which belong to one of `Mc`, `Me`, and ` ## Rule Details -This rule reports the regular expressions which include multiple code point characters in character class syntax. +This rule reports regular expressions which include multiple code point characters in character class syntax. Examples of **incorrect** code for this rule: @@ -66,6 +66,7 @@ Examples of **incorrect** code for this rule: /^[🇯🇵]$/u; /^[👨‍👩‍👦]$/u; /^[👍]$/; +new RegExp("[🎵]"); ``` ::: @@ -80,6 +81,50 @@ Examples of **correct** code for this rule: /^[abc]$/; /^[👍]$/u; /^[\q{👶🏻}]$/v; +new RegExp("^[]$"); +new RegExp(`[Á-${z}]`, "u"); // variable pattern +``` + +::: + +## Options + +This rule has an object option: + +* `"allowEscape"`: When set to `true`, the rule allows any grouping of code points inside a character class as long as they are written using escape sequences. This option only has effect on regular expression literals and on regular expressions created with the `RegExp` constructor with a literal argument as a pattern. + +### allowEscape + +Examples of **incorrect** code for this rule with the `{ "allowEscape": true }` option: + +::: incorrect + +```js +/* eslint no-misleading-character-class: ["error", { "allowEscape": true }] */ + +/[\👍]/; // backslash can be omitted + +new RegExp("[\ud83d" + "\udc4d]"); + +const pattern = "[\ud83d\udc4d]"; +new RegExp(pattern); +``` + +::: + +Examples of **correct** code for this rule with the `{ "allowEscape": true }` option: + +::: correct + +```js +/* eslint no-misleading-character-class: ["error", { "allowEscape": true }] */ + +/[\ud83d\udc4d]/; +/[\u00B7\u0300-\u036F]/u; +/[👨\u200d👩]/u; +new RegExp("[\x41\u0301]"); +new RegExp(`[\u{1F1EF}\u{1F1F5}]`, "u"); +new RegExp("[\\u{1F1EF}\\u{1F1F5}]", "u"); ``` ::: diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index fa50e226f97..d10e0812a83 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -85,12 +85,10 @@ function isUnicodeCodePointEscape(char) { const findCharacterSequences = { *surrogatePairWithoutUFlag(chars) { for (const [index, char] of chars.entries()) { - if (index === 0) { - continue; - } const previous = chars[index - 1]; if ( + previous && char && isSurrogatePair(previous.value, char.value) && !isUnicodeCodePointEscape(previous) && !isUnicodeCodePointEscape(char) @@ -102,12 +100,10 @@ const findCharacterSequences = { *surrogatePair(chars) { for (const [index, char] of chars.entries()) { - if (index === 0) { - continue; - } const previous = chars[index - 1]; if ( + previous && char && isSurrogatePair(previous.value, char.value) && ( isUnicodeCodePointEscape(previous) || @@ -119,14 +115,17 @@ const findCharacterSequences = { } }, - *combiningClass(chars) { + *combiningClass(chars, unfilteredChars) { + + /* + * When `allowEscape` is `true`, a combined character should only be allowed if the combining mark appears as an escape sequence. + * This means that the base character should be considered even if it's escaped. + */ for (const [index, char] of chars.entries()) { - if (index === 0) { - continue; - } - const previous = chars[index - 1]; + const previous = unfilteredChars[index - 1]; if ( + previous && char && isCombiningCharacter(char.value) && !isCombiningCharacter(previous.value) ) { @@ -137,12 +136,10 @@ const findCharacterSequences = { *emojiModifier(chars) { for (const [index, char] of chars.entries()) { - if (index === 0) { - continue; - } const previous = chars[index - 1]; if ( + previous && char && isEmojiModifier(char.value) && !isEmojiModifier(previous.value) ) { @@ -153,12 +150,10 @@ const findCharacterSequences = { *regionalIndicatorSymbol(chars) { for (const [index, char] of chars.entries()) { - if (index === 0) { - continue; - } const previous = chars[index - 1]; if ( + previous && char && isRegionalIndicatorSymbol(char.value) && isRegionalIndicatorSymbol(previous.value) ) { @@ -171,17 +166,18 @@ const findCharacterSequences = { let sequence = null; for (const [index, char] of chars.entries()) { - if (index === 0 || index === chars.length - 1) { - continue; - } + const previous = chars[index - 1]; + const next = chars[index + 1]; + if ( + previous && char && next && char.value === 0x200d && - chars[index - 1].value !== 0x200d && - chars[index + 1].value !== 0x200d + previous.value !== 0x200d && + next.value !== 0x200d ) { if (sequence) { - if (sequence.at(-1) === chars[index - 1]) { - sequence.push(char, chars[index + 1]); // append to the sequence + if (sequence.at(-1) === previous) { + sequence.push(char, next); // append to the sequence } else { yield sequence; sequence = chars.slice(index - 1, index + 2); @@ -227,6 +223,41 @@ function getStaticValueOrRegex(node, initialScope) { return staticValue; } +/** + * Checks whether a specified regexpp character is represented as an acceptable escape sequence. + * This function requires the source text of the character to be known. + * @param {Character} char Character to check. + * @param {string} charSource Source text of the character to check. + * @returns {boolean} Whether the specified regexpp character is represented as an acceptable escape sequence. + */ +function checkForAcceptableEscape(char, charSource) { + if (!charSource.startsWith("\\")) { + return false; + } + const match = /(?<=^\\+).$/su.exec(charSource); + + return match?.[0] !== String.fromCodePoint(char.value); +} + +/** + * Checks whether a specified regexpp character is represented as an acceptable escape sequence. + * This function works with characters that are produced by a string or template literal. + * It requires the source text and the CodeUnit list of the literal to be known. + * @param {Character} char Character to check. + * @param {string} nodeSource Source text of the string or template literal that produces the character. + * @param {CodeUnit[]} codeUnits List of CodeUnit objects of the literal that produces the character. + * @returns {boolean} Whether the specified regexpp character is represented as an acceptable escape sequence. + */ +function checkForAcceptableEscapeInString(char, nodeSource, codeUnits) { + const firstIndex = char.start; + const lastIndex = char.end - 1; + const start = codeUnits[firstIndex].start; + const end = codeUnits[lastIndex].end; + const charSource = nodeSource.slice(start, end); + + return checkForAcceptableEscape(char, charSource); +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -244,7 +275,18 @@ module.exports = { hasSuggestions: true, - schema: [], + schema: [ + { + type: "object", + properties: { + allowEscape: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], messages: { surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.", @@ -257,6 +299,7 @@ module.exports = { } }, create(context) { + const allowEscape = context.options[0]?.allowEscape; const sourceCode = context.sourceCode; const parser = new RegExpParser(); const checkedPatternNodes = new Set(); @@ -288,24 +331,62 @@ module.exports = { return; } + let codeUnits = null; + + /** + * Checks whether a specified regexpp character is represented as an acceptable escape sequence. + * For the purposes of this rule, an escape sequence is considered acceptable if it consists of one or more backslashes followed by the character being escaped. + * @param {Character} char Character to check. + * @returns {boolean} Whether the specified regexpp character is represented as an acceptable escape sequence. + */ + function isAcceptableEscapeSequence(char) { + if (node.type === "Literal" && node.regex) { + return checkForAcceptableEscape(char, char.raw); + } + if (node.type === "Literal" && typeof node.value === "string") { + const nodeSource = node.raw; + + codeUnits ??= parseStringLiteral(nodeSource); + + return checkForAcceptableEscapeInString(char, nodeSource, codeUnits); + } + if (astUtils.isStaticTemplateLiteral(node)) { + const nodeSource = sourceCode.getText(node); + + codeUnits ??= parseTemplateToken(nodeSource); + + return checkForAcceptableEscapeInString(char, nodeSource, codeUnits); + } + return false; + } + const foundKindMatches = new Map(); visitRegExpAST(patternNode, { onCharacterClassEnter(ccNode) { - for (const chars of iterateCharacterSequence(ccNode.elements)) { + for (const unfilteredChars of iterateCharacterSequence(ccNode.elements)) { + let chars; + + if (allowEscape) { + + // Replace escape sequences with null to avoid having them flagged. + chars = unfilteredChars.map(char => (isAcceptableEscapeSequence(char) ? null : char)); + } else { + chars = unfilteredChars; + } for (const kind of kinds) { + const matches = findCharacterSequences[kind](chars, unfilteredChars); + if (foundKindMatches.has(kind)) { - foundKindMatches.get(kind).push(...findCharacterSequences[kind](chars)); + foundKindMatches.get(kind).push(...matches); } else { - foundKindMatches.set(kind, [...findCharacterSequences[kind](chars)]); + foundKindMatches.set(kind, [...matches]); } } } } }); - let codeUnits = null; - /** * Finds the report loc(s) for a range of matches. * Only literals and expression-less templates generate granular errors. diff --git a/tests/lib/rules/no-misleading-character-class.js b/tests/lib/rules/no-misleading-character-class.js index 6a276ae12c2..7316cb2acd5 100644 --- a/tests/lib/rules/no-misleading-character-class.js +++ b/tests/lib/rules/no-misleading-character-class.js @@ -94,7 +94,57 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = /^[\q{👶🏻}]$/v`, languageOptions: { ecmaVersion: 2024 } }, { code: String.raw`var r = /[🇯\q{abc}🇵]/v`, languageOptions: { ecmaVersion: 2024 } }, { code: "var r = /[🇯[A]🇵]/v", languageOptions: { ecmaVersion: 2024 } }, - { code: "var r = /[🇯[A--B]🇵]/v", languageOptions: { ecmaVersion: 2024 } } + { code: "var r = /[🇯[A--B]🇵]/v", languageOptions: { ecmaVersion: 2024 } }, + + // allowEscape + { + code: String.raw`/[\ud83d\udc4d]/`, + options: [{ allowEscape: true }] + }, + { + code: '/[\ud83d\\udc4d]/u // U+D83D + Backslash + "udc4d"', + options: [{ allowEscape: true }] + }, + { + code: String.raw`/[A\u0301]/`, + options: [{ allowEscape: true }] + }, + { + code: String.raw`/[👶\u{1f3fb}]/u`, + options: [{ allowEscape: true }] + }, + { + code: String.raw`/[\u{1F1EF}\u{1F1F5}]/u`, + options: [{ allowEscape: true }] + }, + { + code: String.raw`/[👨\u200d👩\u200d👦]/u`, + options: [{ allowEscape: true }] + }, + { + code: String.raw`/[\u00B7\u0300-\u036F]/u`, + options: [{ allowEscape: true }] + }, + { + code: String.raw`/[\n\u0305]/`, + options: [{ allowEscape: true }] + }, + { + code: String.raw`RegExp("[\uD83D\uDC4D]")`, + options: [{ allowEscape: true }] + }, + { + code: String.raw`RegExp("[A\u0301]")`, + options: [{ allowEscape: true }] + }, + { + code: String.raw`RegExp("[\x41\\u0301]")`, + options: [{ allowEscape: true }] + }, + { + code: 'RegExp(`[\\uD83D\\uDC4D]`) // Backslash + "uD83D" + Backslash + "uDC4D"', + options: [{ allowEscape: true }] + } ], invalid: [ @@ -1589,7 +1639,7 @@ ruleTester.run("no-misleading-character-class", rule, { messageId: "surrogatePairWithoutUFlag", suggestions: [{ messageId: "suggestUnicodeFlag", output: "new RegExp(/^[👍]$/v, 'u')" }] }] - } + }, /* * This test case has been disabled because of a limitation in Node.js 18, see https://github.com/eslint/eslint/pull/18082#discussion_r1506142421. @@ -1613,6 +1663,80 @@ ruleTester.run("no-misleading-character-class", rule, { * } */ + // allowEscape + + { + code: String.raw`/[Á]/`, + options: [{ allowEscape: false }], + errors: [{ messageId: "combiningClass" }] + }, + { + code: String.raw`/[Á]/`, + options: [{ allowEscape: void 0 }], + errors: [{ messageId: "combiningClass" }] + }, + { + code: String.raw`/[\\̶]/`, // Backslash + Backslash + Combining Long Stroke Overlay + options: [{ allowEscape: true }], + errors: [{ messageId: "combiningClass" }] + }, + { + code: String.raw`/[\n̅]/`, + options: [{ allowEscape: true }], + errors: [{ messageId: "combiningClass" }] + }, + { + code: String.raw`/[\👍]/`, + options: [{ allowEscape: true }], + errors: [{ messageId: "surrogatePairWithoutUFlag" }] + }, + { + code: String.raw`RegExp('[\è]')`, // Backslash + Latin Small Letter E + Combining Grave Accent + options: [{ allowEscape: true }], + errors: [{ messageId: "combiningClass" }] + }, + { + code: String.raw`RegExp('[\👍]')`, + options: [{ allowEscape: true }], + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ + messageId: "suggestUnicodeFlag", + output: String.raw`RegExp('[\👍]', "u")` + }] + }] + }, + { + code: String.raw`RegExp('[\\👍]')`, + options: [{ allowEscape: true }], + errors: [{ messageId: "surrogatePairWithoutUFlag" }] + }, + { + code: String.raw`RegExp('[\❇️]')`, + options: [{ allowEscape: true }], + errors: [{ messageId: "combiningClass" }] + }, + { + code: "RegExp(`[\\👍]`) // Backslash + U+D83D + U+DC4D", + options: [{ allowEscape: true }], + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ + messageId: "suggestUnicodeFlag", + output: 'RegExp(`[\\👍]`, "u") // Backslash + U+D83D + U+DC4D' + }] + }] + }, + { + /* + * In this case the rule can determine the value of `pattern` statically but has no information about the source, + * so it doesn't know that escape sequences were used. This is a limitation with our tools. + */ + code: String.raw`const pattern = "[\x41\u0301]"; RegExp(pattern);`, + options: [{ allowEscape: true }], + errors: [{ messageId: "combiningClass" }] + } + /* eslint-enable lines-around-comment, internal-rules/multiline-comment-style -- re-enable rule */ ] From 1db9bae944b69945e3b05f76754cced16ae83838 Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Sun, 12 May 2024 00:21:09 -0700 Subject: [PATCH 133/166] docs: Fix typos (#18443) --- docs/src/contribute/development-environment.md | 2 +- docs/src/contribute/index.md | 2 +- docs/src/maintain/overview.md | 2 +- docs/src/use/configure/migration-guide.md | 2 +- lib/rules/no-constant-binary-expression.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/contribute/development-environment.md b/docs/src/contribute/development-environment.md index b84af4e3274..b7e4b15f009 100644 --- a/docs/src/contribute/development-environment.md +++ b/docs/src/contribute/development-environment.md @@ -22,7 +22,7 @@ Go to and click the "Fork" button. Follow the Clone your fork: ```shell -git clone https://github.com//eslint +git clone https://github.com//eslint ``` Once you've cloned the repository, run `npm install` to get all the necessary dependencies: diff --git a/docs/src/contribute/index.md b/docs/src/contribute/index.md index 3b0a6d8f5c5..8bf55b30d30 100644 --- a/docs/src/contribute/index.md +++ b/docs/src/contribute/index.md @@ -60,7 +60,7 @@ Describes the governance policy for ESLint, including the rights and privileges ## [Report a Security Vulnerability](report-security-vulnerability) -To report a security vulnerability in ESLint, please create an advisory on Github. +To report a security vulnerability in ESLint, please create an advisory on GitHub. ## Sign the CLA diff --git a/docs/src/maintain/overview.md b/docs/src/maintain/overview.md index 362637a8a3c..48d476e84b5 100644 --- a/docs/src/maintain/overview.md +++ b/docs/src/maintain/overview.md @@ -27,7 +27,7 @@ The OpenJS Foundation does not participate in the day-to-day functioning of ESLi ESLint is funded through several sources, including: * [**Open Collective**](https://opencollective.com/eslint): A platform for financing open source projects. -* [**GitHub Sponsors**](https://github.com/sponsors/eslint): A platform for funding open source projects associated with Github. +* [**GitHub Sponsors**](https://github.com/sponsors/eslint): A platform for funding open source projects associated with GitHub. * [**Tidelift**](https://tidelift.com/subscription/pkg/npm-eslint): A subscription service that lets enterprises manage and fund the open source projects that their organization uses. * [**Carbon Ads**](https://www.carbonads.net/open-source): Developer-centric advertising provider used on [eslint.org](https://eslint.org/). * [**Stackaid.us**](https://simulation.stackaid.us/github/eslint/eslint): Tool that developers can use to allocate funding to the open source projects they use. diff --git a/docs/src/use/configure/migration-guide.md b/docs/src/use/configure/migration-guide.md index dd09e9b15d8..e12bfd0b427 100644 --- a/docs/src/use/configure/migration-guide.md +++ b/docs/src/use/configure/migration-guide.md @@ -668,7 +668,7 @@ The following changes have been made from the eslintrc to the flat config file f You can see the TypeScript types for the flat config file format in the DefinitelyTyped project. The interface for the objects in the config’s array is called the `FlatConfig`. -You can view the type definitions in the [DefinitelyTyped repository on Github](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/eslint/index.d.ts). +You can view the type definitions in the [DefinitelyTyped repository on GitHub](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/eslint/index.d.ts). ## Further Reading diff --git a/lib/rules/no-constant-binary-expression.js b/lib/rules/no-constant-binary-expression.js index 2f3013b74f6..bc8f0730aec 100644 --- a/lib/rules/no-constant-binary-expression.js +++ b/lib/rules/no-constant-binary-expression.js @@ -148,7 +148,7 @@ function isStaticBoolean(scope, node) { * truthiness. * https://262.ecma-international.org/5.1/#sec-11.9.3 * - * Javascript `==` operator works by converting the boolean to `1` (true) or + * JavaScript `==` operator works by converting the boolean to `1` (true) or * `+0` (false) and then checks the values `==` equality to that number. * @param {Scope} scope The scope in which node was found. * @param {ASTNode} node The node to test. From d23574c5c0275c8b3714a7a6d3e8bf2108af60f1 Mon Sep 17 00:00:00 2001 From: benj-dobs <35073256+benj-dobs@users.noreply.github.com> Date: Mon, 13 May 2024 10:21:26 +0100 Subject: [PATCH 134/166] docs: Clarify usage of `no-unreachable` with TypeScript (#18445) docs: Clarify usage of no-unreachable with TypeScript Fixes: #18378 --- docs/src/rules/no-unreachable.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/rules/no-unreachable.md b/docs/src/rules/no-unreachable.md index 15f77f8173e..8d95cbf6093 100644 --- a/docs/src/rules/no-unreachable.md +++ b/docs/src/rules/no-unreachable.md @@ -2,6 +2,10 @@ title: no-unreachable rule_type: problem handled_by_typescript: true +extra_typescript_info: >- + TypeScript must be configured with + [`allowUnreachableCode: false`](https://www.typescriptlang.org/tsconfig#allowUnreachableCode) + for it to consider unreachable code an error. --- From 2465a1e3f3b78f302f64e62e5f0d851626b81b3c Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Tue, 14 May 2024 08:08:10 +0000 Subject: [PATCH 135/166] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e7268339d1..917892d672a 100644 --- a/README.md +++ b/README.md @@ -296,7 +296,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

-

JetBrains Liftoff American Express Workleap

Bronze Sponsors

+

JetBrains Liftoff American Express Workleap

Bronze Sponsors

notion Anagram Solver Icons8 Discord Ignition Nx HeroCoders Nextbase Starter Kit

From b67eba4514026ef7e489798fd883beb678817a46 Mon Sep 17 00:00:00 2001 From: Akul Srivastava Date: Tue, 14 May 2024 13:51:44 +0530 Subject: [PATCH 136/166] feat: add `restrictedNamedExportsPattern` to `no-restricted-exports` (#18431) * fix: allow glob patterns for `restrictedNamedExports` in `no-restricted-exports` * chore: update option name * chore: use regex instead of glob patterns * chore: add docs and use set for given regex strings * chore: review changes * chore: add correct/incorrect example doc * chore: review changes * chore: review changes * chore: add default test cases --- docs/src/rules/no-restricted-exports.md | 33 +++++++++ lib/rules/no-restricted-exports.js | 15 +++- tests/lib/rules/no-restricted-exports.js | 89 ++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 2 deletions(-) diff --git a/docs/src/rules/no-restricted-exports.md b/docs/src/rules/no-restricted-exports.md index 44672021a10..446c4fbc337 100644 --- a/docs/src/rules/no-restricted-exports.md +++ b/docs/src/rules/no-restricted-exports.md @@ -17,6 +17,7 @@ By default, this rule doesn't disallow any names. Only the names you specify in This rule has an object option: * `"restrictedNamedExports"` is an array of strings, where each string is a name to be restricted. +* `"restrictedNamedExportsPattern"` is a string representing a regular expression pattern. Named exports matching this pattern will be restricted. This option does not apply to `default` named exports. * `"restrictDefaultExports"` is an object option with boolean properties to restrict certain default export declarations. The option works only if the `restrictedNamedExports` option does not contain the `"default"` value. The following properties are allowed: * `direct`: restricts `export default` declarations. * `named`: restricts `export { foo as default };` declarations. @@ -130,6 +131,38 @@ export default function foo() {} ::: +### restrictedNamedExportsPattern + +Example of **incorrect** code for the `"restrictedNamedExportsPattern"` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { + "restrictedNamedExportsPattern": "bar$" +}]*/ + +export const foobar = 1; +``` + +::: + +Example of **correct** code for the `"restrictedNamedExportsPattern"` option: + +::: correct + +```js +/*eslint no-restricted-exports: ["error", { + "restrictedNamedExportsPattern": "bar$" +}]*/ + +export const abc = 1; +``` + +::: + +Note that this option does not apply to `export default` or any `default` named exports. If you want to also restrict `default` exports, use the `restrictDefaultExports` option. + ### restrictDefaultExports This option allows you to restrict certain `default` declarations. The option works only if the `restrictedNamedExports` option does not contain the `"default"` value. This option accepts the following properties: diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index a1d54b085fd..8da2f2dfe01 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -37,7 +37,8 @@ module.exports = { type: "string" }, uniqueItems: true - } + }, + restrictedNamedExportsPattern: { type: "string" } }, additionalProperties: false }, @@ -52,6 +53,7 @@ module.exports = { }, uniqueItems: true }, + restrictedNamedExportsPattern: { type: "string" }, restrictDefaultExports: { type: "object", properties: { @@ -98,6 +100,7 @@ module.exports = { create(context) { const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports); + const restrictedNamePattern = context.options[0] && context.options[0].restrictedNamedExportsPattern; const restrictDefaultExports = context.options[0] && context.options[0].restrictDefaultExports; const sourceCode = context.sourceCode; @@ -109,7 +112,15 @@ module.exports = { function checkExportedName(node) { const name = astUtils.getModuleExportName(node); - if (restrictedNames.has(name)) { + let matchesRestrictedNamePattern = false; + + if (restrictedNamePattern && name !== "default") { + const patternRegex = new RegExp(restrictedNamePattern, "u"); + + matchesRestrictedNamePattern = patternRegex.test(name); + } + + if (matchesRestrictedNamePattern || restrictedNames.has(name)) { context.report({ node, messageId: "restrictedNamed", diff --git a/tests/lib/rules/no-restricted-exports.js b/tests/lib/rules/no-restricted-exports.js index cea7c046694..aab1043fc70 100644 --- a/tests/lib/rules/no-restricted-exports.js +++ b/tests/lib/rules/no-restricted-exports.js @@ -91,6 +91,50 @@ ruleTester.run("no-restricted-exports", rule, { { code: "import a from 'foo';", options: [{ restrictedNamedExports: ["a"] }] }, { code: "import { a } from 'foo';", options: [{ restrictedNamedExports: ["a"] }] }, { code: "import { b as a } from 'foo';", options: [{ restrictedNamedExports: ["a"] }] }, + { + code: "var setSomething; export { setSomething };", + options: [{ restrictedNamedExportsPattern: "^get" }] + }, + { + code: "var foo, bar; export { foo, bar };", + options: [{ restrictedNamedExportsPattern: "^(?!foo)(?!bar).+$" }] + }, + { + code: "var foobar; export default foobar;", + options: [{ restrictedNamedExportsPattern: "bar$" }] + }, + { + code: "var foobar; export default foobar;", + options: [{ restrictedNamedExportsPattern: "default" }] + }, + { + code: "export default 'default';", + options: [{ restrictedNamedExportsPattern: "default" }] + }, + { + code: "var foobar; export { foobar as default };", + options: [{ restrictedNamedExportsPattern: "default" }] + }, + { + code: "var foobar; export { foobar as 'default' };", + options: [{ restrictedNamedExportsPattern: "default" }] + }, + { + code: "export { default } from 'mod';", + options: [{ restrictedNamedExportsPattern: "default" }] + }, + { + code: "export { default as default } from 'mod';", + options: [{ restrictedNamedExportsPattern: "default" }] + }, + { + code: "export { foobar as default } from 'mod';", + options: [{ restrictedNamedExportsPattern: "default" }] + }, + { + code: "export * as default from 'mod';", + options: [{ restrictedNamedExportsPattern: "default" }] + }, // does not check re-export all declarations { code: "export * from 'foo';", options: [{ restrictedNamedExports: ["a"] }] }, @@ -533,6 +577,51 @@ ruleTester.run("no-restricted-exports", rule, { ] }, + // restrictedNamedExportsPattern + { + code: "var getSomething; export { getSomething };", + options: [{ restrictedNamedExportsPattern: "get*" }], + errors: [ + { messageId: "restrictedNamed", data: { name: "getSomething" }, type: "Identifier" } + ] + }, + { + code: "var getSomethingFromUser; export { getSomethingFromUser };", + options: [{ restrictedNamedExportsPattern: "User$" }], + errors: [ + { messageId: "restrictedNamed", data: { name: "getSomethingFromUser" }, type: "Identifier" } + ] + }, + { + code: "var foo, ab, xy; export { foo, ab, xy };", + options: [{ restrictedNamedExportsPattern: "(b|y)$" }], + errors: [ + { messageId: "restrictedNamed", data: { name: "ab" }, type: "Identifier" }, + { messageId: "restrictedNamed", data: { name: "xy" }, type: "Identifier" } + ] + }, + { + code: "var foo; export { foo as ab };", + options: [{ restrictedNamedExportsPattern: "(b|y)$" }], + errors: [ + { messageId: "restrictedNamed", data: { name: "ab" }, type: "Identifier" } + ] + }, + { + code: "var privateUserEmail; export { privateUserEmail };", + options: [{ restrictedNamedExportsPattern: "^privateUser" }], + errors: [ + { messageId: "restrictedNamed", data: { name: "privateUserEmail" }, type: "Identifier" } + ] + }, + { + code: "export const a = 1;", + options: [{ restrictedNamedExportsPattern: "^(?!foo)(?!bar).+$" }], + errors: [ + { messageId: "restrictedNamed", data: { name: "a" }, type: "Identifier" } + ] + }, + // reports "default" in named export declarations (when configured) { code: "var a; export { a as default };", From 39fb0ee9cd33f952707294e67f194d414261a571 Mon Sep 17 00:00:00 2001 From: dalaoshu <165626830+shulaoda@users.noreply.github.com> Date: Thu, 16 May 2024 10:13:35 +0800 Subject: [PATCH 137/166] fix: object-shorthand loses type parameters when auto-fixing (#18438) * fix: object-shorthand loses type parameters when auto-fixing Fixes #18429 * refactor: remove special astUtils feature * refactor: judge the first token and use it's index * style: remove unnecessary condition * test: update test fixture * fix: modify the logic to support special test case * refactor: follow very nice code advice --- lib/rules/object-shorthand.js | 27 +- .../object-with-generic-arrow-fn-props.js | 2137 +++++++++++++++++ tests/lib/rules/object-shorthand.js | 33 + 3 files changed, 2177 insertions(+), 20 deletions(-) create mode 100644 tests/fixtures/parsers/typescript-parsers/object-with-generic-arrow-fn-props.js diff --git a/lib/rules/object-shorthand.js b/lib/rules/object-shorthand.js index e4cb3a44573..269a7cf7d17 100644 --- a/lib/rules/object-shorthand.js +++ b/lib/rules/object-shorthand.js @@ -284,35 +284,22 @@ module.exports = { const arrowToken = sourceCode.getTokenBefore(node.value.body, astUtils.isArrowToken); const fnBody = sourceCode.text.slice(arrowToken.range[1], node.value.range[1]); - let shouldAddParensAroundParameters = false; - let tokenBeforeParams; - - if (node.value.params.length === 0) { - tokenBeforeParams = sourceCode.getFirstToken(node.value, astUtils.isOpeningParenToken); - } else { - tokenBeforeParams = sourceCode.getTokenBefore(node.value.params[0]); - } - - if (node.value.params.length === 1) { - const hasParen = astUtils.isOpeningParenToken(tokenBeforeParams); - const isTokenOutsideNode = tokenBeforeParams.range[0] < node.range[0]; - - shouldAddParensAroundParameters = !hasParen || isTokenOutsideNode; - } + // First token should not be `async` + const firstValueToken = sourceCode.getFirstToken(node.value, { + skip: node.value.async ? 1 : 0 + }); - const sliceStart = shouldAddParensAroundParameters - ? node.value.params[0].range[0] - : tokenBeforeParams.range[0]; + const sliceStart = firstValueToken.range[0]; const sliceEnd = sourceCode.getTokenBefore(arrowToken).range[1]; + const shouldAddParens = node.value.params.length === 1 && node.value.params[0].range[0] === sliceStart; const oldParamText = sourceCode.text.slice(sliceStart, sliceEnd); - const newParamText = shouldAddParensAroundParameters ? `(${oldParamText})` : oldParamText; + const newParamText = shouldAddParens ? `(${oldParamText})` : oldParamText; return fixer.replaceTextRange( fixRange, methodPrefix + newParamText + fnBody ); - } /** diff --git a/tests/fixtures/parsers/typescript-parsers/object-with-generic-arrow-fn-props.js b/tests/fixtures/parsers/typescript-parsers/object-with-generic-arrow-fn-props.js new file mode 100644 index 00000000000..757fb5b48ef --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/object-with-generic-arrow-fn-props.js @@ -0,0 +1,2137 @@ +"use strict"; + +/* + * Parsed on https://typescript-eslint.io/play using @typescript-eslint/parser@5.4.5 + * + * Source: +const test = { + key: (): void => { }, + key: async (): Promise => { }, + + key: (arg: T): T => { return arg }, + key: async (arg: T): Promise => { return arg }, +} + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "VariableDeclaration", + declarations: [ + { + type: "VariableDeclarator", + definite: false, + id: { + type: "Identifier", + decorators: [], + name: "test", + optional: false, + range: [6, 10], + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 10, + }, + }, + }, + init: { + type: "ObjectExpression", + properties: [ + { + type: "Property", + key: { + type: "Identifier", + decorators: [], + name: "key", + optional: false, + range: [19, 22], + loc: { + start: { + line: 2, + column: 4, + }, + end: { + line: 2, + column: 7, + }, + }, + }, + value: { + type: "ArrowFunctionExpression", + generator: false, + id: null, + params: [], + body: { + type: "BlockStatement", + body: [], + range: [39, 42], + loc: { + start: { + line: 2, + column: 24, + }, + end: { + line: 2, + column: 27, + }, + }, + }, + async: false, + expression: false, + returnType: { + type: "TSTypeAnnotation", + loc: { + start: { + line: 2, + column: 14, + }, + end: { + line: 2, + column: 20, + }, + }, + range: [29, 35], + typeAnnotation: { + type: "TSVoidKeyword", + range: [31, 35], + loc: { + start: { + line: 2, + column: 16, + }, + end: { + line: 2, + column: 20, + }, + }, + }, + }, + typeParameters: { + type: "TSTypeParameterDeclaration", + range: [24, 27], + loc: { + start: { + line: 2, + column: 9, + }, + end: { + line: 2, + column: 12, + }, + }, + params: [ + { + type: "TSTypeParameter", + name: { + type: "Identifier", + decorators: [], + name: "T", + optional: false, + range: [25, 26], + loc: { + start: { + line: 2, + column: 10, + }, + end: { + line: 2, + column: 11, + }, + }, + }, + in: false, + out: false, + const: false, + range: [25, 26], + loc: { + start: { + line: 2, + column: 10, + }, + end: { + line: 2, + column: 11, + }, + }, + }, + ], + }, + range: [24, 42], + loc: { + start: { + line: 2, + column: 9, + }, + end: { + line: 2, + column: 27, + }, + }, + }, + computed: false, + method: false, + optional: false, + shorthand: false, + kind: "init", + range: [19, 42], + loc: { + start: { + line: 2, + column: 4, + }, + end: { + line: 2, + column: 27, + }, + }, + }, + { + type: "Property", + key: { + type: "Identifier", + decorators: [], + name: "key", + optional: false, + range: [48, 51], + loc: { + start: { + line: 3, + column: 4, + }, + end: { + line: 3, + column: 7, + }, + }, + }, + value: { + type: "ArrowFunctionExpression", + generator: false, + id: null, + params: [], + body: { + type: "BlockStatement", + body: [], + range: [83, 86], + loc: { + start: { + line: 3, + column: 39, + }, + end: { + line: 3, + column: 42, + }, + }, + }, + async: true, + expression: false, + returnType: { + type: "TSTypeAnnotation", + loc: { + start: { + line: 3, + column: 20, + }, + end: { + line: 3, + column: 35, + }, + }, + range: [64, 79], + typeAnnotation: { + type: "TSTypeReference", + typeName: { + type: "Identifier", + decorators: [], + name: "Promise", + optional: false, + range: [66, 73], + loc: { + start: { + line: 3, + column: 22, + }, + end: { + line: 3, + column: 29, + }, + }, + }, + typeArguments: { + type: "TSTypeParameterInstantiation", + range: [73, 79], + params: [ + { + type: "TSVoidKeyword", + range: [74, 78], + loc: { + start: { + line: 3, + column: 30, + }, + end: { + line: 3, + column: 34, + }, + }, + }, + ], + loc: { + start: { + line: 3, + column: 29, + }, + end: { + line: 3, + column: 35, + }, + }, + }, + range: [66, 79], + loc: { + start: { + line: 3, + column: 22, + }, + end: { + line: 3, + column: 35, + }, + }, + }, + }, + typeParameters: { + type: "TSTypeParameterDeclaration", + range: [59, 62], + loc: { + start: { + line: 3, + column: 15, + }, + end: { + line: 3, + column: 18, + }, + }, + params: [ + { + type: "TSTypeParameter", + name: { + type: "Identifier", + decorators: [], + name: "T", + optional: false, + range: [60, 61], + loc: { + start: { + line: 3, + column: 16, + }, + end: { + line: 3, + column: 17, + }, + }, + }, + in: false, + out: false, + const: false, + range: [60, 61], + loc: { + start: { + line: 3, + column: 16, + }, + end: { + line: 3, + column: 17, + }, + }, + }, + ], + }, + range: [53, 86], + loc: { + start: { + line: 3, + column: 9, + }, + end: { + line: 3, + column: 42, + }, + }, + }, + computed: false, + method: false, + optional: false, + shorthand: false, + kind: "init", + range: [48, 86], + loc: { + start: { + line: 3, + column: 4, + }, + end: { + line: 3, + column: 42, + }, + }, + }, + { + type: "Property", + key: { + type: "Identifier", + decorators: [], + name: "key", + optional: false, + range: [93, 96], + loc: { + start: { + line: 5, + column: 4, + }, + end: { + line: 5, + column: 7, + }, + }, + }, + value: { + type: "ArrowFunctionExpression", + generator: false, + id: null, + params: [ + { + type: "Identifier", + decorators: [], + name: "arg", + optional: false, + typeAnnotation: { + type: "TSTypeAnnotation", + loc: { + start: { + line: 5, + column: 16, + }, + end: { + line: 5, + column: 19, + }, + }, + range: [105, 108], + typeAnnotation: { + type: "TSTypeReference", + typeName: { + type: "Identifier", + decorators: [], + name: "T", + optional: false, + range: [107, 108], + loc: { + start: { + line: 5, + column: 18, + }, + end: { + line: 5, + column: 19, + }, + }, + }, + range: [107, 108], + loc: { + start: { + line: 5, + column: 18, + }, + end: { + line: 5, + column: 19, + }, + }, + }, + }, + range: [102, 108], + loc: { + start: { + line: 5, + column: 13, + }, + end: { + line: 5, + column: 19, + }, + }, + }, + ], + body: { + type: "BlockStatement", + body: [ + { + type: "ReturnStatement", + argument: { + type: "Identifier", + decorators: [], + name: "arg", + optional: false, + range: [125, 128], + loc: { + start: { + line: 5, + column: 36, + }, + end: { + line: 5, + column: 39, + }, + }, + }, + range: [118, 128], + loc: { + start: { + line: 5, + column: 29, + }, + end: { + line: 5, + column: 39, + }, + }, + }, + ], + range: [116, 130], + loc: { + start: { + line: 5, + column: 27, + }, + end: { + line: 5, + column: 41, + }, + }, + }, + async: false, + expression: false, + returnType: { + type: "TSTypeAnnotation", + loc: { + start: { + line: 5, + column: 20, + }, + end: { + line: 5, + column: 23, + }, + }, + range: [109, 112], + typeAnnotation: { + type: "TSTypeReference", + typeName: { + type: "Identifier", + decorators: [], + name: "T", + optional: false, + range: [111, 112], + loc: { + start: { + line: 5, + column: 22, + }, + end: { + line: 5, + column: 23, + }, + }, + }, + range: [111, 112], + loc: { + start: { + line: 5, + column: 22, + }, + end: { + line: 5, + column: 23, + }, + }, + }, + }, + typeParameters: { + type: "TSTypeParameterDeclaration", + range: [98, 101], + loc: { + start: { + line: 5, + column: 9, + }, + end: { + line: 5, + column: 12, + }, + }, + params: [ + { + type: "TSTypeParameter", + name: { + type: "Identifier", + decorators: [], + name: "T", + optional: false, + range: [99, 100], + loc: { + start: { + line: 5, + column: 10, + }, + end: { + line: 5, + column: 11, + }, + }, + }, + in: false, + out: false, + const: false, + range: [99, 100], + loc: { + start: { + line: 5, + column: 10, + }, + end: { + line: 5, + column: 11, + }, + }, + }, + ], + }, + range: [98, 130], + loc: { + start: { + line: 5, + column: 9, + }, + end: { + line: 5, + column: 41, + }, + }, + }, + computed: false, + method: false, + optional: false, + shorthand: false, + kind: "init", + range: [93, 130], + loc: { + start: { + line: 5, + column: 4, + }, + end: { + line: 5, + column: 41, + }, + }, + }, + { + type: "Property", + key: { + type: "Identifier", + decorators: [], + name: "key", + optional: false, + range: [136, 139], + loc: { + start: { + line: 6, + column: 4, + }, + end: { + line: 6, + column: 7, + }, + }, + }, + value: { + type: "ArrowFunctionExpression", + generator: false, + id: null, + params: [ + { + type: "Identifier", + decorators: [], + name: "arg", + optional: false, + typeAnnotation: { + type: "TSTypeAnnotation", + loc: { + start: { + line: 6, + column: 22, + }, + end: { + line: 6, + column: 25, + }, + }, + range: [154, 157], + typeAnnotation: { + type: "TSTypeReference", + typeName: { + type: "Identifier", + decorators: [], + name: "T", + optional: false, + range: [156, 157], + loc: { + start: { + line: 6, + column: 24, + }, + end: { + line: 6, + column: 25, + }, + }, + }, + range: [156, 157], + loc: { + start: { + line: 6, + column: 24, + }, + end: { + line: 6, + column: 25, + }, + }, + }, + }, + range: [151, 157], + loc: { + start: { + line: 6, + column: 19, + }, + end: { + line: 6, + column: 25, + }, + }, + }, + ], + body: { + type: "BlockStatement", + body: [ + { + type: "ReturnStatement", + argument: { + type: "Identifier", + decorators: [], + name: "arg", + optional: false, + range: [183, 186], + loc: { + start: { + line: 6, + column: 51, + }, + end: { + line: 6, + column: 54, + }, + }, + }, + range: [176, 186], + loc: { + start: { + line: 6, + column: 44, + }, + end: { + line: 6, + column: 54, + }, + }, + }, + ], + range: [174, 188], + loc: { + start: { + line: 6, + column: 42, + }, + end: { + line: 6, + column: 56, + }, + }, + }, + async: true, + expression: false, + returnType: { + type: "TSTypeAnnotation", + loc: { + start: { + line: 6, + column: 26, + }, + end: { + line: 6, + column: 38, + }, + }, + range: [158, 170], + typeAnnotation: { + type: "TSTypeReference", + typeName: { + type: "Identifier", + decorators: [], + name: "Promise", + optional: false, + range: [160, 167], + loc: { + start: { + line: 6, + column: 28, + }, + end: { + line: 6, + column: 35, + }, + }, + }, + typeArguments: { + type: "TSTypeParameterInstantiation", + range: [167, 170], + params: [ + { + type: "TSTypeReference", + typeName: { + type: "Identifier", + decorators: [], + name: "T", + optional: false, + range: [168, 169], + loc: { + start: { + line: 6, + column: 36, + }, + end: { + line: 6, + column: 37, + }, + }, + }, + range: [168, 169], + loc: { + start: { + line: 6, + column: 36, + }, + end: { + line: 6, + column: 37, + }, + }, + }, + ], + loc: { + start: { + line: 6, + column: 35, + }, + end: { + line: 6, + column: 38, + }, + }, + }, + range: [160, 170], + loc: { + start: { + line: 6, + column: 28, + }, + end: { + line: 6, + column: 38, + }, + }, + }, + }, + typeParameters: { + type: "TSTypeParameterDeclaration", + range: [147, 150], + loc: { + start: { + line: 6, + column: 15, + }, + end: { + line: 6, + column: 18, + }, + }, + params: [ + { + type: "TSTypeParameter", + name: { + type: "Identifier", + decorators: [], + name: "T", + optional: false, + range: [148, 149], + loc: { + start: { + line: 6, + column: 16, + }, + end: { + line: 6, + column: 17, + }, + }, + }, + in: false, + out: false, + const: false, + range: [148, 149], + loc: { + start: { + line: 6, + column: 16, + }, + end: { + line: 6, + column: 17, + }, + }, + }, + ], + }, + range: [141, 188], + loc: { + start: { + line: 6, + column: 9, + }, + end: { + line: 6, + column: 56, + }, + }, + }, + computed: false, + method: false, + optional: false, + shorthand: false, + kind: "init", + range: [136, 188], + loc: { + start: { + line: 6, + column: 4, + }, + end: { + line: 6, + column: 56, + }, + }, + }, + ], + range: [13, 191], + loc: { + start: { + line: 1, + column: 13, + }, + end: { + line: 7, + column: 1, + }, + }, + }, + range: [6, 191], + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 7, + column: 1, + }, + }, + }, + ], + declare: false, + kind: "const", + range: [0, 191], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 7, + column: 1, + }, + }, + }, + ], + comments: [], + range: [0, 191], + sourceType: "script", + tokens: [ + { + type: "Keyword", + value: "const", + range: [0, 5], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + { + type: "Identifier", + value: "test", + range: [6, 10], + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 10, + }, + }, + }, + { + type: "Punctuator", + value: "=", + range: [11, 12], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 12, + }, + }, + }, + { + type: "Punctuator", + value: "{", + range: [13, 14], + loc: { + start: { + line: 1, + column: 13, + }, + end: { + line: 1, + column: 14, + }, + }, + }, + { + type: "Identifier", + value: "key", + range: [19, 22], + loc: { + start: { + line: 2, + column: 4, + }, + end: { + line: 2, + column: 7, + }, + }, + }, + { + type: "Punctuator", + value: ":", + range: [22, 23], + loc: { + start: { + line: 2, + column: 7, + }, + end: { + line: 2, + column: 8, + }, + }, + }, + { + type: "Punctuator", + value: "<", + range: [24, 25], + loc: { + start: { + line: 2, + column: 9, + }, + end: { + line: 2, + column: 10, + }, + }, + }, + { + type: "Identifier", + value: "T", + range: [25, 26], + loc: { + start: { + line: 2, + column: 10, + }, + end: { + line: 2, + column: 11, + }, + }, + }, + { + type: "Punctuator", + value: ">", + range: [26, 27], + loc: { + start: { + line: 2, + column: 11, + }, + end: { + line: 2, + column: 12, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [27, 28], + loc: { + start: { + line: 2, + column: 12, + }, + end: { + line: 2, + column: 13, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [28, 29], + loc: { + start: { + line: 2, + column: 13, + }, + end: { + line: 2, + column: 14, + }, + }, + }, + { + type: "Punctuator", + value: ":", + range: [29, 30], + loc: { + start: { + line: 2, + column: 14, + }, + end: { + line: 2, + column: 15, + }, + }, + }, + { + type: "Keyword", + value: "void", + range: [31, 35], + loc: { + start: { + line: 2, + column: 16, + }, + end: { + line: 2, + column: 20, + }, + }, + }, + { + type: "Punctuator", + value: "=>", + range: [36, 38], + loc: { + start: { + line: 2, + column: 21, + }, + end: { + line: 2, + column: 23, + }, + }, + }, + { + type: "Punctuator", + value: "{", + range: [39, 40], + loc: { + start: { + line: 2, + column: 24, + }, + end: { + line: 2, + column: 25, + }, + }, + }, + { + type: "Punctuator", + value: "}", + range: [41, 42], + loc: { + start: { + line: 2, + column: 26, + }, + end: { + line: 2, + column: 27, + }, + }, + }, + { + type: "Punctuator", + value: ",", + range: [42, 43], + loc: { + start: { + line: 2, + column: 27, + }, + end: { + line: 2, + column: 28, + }, + }, + }, + { + type: "Identifier", + value: "key", + range: [48, 51], + loc: { + start: { + line: 3, + column: 4, + }, + end: { + line: 3, + column: 7, + }, + }, + }, + { + type: "Punctuator", + value: ":", + range: [51, 52], + loc: { + start: { + line: 3, + column: 7, + }, + end: { + line: 3, + column: 8, + }, + }, + }, + { + type: "Identifier", + value: "async", + range: [53, 58], + loc: { + start: { + line: 3, + column: 9, + }, + end: { + line: 3, + column: 14, + }, + }, + }, + { + type: "Punctuator", + value: "<", + range: [59, 60], + loc: { + start: { + line: 3, + column: 15, + }, + end: { + line: 3, + column: 16, + }, + }, + }, + { + type: "Identifier", + value: "T", + range: [60, 61], + loc: { + start: { + line: 3, + column: 16, + }, + end: { + line: 3, + column: 17, + }, + }, + }, + { + type: "Punctuator", + value: ">", + range: [61, 62], + loc: { + start: { + line: 3, + column: 17, + }, + end: { + line: 3, + column: 18, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [62, 63], + loc: { + start: { + line: 3, + column: 18, + }, + end: { + line: 3, + column: 19, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [63, 64], + loc: { + start: { + line: 3, + column: 19, + }, + end: { + line: 3, + column: 20, + }, + }, + }, + { + type: "Punctuator", + value: ":", + range: [64, 65], + loc: { + start: { + line: 3, + column: 20, + }, + end: { + line: 3, + column: 21, + }, + }, + }, + { + type: "Identifier", + value: "Promise", + range: [66, 73], + loc: { + start: { + line: 3, + column: 22, + }, + end: { + line: 3, + column: 29, + }, + }, + }, + { + type: "Punctuator", + value: "<", + range: [73, 74], + loc: { + start: { + line: 3, + column: 29, + }, + end: { + line: 3, + column: 30, + }, + }, + }, + { + type: "Keyword", + value: "void", + range: [74, 78], + loc: { + start: { + line: 3, + column: 30, + }, + end: { + line: 3, + column: 34, + }, + }, + }, + { + type: "Punctuator", + value: ">", + range: [78, 79], + loc: { + start: { + line: 3, + column: 34, + }, + end: { + line: 3, + column: 35, + }, + }, + }, + { + type: "Punctuator", + value: "=>", + range: [80, 82], + loc: { + start: { + line: 3, + column: 36, + }, + end: { + line: 3, + column: 38, + }, + }, + }, + { + type: "Punctuator", + value: "{", + range: [83, 84], + loc: { + start: { + line: 3, + column: 39, + }, + end: { + line: 3, + column: 40, + }, + }, + }, + { + type: "Punctuator", + value: "}", + range: [85, 86], + loc: { + start: { + line: 3, + column: 41, + }, + end: { + line: 3, + column: 42, + }, + }, + }, + { + type: "Punctuator", + value: ",", + range: [86, 87], + loc: { + start: { + line: 3, + column: 42, + }, + end: { + line: 3, + column: 43, + }, + }, + }, + { + type: "Identifier", + value: "key", + range: [93, 96], + loc: { + start: { + line: 5, + column: 4, + }, + end: { + line: 5, + column: 7, + }, + }, + }, + { + type: "Punctuator", + value: ":", + range: [96, 97], + loc: { + start: { + line: 5, + column: 7, + }, + end: { + line: 5, + column: 8, + }, + }, + }, + { + type: "Punctuator", + value: "<", + range: [98, 99], + loc: { + start: { + line: 5, + column: 9, + }, + end: { + line: 5, + column: 10, + }, + }, + }, + { + type: "Identifier", + value: "T", + range: [99, 100], + loc: { + start: { + line: 5, + column: 10, + }, + end: { + line: 5, + column: 11, + }, + }, + }, + { + type: "Punctuator", + value: ">", + range: [100, 101], + loc: { + start: { + line: 5, + column: 11, + }, + end: { + line: 5, + column: 12, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [101, 102], + loc: { + start: { + line: 5, + column: 12, + }, + end: { + line: 5, + column: 13, + }, + }, + }, + { + type: "Identifier", + value: "arg", + range: [102, 105], + loc: { + start: { + line: 5, + column: 13, + }, + end: { + line: 5, + column: 16, + }, + }, + }, + { + type: "Punctuator", + value: ":", + range: [105, 106], + loc: { + start: { + line: 5, + column: 16, + }, + end: { + line: 5, + column: 17, + }, + }, + }, + { + type: "Identifier", + value: "T", + range: [107, 108], + loc: { + start: { + line: 5, + column: 18, + }, + end: { + line: 5, + column: 19, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [108, 109], + loc: { + start: { + line: 5, + column: 19, + }, + end: { + line: 5, + column: 20, + }, + }, + }, + { + type: "Punctuator", + value: ":", + range: [109, 110], + loc: { + start: { + line: 5, + column: 20, + }, + end: { + line: 5, + column: 21, + }, + }, + }, + { + type: "Identifier", + value: "T", + range: [111, 112], + loc: { + start: { + line: 5, + column: 22, + }, + end: { + line: 5, + column: 23, + }, + }, + }, + { + type: "Punctuator", + value: "=>", + range: [113, 115], + loc: { + start: { + line: 5, + column: 24, + }, + end: { + line: 5, + column: 26, + }, + }, + }, + { + type: "Punctuator", + value: "{", + range: [116, 117], + loc: { + start: { + line: 5, + column: 27, + }, + end: { + line: 5, + column: 28, + }, + }, + }, + { + type: "Keyword", + value: "return", + range: [118, 124], + loc: { + start: { + line: 5, + column: 29, + }, + end: { + line: 5, + column: 35, + }, + }, + }, + { + type: "Identifier", + value: "arg", + range: [125, 128], + loc: { + start: { + line: 5, + column: 36, + }, + end: { + line: 5, + column: 39, + }, + }, + }, + { + type: "Punctuator", + value: "}", + range: [129, 130], + loc: { + start: { + line: 5, + column: 40, + }, + end: { + line: 5, + column: 41, + }, + }, + }, + { + type: "Punctuator", + value: ",", + range: [130, 131], + loc: { + start: { + line: 5, + column: 41, + }, + end: { + line: 5, + column: 42, + }, + }, + }, + { + type: "Identifier", + value: "key", + range: [136, 139], + loc: { + start: { + line: 6, + column: 4, + }, + end: { + line: 6, + column: 7, + }, + }, + }, + { + type: "Punctuator", + value: ":", + range: [139, 140], + loc: { + start: { + line: 6, + column: 7, + }, + end: { + line: 6, + column: 8, + }, + }, + }, + { + type: "Identifier", + value: "async", + range: [141, 146], + loc: { + start: { + line: 6, + column: 9, + }, + end: { + line: 6, + column: 14, + }, + }, + }, + { + type: "Punctuator", + value: "<", + range: [147, 148], + loc: { + start: { + line: 6, + column: 15, + }, + end: { + line: 6, + column: 16, + }, + }, + }, + { + type: "Identifier", + value: "T", + range: [148, 149], + loc: { + start: { + line: 6, + column: 16, + }, + end: { + line: 6, + column: 17, + }, + }, + }, + { + type: "Punctuator", + value: ">", + range: [149, 150], + loc: { + start: { + line: 6, + column: 17, + }, + end: { + line: 6, + column: 18, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [150, 151], + loc: { + start: { + line: 6, + column: 18, + }, + end: { + line: 6, + column: 19, + }, + }, + }, + { + type: "Identifier", + value: "arg", + range: [151, 154], + loc: { + start: { + line: 6, + column: 19, + }, + end: { + line: 6, + column: 22, + }, + }, + }, + { + type: "Punctuator", + value: ":", + range: [154, 155], + loc: { + start: { + line: 6, + column: 22, + }, + end: { + line: 6, + column: 23, + }, + }, + }, + { + type: "Identifier", + value: "T", + range: [156, 157], + loc: { + start: { + line: 6, + column: 24, + }, + end: { + line: 6, + column: 25, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [157, 158], + loc: { + start: { + line: 6, + column: 25, + }, + end: { + line: 6, + column: 26, + }, + }, + }, + { + type: "Punctuator", + value: ":", + range: [158, 159], + loc: { + start: { + line: 6, + column: 26, + }, + end: { + line: 6, + column: 27, + }, + }, + }, + { + type: "Identifier", + value: "Promise", + range: [160, 167], + loc: { + start: { + line: 6, + column: 28, + }, + end: { + line: 6, + column: 35, + }, + }, + }, + { + type: "Punctuator", + value: "<", + range: [167, 168], + loc: { + start: { + line: 6, + column: 35, + }, + end: { + line: 6, + column: 36, + }, + }, + }, + { + type: "Identifier", + value: "T", + range: [168, 169], + loc: { + start: { + line: 6, + column: 36, + }, + end: { + line: 6, + column: 37, + }, + }, + }, + { + type: "Punctuator", + value: ">", + range: [169, 170], + loc: { + start: { + line: 6, + column: 37, + }, + end: { + line: 6, + column: 38, + }, + }, + }, + { + type: "Punctuator", + value: "=>", + range: [171, 173], + loc: { + start: { + line: 6, + column: 39, + }, + end: { + line: 6, + column: 41, + }, + }, + }, + { + type: "Punctuator", + value: "{", + range: [174, 175], + loc: { + start: { + line: 6, + column: 42, + }, + end: { + line: 6, + column: 43, + }, + }, + }, + { + type: "Keyword", + value: "return", + range: [176, 182], + loc: { + start: { + line: 6, + column: 44, + }, + end: { + line: 6, + column: 50, + }, + }, + }, + { + type: "Identifier", + value: "arg", + range: [183, 186], + loc: { + start: { + line: 6, + column: 51, + }, + end: { + line: 6, + column: 54, + }, + }, + }, + { + type: "Punctuator", + value: "}", + range: [187, 188], + loc: { + start: { + line: 6, + column: 55, + }, + end: { + line: 6, + column: 56, + }, + }, + }, + { + type: "Punctuator", + value: ",", + range: [188, 189], + loc: { + start: { + line: 6, + column: 56, + }, + end: { + line: 6, + column: 57, + }, + }, + }, + { + type: "Punctuator", + value: "}", + range: [190, 191], + loc: { + start: { + line: 7, + column: 0, + }, + end: { + line: 7, + column: 1, + }, + }, + }, + ], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 7, + column: 1, + }, + }, + parent: null, +}); diff --git a/tests/lib/rules/object-shorthand.js b/tests/lib/rules/object-shorthand.js index 26fd0bc6076..1cc168bb849 100644 --- a/tests/lib/rules/object-shorthand.js +++ b/tests/lib/rules/object-shorthand.js @@ -960,6 +960,12 @@ ruleTester.run("object-shorthand", rule, { }, // avoidExplicitReturnArrows + { + code: "({ x: (arg => { return; }) })", + output: "({ x(arg) { return; } })", + options: ["always", { avoidExplicitReturnArrows: true }], + errors: [METHOD_ERROR] + }, { code: "({ x: () => { return; } })", output: "({ x() { return; } })", @@ -1206,6 +1212,33 @@ ruleTester.run("object-shorthand", rule, { errors: [LONGFORM_METHOD_ERROR] }, + // https://github.com/eslint/eslint/issues/18429 + { + code: unIndent` + const test = { + key: (): void => { }, + key: async (): Promise => { }, + + key: (arg: T): T => { return arg }, + key: async (arg: T): Promise => { return arg }, + } + `, + output: unIndent` + const test = { + key(): void { }, + async key(): Promise { }, + + key(arg: T): T { return arg }, + async key(arg: T): Promise { return arg }, + } + `, + options: ["always", { avoidExplicitReturnArrows: true }], + languageOptions: { + parser: require("../../fixtures/parsers/typescript-parsers/object-with-generic-arrow-fn-props") + }, + errors: Array(4).fill(METHOD_ERROR) + }, + // typescript: arrow function should preserve the return value { code: unIndent` From e17e1c0dd5d5dc5a4cae5888116913f6555b1f1e Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 16 May 2024 08:06:21 +0000 Subject: [PATCH 138/166] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 917892d672a..f06f2fefc7c 100644 --- a/README.md +++ b/README.md @@ -296,7 +296,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

-

JetBrains Liftoff American Express Workleap

Bronze Sponsors

+

JetBrains Liftoff American Express Workleap

Bronze Sponsors

notion Anagram Solver Icons8 Discord Ignition Nx HeroCoders Nextbase Starter Kit

From 62e686c5e90411fed2b5561be5688d7faf64d791 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 16 May 2024 11:03:01 -0400 Subject: [PATCH 139/166] docs: Add troubleshooting info for plugin compatibility (#18451) * docs: Add troubleshooting info for plugin compatibility * Update docs/src/use/configure/plugins.md Co-authored-by: Milos Djermanovic * Update docs/src/use/configure/plugins.md Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- docs/src/use/configure/migration-guide.md | 4 ++ docs/src/use/configure/plugins.md | 5 +++ docs/src/use/troubleshooting/index.md | 6 +++ .../troubleshooting/v9-rule-api-changes.md | 40 +++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 docs/src/use/troubleshooting/v9-rule-api-changes.md diff --git a/docs/src/use/configure/migration-guide.md b/docs/src/use/configure/migration-guide.md index e12bfd0b427..723b87d77b3 100644 --- a/docs/src/use/configure/migration-guide.md +++ b/docs/src/use/configure/migration-guide.md @@ -78,6 +78,10 @@ export default [ ]; ``` +::: tip +If you import a plugin and get an error such as "TypeError: context.getScope is not a function", then that means the plugin has not yet been updated to the ESLint v9.x rule API. While you should file an issue with the particular plugin, you can manually patch the plugin to work in ESLint v9.x using the [compatibility utilities](https://eslint.org/blog/2024/05/eslint-compatibility-utilities/). +::: + ### Custom Parsers In eslintrc files, importing a custom parser is similar to importing a plugin: you use a string to specify the name of the parser. diff --git a/docs/src/use/configure/plugins.md b/docs/src/use/configure/plugins.md index 0a801f87011..7319c8ece00 100644 --- a/docs/src/use/configure/plugins.md +++ b/docs/src/use/configure/plugins.md @@ -214,3 +214,8 @@ export default [ ``` ESLint only lints named code blocks when they are JavaScript files or if they match a `files` entry in a config object. Be sure to add a config object with a matching `files` entry if you want to lint non-JavaScript named code blocks. + +## Common Problems + +* [Plugin rules using the ESLint < v9.0.0 API](../troubleshooting/v9-rule-api-changes) +* [Plugin configurations have not been upgraded to flat config](migration-guide#using-eslintrc-configs-in-flat-config) diff --git a/docs/src/use/troubleshooting/index.md b/docs/src/use/troubleshooting/index.md index 25cbbc87f3a..a77256b1117 100644 --- a/docs/src/use/troubleshooting/index.md +++ b/docs/src/use/troubleshooting/index.md @@ -9,6 +9,12 @@ eleventyNavigation: This page serves as a reference for common issues working with ESLint. +## Configuration + +* [`TypeError: context.getScope is not a function`](./v9-rule-api-changes) + +## Legacy (eslintrc) Configuration + * [`ESLint couldn't determine the plugin … uniquely`](./couldnt-determine-the-plugin-uniquely) * [`ESLint couldn't find the config … to extend from`](./couldnt-find-the-config) * [`ESLint couldn't find the plugin …`](./couldnt-find-the-plugin) diff --git a/docs/src/use/troubleshooting/v9-rule-api-changes.md b/docs/src/use/troubleshooting/v9-rule-api-changes.md new file mode 100644 index 00000000000..d8128fa3596 --- /dev/null +++ b/docs/src/use/troubleshooting/v9-rule-api-changes.md @@ -0,0 +1,40 @@ +--- +title: "TypeError: context.getScope is not a function" +eleventyNavigation: + key: v9 rule api changes + parent: troubleshooting + title: "TypeError: context.getScope is not a function" +--- + +## Symptoms + +When using ESLint v9.0.0 or later with a plugin, you may see one of the following errors: + +```plaintext +TypeError: context.getScope is not a function +TypeError: context.getAncestors is not a function +TypeError: context.markVariableAsUsed is not a function +TypeError: context.getDeclaredVariables is not a function +``` + +## Cause + +ESLint v9.0.0 introduces [changes to the rules API](https://eslint.org/blog/2023/09/preparing-custom-rules-eslint-v9/) that plugin rules use, which included moving some methods from the `context` object to the `sourceCode` object. If you're seeing one of these errors, that means the plugin has not yet been updated to use the new rules API. + +## Resolution + +Common resolutions for this issue include: + +* Upgrade the plugin to the latest version +* Use the [compatibility utilities](https://eslint.org/blog/2024/05/eslint-compatibility-utilities/) to patch the plugin in your config file + +::: important +If you are already using the latest version of the plugin and you need to use the compatibility utilities to make the plugin work with ESLint v9.0.0 and later, make sure to open an issue on the plugin's repository to ask the maintainer to make the necessary API changes. +::: + +## Resources + +For more information, see: + +* [Configure Plugins](../configure/plugins) for documentation on how to configure plugins +* [Create Plugins](../../extend/plugins#configs-in-plugins) for documentation on how to define plugins From 5c28d9a367e1608e097c491f40b8afd0730a8b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Jastrz=C4=99bski?= Date: Thu, 16 May 2024 19:34:29 +0200 Subject: [PATCH 140/166] fix: don't remove comments between key and value in object-shorthand (#18442) * fix: don't collapse comments in object-shorthand Fixes #18441 * add missing test * refactor * fix: recognize comments inside value node --- lib/rules/object-shorthand.js | 14 ++++++++++++++ tests/lib/rules/object-shorthand.js | 30 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/rules/object-shorthand.js b/lib/rules/object-shorthand.js index 269a7cf7d17..f035bbe581f 100644 --- a/lib/rules/object-shorthand.js +++ b/lib/rules/object-shorthand.js @@ -484,6 +484,13 @@ module.exports = { node, messageId: "expectedPropertyShorthand", fix(fixer) { + + // x: /* */ x + // x: (/* */ x) + if (sourceCode.getCommentsInside(node).length > 0) { + return null; + } + return fixer.replaceText(node, node.value.name); } }); @@ -497,6 +504,13 @@ module.exports = { node, messageId: "expectedPropertyShorthand", fix(fixer) { + + // "x": /* */ x + // "x": (/* */ x) + if (sourceCode.getCommentsInside(node).length > 0) { + return null; + } + return fixer.replaceText(node, node.value.name); } }); diff --git a/tests/lib/rules/object-shorthand.js b/tests/lib/rules/object-shorthand.js index 1cc168bb849..04ac9c44f04 100644 --- a/tests/lib/rules/object-shorthand.js +++ b/tests/lib/rules/object-shorthand.js @@ -516,6 +516,36 @@ ruleTester.run("object-shorthand", rule, { output: null, errors: [METHOD_ERROR] }, + { + code: "var x = {a: /* comment */ a}", + output: null, + errors: [PROPERTY_ERROR] + }, + { + code: "var x = {a /* comment */: a}", + output: null, + errors: [PROPERTY_ERROR] + }, + { + code: "var x = {a: (a /* comment */)}", + output: null, + errors: [PROPERTY_ERROR] + }, + { + code: "var x = {'a': /* comment */ a}", + output: null, + errors: [PROPERTY_ERROR] + }, + { + code: "var x = {'a': (a /* comment */)}", + output: null, + errors: [PROPERTY_ERROR] + }, + { + code: "var x = {'a' /* comment */: a}", + output: null, + errors: [PROPERTY_ERROR] + }, { code: "var x = {y: function() {}}", output: "var x = {y() {}}", From 06f1d1cd874dfc40a6651b08d766f6522a67b3f0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 17 May 2024 10:16:49 +0200 Subject: [PATCH 141/166] chore: update dependency @humanwhocodes/retry to ^0.3.0 (#18463) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 54290f00d54..75cc125ce5f 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@eslint/js": "9.2.0", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.2.4", + "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", "chalk": "^4.0.0", From b32153c97317c6fc593c2abbf6ae994519d473b4 Mon Sep 17 00:00:00 2001 From: Percy Ma Date: Fri, 17 May 2024 19:51:38 +0800 Subject: [PATCH 142/166] feat: add `overrides.namedExports` to `func-style` rule (#18444) * feat: add `overrides.namedExports` to `func-style` rule * Apply suggestions from code review Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> * docs: fix examples * Update lib/rules/func-style.js Co-authored-by: Milos Djermanovic * fix: false negative --------- Co-authored-by: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Co-authored-by: Milos Djermanovic --- docs/src/rules/func-style.md | 97 ++++++++++++++++- lib/rules/func-style.js | 46 +++++++- tests/lib/rules/func-style.js | 197 ++++++++++++++++++++++++++++++++++ 3 files changed, 335 insertions(+), 5 deletions(-) diff --git a/docs/src/rules/func-style.md b/docs/src/rules/func-style.md index 05b667790d4..6c15a7072a6 100644 --- a/docs/src/rules/func-style.md +++ b/docs/src/rules/func-style.md @@ -59,9 +59,14 @@ This rule has a string option: * `"expression"` (default) requires the use of function expressions instead of function declarations * `"declaration"` requires the use of function declarations instead of function expressions -This rule has an object option for an exception: +This rule has an object option for two exceptions: * `"allowArrowFunctions"`: `true` (default `false`) allows the use of arrow functions. This option applies only when the string option is set to `"declaration"` (arrow functions are always allowed when the string option is set to `"expression"`, regardless of this option) +* `"overrides"`: + * `"namedExports": "expression" | "declaration" | "ignore"`: used to override function styles in named exports + * `"expression"`: like string option + * `"declaration"`: like string option + * `"ignore"`: either style is acceptable ### expression @@ -148,6 +153,96 @@ var foo = () => {}; ::: +### overrides + +#### namedExports + +##### expression + +Examples of **incorrect** code for this rule with the `"declaration"` and `{"overrides": { "namedExports": "expression" }}` option: + +::: incorrect + +```js +/*eslint func-style: ["error", "declaration", { "overrides": { "namedExports": "expression" } }]*/ + +export function foo() { + // ... +} +``` + +::: + +Examples of **correct** code for this rule with the `"declaration"` and `{"overrides": { "namedExports": "expression" }}` option: + +::: correct + +```js +/*eslint func-style: ["error", "declaration", { "overrides": { "namedExports": "expression" } }]*/ + +export var foo = function() { + // ... +}; + +export var bar = () => {}; +``` + +::: + +##### declaration + +Examples of **incorrect** code for this rule with the `"expression"` and `{"overrides": { "namedExports": "declaration" }}` option: + +::: incorrect + +```js +/*eslint func-style: ["error", "expression", { "overrides": { "namedExports": "declaration" } }]*/ + +export var foo = function() { + // ... +}; + +export var bar = () => {}; +``` + +::: + +Examples of **correct** code for this rule with the `"expression"` and `{"overrides": { "namedExports": "declaration" }}` option: + +::: correct + +```js +/*eslint func-style: ["error", "expression", { "overrides": { "namedExports": "declaration" } }]*/ + +export function foo() { + // ... +} +``` + +::: + +##### ignore + +Examples of **correct** code for this rule with the `{"overrides": { "namedExports": "ignore" }}` option: + +::: correct + +```js +/*eslint func-style: ["error", "expression", { "overrides": { "namedExports": "ignore" } }]*/ + +export var foo = function() { + // ... +}; + +export var bar = () => {}; + +export function baz() { + // ... +} +``` + +::: + ## When Not To Use It If you want to allow developers to each decide how they want to write functions on their own, then you can disable this rule. diff --git a/lib/rules/func-style.js b/lib/rules/func-style.js index ab83772ef5f..46af53293ea 100644 --- a/lib/rules/func-style.js +++ b/lib/rules/func-style.js @@ -29,6 +29,15 @@ module.exports = { allowArrowFunctions: { type: "boolean", default: false + }, + overrides: { + type: "object", + properties: { + namedExports: { + enum: ["declaration", "expression", "ignore"] + } + }, + additionalProperties: false } }, additionalProperties: false @@ -46,13 +55,22 @@ module.exports = { const style = context.options[0], allowArrowFunctions = context.options[1] && context.options[1].allowArrowFunctions, enforceDeclarations = (style === "declaration"), + exportFunctionStyle = context.options[1] && context.options[1].overrides && context.options[1].overrides.namedExports, stack = []; const nodesToCheck = { FunctionDeclaration(node) { stack.push(false); - if (!enforceDeclarations && node.parent.type !== "ExportDefaultDeclaration") { + if ( + !enforceDeclarations && + node.parent.type !== "ExportDefaultDeclaration" && + (typeof exportFunctionStyle === "undefined" || node.parent.type !== "ExportNamedDeclaration") + ) { + context.report({ node, messageId: "expression" }); + } + + if (node.parent.type === "ExportNamedDeclaration" && exportFunctionStyle === "expression") { context.report({ node, messageId: "expression" }); } }, @@ -63,7 +81,18 @@ module.exports = { FunctionExpression(node) { stack.push(false); - if (enforceDeclarations && node.parent.type === "VariableDeclarator") { + if ( + enforceDeclarations && + node.parent.type === "VariableDeclarator" && + (typeof exportFunctionStyle === "undefined" || node.parent.parent.parent.type !== "ExportNamedDeclaration") + ) { + context.report({ node: node.parent, messageId: "declaration" }); + } + + if ( + node.parent.type === "VariableDeclarator" && node.parent.parent.parent.type === "ExportNamedDeclaration" && + exportFunctionStyle === "declaration" + ) { context.report({ node: node.parent, messageId: "declaration" }); } }, @@ -86,8 +115,17 @@ module.exports = { nodesToCheck["ArrowFunctionExpression:exit"] = function(node) { const hasThisExpr = stack.pop(); - if (enforceDeclarations && !hasThisExpr && node.parent.type === "VariableDeclarator") { - context.report({ node: node.parent, messageId: "declaration" }); + if (!hasThisExpr && node.parent.type === "VariableDeclarator") { + if ( + enforceDeclarations && + (typeof exportFunctionStyle === "undefined" || node.parent.parent.parent.type !== "ExportNamedDeclaration") + ) { + context.report({ node: node.parent, messageId: "declaration" }); + } + + if (node.parent.parent.parent.type === "ExportNamedDeclaration" && exportFunctionStyle === "declaration") { + context.report({ node: node.parent, messageId: "declaration" }); + } } }; } diff --git a/tests/lib/rules/func-style.js b/tests/lib/rules/func-style.js index 083cce4bf39..fd8985d468b 100644 --- a/tests/lib/rules/func-style.js +++ b/tests/lib/rules/func-style.js @@ -81,6 +81,74 @@ ruleTester.run("func-style", rule, { code: "var foo = () => { function foo() { this; } };", options: ["declaration", { allowArrowFunctions: true }], languageOptions: { ecmaVersion: 6 } + }, + { + code: "export function foo() {};", + options: ["declaration"] + }, + { + code: "export function foo() {};", + options: ["expression", { overrides: { namedExports: "declaration" } }] + }, + { + code: "export function foo() {};", + options: ["declaration", { overrides: { namedExports: "declaration" } }] + }, + { + code: "export function foo() {};", + options: ["expression", { overrides: { namedExports: "ignore" } }] + }, + { + code: "export function foo() {};", + options: ["declaration", { overrides: { namedExports: "ignore" } }] + }, + { + code: "export var foo = function(){};", + options: ["expression"] + }, + { + code: "export var foo = function(){};", + options: ["declaration", { overrides: { namedExports: "expression" } }] + }, + { + code: "export var foo = function(){};", + options: ["expression", { overrides: { namedExports: "expression" } }] + }, + { + code: "export var foo = function(){};", + options: ["declaration", { overrides: { namedExports: "ignore" } }] + }, + { + code: "export var foo = function(){};", + options: ["expression", { overrides: { namedExports: "ignore" } }] + }, + { + code: "export var foo = () => {};", + options: ["expression", { overrides: { namedExports: "expression" } }] + }, + { + code: "export var foo = () => {};", + options: ["declaration", { overrides: { namedExports: "expression" } }] + }, + { + code: "export var foo = () => {};", + options: ["declaration", { overrides: { namedExports: "ignore" } }] + }, + { + code: "export var foo = () => {};", + options: ["expression", { overrides: { namedExports: "ignore" } }] + }, + { + code: "export var foo = () => {};", + options: ["declaration", { allowArrowFunctions: true, overrides: { namedExports: "expression" } }] + }, + { + code: "export var foo = () => {};", + options: ["expression", { allowArrowFunctions: true, overrides: { namedExports: "expression" } }] + }, + { + code: "export var foo = () => {};", + options: ["declaration", { allowArrowFunctions: true, overrides: { namedExports: "ignore" } }] } ], @@ -126,6 +194,135 @@ ruleTester.run("func-style", rule, { type: "FunctionDeclaration" } ] + }, + { + code: "export function foo(){}", + options: ["expression"], + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration" + } + ] + }, + { + code: "export function foo() {};", + options: ["declaration", { overrides: { namedExports: "expression" } }], + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration" + } + ] + }, + { + code: "export function foo() {};", + options: ["expression", { overrides: { namedExports: "expression" } }], + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration" + } + ] + }, + { + code: "export var foo = function(){};", + options: ["declaration"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator" + } + ] + }, + { + code: "export var foo = function(){};", + options: ["expression", { overrides: { namedExports: "declaration" } }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator" + } + ] + }, + { + code: "export var foo = function(){};", + options: ["declaration", { overrides: { namedExports: "declaration" } }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator" + } + ] + }, + { + code: "export var foo = () => {};", + options: ["declaration"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator" + } + ] + }, + { + code: "export var b = () => {};", + options: ["expression", { overrides: { namedExports: "declaration" } }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator" + } + ] + }, + { + code: "export var c = () => {};", + options: ["declaration", { overrides: { namedExports: "declaration" } }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator" + } + ] + }, + { + code: "function foo() {};", + options: ["expression", { overrides: { namedExports: "declaration" } }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration" + } + ] + }, + { + code: "var foo = function() {};", + options: ["declaration", { overrides: { namedExports: "expression" } }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator" + } + ] + }, + { + code: "var foo = () => {};", + options: ["declaration", { overrides: { namedExports: "expression" } }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator" + } + ] } ] }); From ceada8c702d4903d6872f46a25d68b672d2c6289 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 17 May 2024 16:14:25 +0200 Subject: [PATCH 143/166] docs: explain how to use "tsc waiting" label (#18466) --- docs/src/maintain/manage-issues.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/src/maintain/manage-issues.md b/docs/src/maintain/manage-issues.md index 14abbdbb306..ffd1fb9e694 100644 --- a/docs/src/maintain/manage-issues.md +++ b/docs/src/maintain/manage-issues.md @@ -151,6 +151,13 @@ When a suggestion is too ambitious or would take too much time to complete, it's **Breaking Changes:** Be on the lookout for changes that would be breaking. Issues that represent breaking changes should be labeled as "breaking". +## Request Feedback from TSC + +To request feedback from the TSC, team members can ping `@eslint/eslint-tsc` and add the label "tsc waiting" on an issue or pull request. +Unless otherwise requested, every TSC member should provide feedback on [issues labeled "tsc waiting"](https://github.com/issues?q=org%3Aeslint+label%3A"tsc+waiting"). +If a TSC member is unable to respond in a timely manner, they should post a comment indicating when they expect to be able to leave their feedback. +The last TSC member who provides feedback on an issue labeled "tsc waiting" should remove the label. + ## When to Close an Issue All team members are allowed to close issues depending on how the issue has been resolved. From 8db0eff4ba89b45f439c27ba1202ed056ae92e83 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 17 May 2024 14:52:48 -0400 Subject: [PATCH 144/166] fix: Improve config error messages (#18457) * fix: Improve config error messages * Fix failing test --- lib/eslint/eslint.js | 1 + messages/config-file-missing.js | 16 ++++++++++++++++ messages/eslintrc-incompat.js | 23 ++++++++++++++++++++++- tests/bin/eslint.js | 2 +- 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 messages/config-file-missing.js diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 29fabca6a34..93efd149c64 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -347,6 +347,7 @@ async function locateConfigFileToUse({ configFile, cwd }) { basePath = path.resolve(path.dirname(configFilePath)); } else { error = new Error("Could not find config file."); + error.messageTemplate = "config-file-missing"; } } diff --git a/messages/config-file-missing.js b/messages/config-file-missing.js new file mode 100644 index 00000000000..a416a87d343 --- /dev/null +++ b/messages/config-file-missing.js @@ -0,0 +1,16 @@ +"use strict"; + +module.exports = function() { + return ` +ESLint couldn't find an eslint.config.(js|mjs|cjs) file. + +From ESLint v9.0.0, the default configuration file is now eslint.config.js. +If you are using a .eslintrc.* file, please follow the migration guide +to update your configuration file to the new format: + +https://eslint.org/docs/latest/use/configure/migration-guide + +If you still have problems after following the migration guide, please stop by +https://eslint.org/chat/help to chat with the team. +`.trimStart(); +}; diff --git a/messages/eslintrc-incompat.js b/messages/eslintrc-incompat.js index ee77cb2328e..b89c39bd88b 100644 --- a/messages/eslintrc-incompat.js +++ b/messages/eslintrc-incompat.js @@ -11,6 +11,9 @@ Flat config uses "languageOptions.globals" to define global variables for your f Please see the following page for information on how to convert your config object into the correct format: https://eslint.org/docs/latest/use/configure/migration-guide#configuring-language-options + +If you're not using "env" directly (it may be coming from a plugin), please see the following: +https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config `, extends: ` @@ -18,8 +21,11 @@ A config object is using the "extends" key, which is not supported in flat confi Instead of "extends", you can include config objects that you'd like to extend from directly in the flat config array. -Please see the following page for more information: +If you're using "extends" in your config file, please see the following: https://eslint.org/docs/latest/use/configure/migration-guide#predefined-and-shareable-configs + +If you're not using "extends" directly (it may be coming from a plugin), please see the following: +https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config `, globals: ` @@ -29,6 +35,9 @@ Flat config uses "languageOptions.globals" to define global variables for your f Please see the following page for information on how to convert your config object into the correct format: https://eslint.org/docs/latest/use/configure/migration-guide#configuring-language-options + +If you're not using "globals" directly (it may be coming from a plugin), please see the following: +https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config `, ignorePatterns: ` @@ -38,6 +47,9 @@ Flat config uses "ignores" to specify files to ignore. Please see the following page for information on how to convert your config object into the correct format: https://eslint.org/docs/latest/use/configure/migration-guide#ignoring-files + +If you're not using "ignorePatterns" directly (it may be coming from a plugin), please see the following: +https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config `, noInlineConfig: ` @@ -56,6 +68,9 @@ Flat config is an array that acts like the eslintrc "overrides" array. Please see the following page for information on how to convert your config object into the correct format: https://eslint.org/docs/latest/use/configure/migration-guide#glob-based-configs + +If you're not using "overrides" directly (it may be coming from a plugin), please see the following: +https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config `, parser: ` @@ -65,6 +80,9 @@ Flat config uses "languageOptions.parser" to override the default parser. Please see the following page for information on how to convert your config object into the correct format: https://eslint.org/docs/latest/use/configure/migration-guide#custom-parsers + +If you're not using "parser" directly (it may be coming from a plugin), please see the following: +https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config `, parserOptions: ` @@ -74,6 +92,9 @@ Flat config uses "languageOptions.parserOptions" to specify parser options. Please see the following page for information on how to convert your config object into the correct format: https://eslint.org/docs/latest/use/configure/migration-guide#configuring-language-options + +If you're not using "parserOptions" directly (it may be coming from a plugin), please see the following: +https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config `, reportUnusedDisableDirectives: ` diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js index 3197506ff8b..392bf57ae72 100644 --- a/tests/bin/eslint.js +++ b/tests/bin/eslint.js @@ -181,7 +181,7 @@ describe("bin/eslint.js", () => { const stderrPromise = getOutput(child).then(output => { assert.match( output.stderr, - /Could not find config file/u + /couldn't find an eslint\.config/u ); }); From b681ecbdf0882cbb7902682a9d35c1e76ac76c30 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 17 May 2024 20:15:00 +0000 Subject: [PATCH 145/166] chore: package.json update for @eslint/js release --- packages/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/package.json b/packages/js/package.json index 7c0aaf1ae8a..5bd4e24613e 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "9.2.0", + "version": "9.3.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 58e271924aeb8ac2b8864845cd787ef3f9239939 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 17 May 2024 22:35:14 +0200 Subject: [PATCH 146/166] chore: update dependencies for v9.3.0 release (#18469) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 75cc125ce5f..d3da292077a 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^3.0.2", - "@eslint/js": "9.2.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.3.0", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.3.0", From 463a2e35b5205d2fd7fc46bc5b52fb2caa152cb9 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 17 May 2024 20:45:50 +0000 Subject: [PATCH 147/166] Build: changelog update for 9.3.0 --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d06bf41ec74..9957a59e51e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,34 @@ +v9.3.0 - May 17, 2024 + +* [`58e2719`](https://github.com/eslint/eslint/commit/58e271924aeb8ac2b8864845cd787ef3f9239939) chore: update dependencies for v9.3.0 release (#18469) (Francesco Trotta) +* [`b681ecb`](https://github.com/eslint/eslint/commit/b681ecbdf0882cbb7902682a9d35c1e76ac76c30) chore: package.json update for @eslint/js release (Jenkins) +* [`8db0eff`](https://github.com/eslint/eslint/commit/8db0eff4ba89b45f439c27ba1202ed056ae92e83) fix: Improve config error messages (#18457) (Nicholas C. Zakas) +* [`ceada8c`](https://github.com/eslint/eslint/commit/ceada8c702d4903d6872f46a25d68b672d2c6289) docs: explain how to use "tsc waiting" label (#18466) (Francesco Trotta) +* [`b32153c`](https://github.com/eslint/eslint/commit/b32153c97317c6fc593c2abbf6ae994519d473b4) feat: add `overrides.namedExports` to `func-style` rule (#18444) (Percy Ma) +* [`06f1d1c`](https://github.com/eslint/eslint/commit/06f1d1cd874dfc40a6651b08d766f6522a67b3f0) chore: update dependency @humanwhocodes/retry to ^0.3.0 (#18463) (renovate[bot]) +* [`5c28d9a`](https://github.com/eslint/eslint/commit/5c28d9a367e1608e097c491f40b8afd0730a8b9e) fix: don't remove comments between key and value in object-shorthand (#18442) (Kuba Jastrzębski) +* [`62e686c`](https://github.com/eslint/eslint/commit/62e686c5e90411fed2b5561be5688d7faf64d791) docs: Add troubleshooting info for plugin compatibility (#18451) (Nicholas C. Zakas) +* [`e17e1c0`](https://github.com/eslint/eslint/commit/e17e1c0dd5d5dc5a4cae5888116913f6555b1f1e) docs: Update README (GitHub Actions Bot) +* [`39fb0ee`](https://github.com/eslint/eslint/commit/39fb0ee9cd33f952707294e67f194d414261a571) fix: object-shorthand loses type parameters when auto-fixing (#18438) (dalaoshu) +* [`b67eba4`](https://github.com/eslint/eslint/commit/b67eba4514026ef7e489798fd883beb678817a46) feat: add `restrictedNamedExportsPattern` to `no-restricted-exports` (#18431) (Akul Srivastava) +* [`2465a1e`](https://github.com/eslint/eslint/commit/2465a1e3f3b78f302f64e62e5f0d851626b81b3c) docs: Update README (GitHub Actions Bot) +* [`d23574c`](https://github.com/eslint/eslint/commit/d23574c5c0275c8b3714a7a6d3e8bf2108af60f1) docs: Clarify usage of `no-unreachable` with TypeScript (#18445) (benj-dobs) +* [`1db9bae`](https://github.com/eslint/eslint/commit/1db9bae944b69945e3b05f76754cced16ae83838) docs: Fix typos (#18443) (Frieder Bluemle) +* [`069aa68`](https://github.com/eslint/eslint/commit/069aa680c78b8516b9a1b568519f1d01e74fb2a2) feat: add option `allowEscape` to `no-misleading-character-class` rule (#18208) (Francesco Trotta) +* [`7065196`](https://github.com/eslint/eslint/commit/70651968beb0f907c9689c2477721c0b991acc4a) docs: Update README (GitHub Actions Bot) +* [`05ef92d`](https://github.com/eslint/eslint/commit/05ef92dd15949014c0735125c89b7bd70dec58c8) feat: deprecate `multiline-comment-style` & `line-comment-position` (#18435) (唯然) +* [`a63ed72`](https://github.com/eslint/eslint/commit/a63ed722a64040d2be90f36e45f1f5060a9fe28e) refactor: Use `node:` protocol for built-in Node.js modules (#18434) (Milos Djermanovic) +* [`04e7c6e`](https://github.com/eslint/eslint/commit/04e7c6e0a24bd2d7691ae641e2dc0e6d538dcdfd) docs: update deprecation notice of `no-return-await` (#18433) (Tanuj Kanti) +* [`e763512`](https://github.com/eslint/eslint/commit/e7635126f36145b47fe5d135ab258af43b2715c9) docs: Link global ignores section in config object property list (#18430) (MaoShizhong) +* [`37eba48`](https://github.com/eslint/eslint/commit/37eba48d6f2d3c99c5ecf2fc3967e428a6051dbb) fix: don't crash when `fs.readFile` returns promise from another realm (#18416) (Milos Djermanovic) +* [`040700a`](https://github.com/eslint/eslint/commit/040700a7a19726bb9568fc190bff95e88fb87269) chore: update dependency markdownlint-cli to ^0.40.0 (#18425) (renovate[bot]) +* [`f47847c`](https://github.com/eslint/eslint/commit/f47847c1b45ef1ac5f05f3a37f5f8c46b860c57f) chore: update actions/stale action to v9 (#18426) (renovate[bot]) +* [`c18ad25`](https://github.com/eslint/eslint/commit/c18ad252c280443e85f788c70ce597e1941f8ff5) chore: update actions/upload-artifact action to v4 (#18427) (renovate[bot]) +* [`27e3060`](https://github.com/eslint/eslint/commit/27e3060f7519d84501a11218343c34df4947b303) chore: Disable documentation label (#18423) (Nicholas C. Zakas) +* [`ac7f718`](https://github.com/eslint/eslint/commit/ac7f718de66131187302387fc26907c4c93196f9) docs: reflect release of v9 in config migration guide (#18412) (Peter Briggs) +* [`db0b174`](https://github.com/eslint/eslint/commit/db0b174c3ace60e29585bfc3520727c44cefcfc5) feat: add `enforceForInnerExpressions` option to `no-extra-boolean-cast` (#18222) (Kirk Waiblinger) +* [`0de0909`](https://github.com/eslint/eslint/commit/0de0909e001191a3464077d37e8c0b3f67e9a1cb) docs: fix grammar in configuration file resolution (#18419) (Mike McCready) + v9.2.0 - May 3, 2024 * [`b346605`](https://github.com/eslint/eslint/commit/b3466052802a1586560ad56a8128d603284d58c2) chore: upgrade @eslint/js@9.2.0 (#18413) (Milos Djermanovic) From 41a871cf43874e2f27ad08554c7900daf0e94b06 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 17 May 2024 20:45:51 +0000 Subject: [PATCH 148/166] 9.3.0 --- docs/package.json | 2 +- docs/src/_data/versions.json | 2 +- docs/src/use/formatters/html-formatter-example.html | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/package.json b/docs/package.json index 5316fecd52f..3f9ec19ac2a 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "9.2.0", + "version": "9.3.0", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/_data/versions.json b/docs/src/_data/versions.json index fe0175c97bc..76d0db5c45f 100644 --- a/docs/src/_data/versions.json +++ b/docs/src/_data/versions.json @@ -6,7 +6,7 @@ "path": "/docs/head/" }, { - "version": "9.2.0", + "version": "9.3.0", "branch": "latest", "path": "/docs/latest/" }, diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index 9b3da63f5a2..88cb0f2dbde 100644 --- a/docs/src/use/formatters/html-formatter-example.html +++ b/docs/src/use/formatters/html-formatter-example.html @@ -118,7 +118,7 @@

ESLint Report

- 8 problems (4 errors, 4 warnings) - Generated on Fri May 03 2024 19:45:43 GMT+0000 (Coordinated Universal Time) + 8 problems (4 errors, 4 warnings) - Generated on Fri May 17 2024 20:45:52 GMT+0000 (Coordinated Universal Time)
diff --git a/package.json b/package.json index d3da292077a..f0b10c13207 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "9.2.0", + "version": "9.3.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 70118a5b11860fce364028d3c515393b6a586aae Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 20 May 2024 21:06:20 +0200 Subject: [PATCH 149/166] fix: `func-style` false positive with arrow functions and `super` (#18473) --- lib/rules/func-style.js | 6 +++--- tests/lib/rules/func-style.js | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/rules/func-style.js b/lib/rules/func-style.js index 46af53293ea..9d52c73e103 100644 --- a/lib/rules/func-style.js +++ b/lib/rules/func-style.js @@ -100,7 +100,7 @@ module.exports = { stack.pop(); }, - ThisExpression() { + "ThisExpression, Super"() { if (stack.length > 0) { stack[stack.length - 1] = true; } @@ -113,9 +113,9 @@ module.exports = { }; nodesToCheck["ArrowFunctionExpression:exit"] = function(node) { - const hasThisExpr = stack.pop(); + const hasThisOrSuperExpr = stack.pop(); - if (!hasThisExpr && node.parent.type === "VariableDeclarator") { + if (!hasThisOrSuperExpr && node.parent.type === "VariableDeclarator") { if ( enforceDeclarations && (typeof exportFunctionStyle === "undefined" || node.parent.parent.parent.type !== "ExportNamedDeclaration") diff --git a/tests/lib/rules/func-style.js b/tests/lib/rules/func-style.js index fd8985d468b..63fcda87766 100644 --- a/tests/lib/rules/func-style.js +++ b/tests/lib/rules/func-style.js @@ -68,6 +68,16 @@ ruleTester.run("func-style", rule, { options: ["declaration"], languageOptions: { ecmaVersion: 6 } }, + { + code: "class C extends D { foo() { var bar = () => { super.baz(); }; } }", + options: ["declaration"], + languageOptions: { ecmaVersion: 6 } + }, + { + code: "var obj = { foo() { var bar = () => super.baz; } }", + options: ["declaration"], + languageOptions: { ecmaVersion: 6 } + }, { code: "export default function () {};", languageOptions: { ecmaVersion: 6, sourceType: "module" } @@ -82,6 +92,11 @@ ruleTester.run("func-style", rule, { options: ["declaration", { allowArrowFunctions: true }], languageOptions: { ecmaVersion: 6 } }, + { + code: "var foo = () => ({ bar() { super.baz(); } });", + options: ["declaration", { allowArrowFunctions: true }], + languageOptions: { ecmaVersion: 6 } + }, { code: "export function foo() {};", options: ["declaration"] @@ -185,6 +200,17 @@ ruleTester.run("func-style", rule, { } ] }, + { + code: "var foo = () => ({ bar() { super.baz(); } });", + options: ["declaration"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator" + } + ] + }, { code: "function foo(){}", options: ["expression"], From 389744be255717c507fafc158746e579ac08d77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Wed, 22 May 2024 15:30:29 +0800 Subject: [PATCH 150/166] fix: use `@eslint/config-inspector@latest` (#18483) * fix: use `@eslint/config-inspector@latest` like @eslint/create-config (https://github.com/eslint/eslint/issues/18369), it may use an outdated version, this commit forces to use the latest version. fixes https://github.com/eslint/eslint/issues/18481 * Update docs/src/use/command-line-interface.md Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- docs/src/use/command-line-interface.md | 2 +- lib/cli.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/use/command-line-interface.md b/docs/src/use/command-line-interface.md index 66bceade810..ab1bb682bee 100644 --- a/docs/src/use/command-line-interface.md +++ b/docs/src/use/command-line-interface.md @@ -161,7 +161,7 @@ If `.eslintrc.*` and/or `package.json` files are also used for configuration (i. #### `--inspect-config` -**Flat Config Mode Only.** This option runs `npx @eslint/config-inspector` to start the config inspector. You can use the config inspector to better understand what your configuration is doing and which files it applies to. When you use this flag, the CLI does not perform linting. +**Flat Config Mode Only.** This option runs `npx @eslint/config-inspector@latest` to start the config inspector. You can use the config inspector to better understand what your configuration is doing and which files it applies to. When you use this flag, the CLI does not perform linting. * **Argument Type**: No argument. diff --git a/lib/cli.js b/lib/cli.js index d07f81b28f2..81fdc19b748 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -448,14 +448,14 @@ const cli = { if (options.inspectConfig) { - log.info("You can also run this command directly using 'npx @eslint/config-inspector' in the same directory as your configuration file."); + log.info("You can also run this command directly using 'npx @eslint/config-inspector@latest' in the same directory as your configuration file."); try { const flatOptions = await translateOptions(options, "flat"); const spawn = require("cross-spawn"); const flags = await cli.calculateInspectConfigFlags(flatOptions.overrideConfigFile); - spawn.sync("npx", ["@eslint/config-inspector", ...flags], { encoding: "utf8", stdio: "inherit" }); + spawn.sync("npx", ["@eslint/config-inspector@latest", ...flags], { encoding: "utf8", stdio: "inherit" }); } catch (error) { log.error(error); return 2; From f06e0b5f51ae1aad8957d27aa0ea4d6d0ad51455 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Mon, 27 May 2024 11:07:51 -0500 Subject: [PATCH 151/166] docs: clarify func-style (#18477) * docs: clarify func-style Fixes #18474 * Update docs/src/rules/func-style.md Co-authored-by: Milos Djermanovic --------- Co-authored-by: Nicholas C. Zakas Co-authored-by: Milos Djermanovic --- docs/src/rules/func-style.md | 26 ++++++++++++++------------ lib/rules/func-style.js | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/src/rules/func-style.md b/docs/src/rules/func-style.md index 6c15a7072a6..e9bf4b4553e 100644 --- a/docs/src/rules/func-style.md +++ b/docs/src/rules/func-style.md @@ -5,36 +5,36 @@ further_reading: - https://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html --- - -There are two ways of defining functions in JavaScript: `function` declarations and `function` expressions. Declarations contain the `function` keyword first, followed by a name and then its arguments and the function body, for example: +There are two ways of defining functions in JavaScript: `function` declarations and function expressions assigned to variables. Function declarations are statements that begin with the `function` keyword. Function expressions can either be arrow functions or use the `function` keyword with an optional name. Here are some examples: ```js +// function declaration function doSomething() { // ... } -``` -Equivalent function expressions begin with the `var` keyword, followed by a name and then the function itself, such as: +// arrow function expression assigned to a variable +const doSomethingElse = () => { + // ... +}; -```js -var doSomething = function() { +// function expression assigned to a variable +const doSomethingAgain = function() { // ... }; ``` -The primary difference between `function` declarations and `function expressions` is that declarations are *hoisted* to the top of the scope in which they are defined, which allows you to write code that uses the function before its declaration. For example: +The primary difference between `function` declarations and function expressions is that declarations are *hoisted* to the top of the scope in which they are defined, which allows you to write code that uses the function before its declaration. For example: ```js -doSomething(); +doSomething(); // ok function doSomething() { // ... } ``` -Although this code might seem like an error, it actually works fine because JavaScript engines hoist the `function` declarations to the top of the scope. That means this code is treated as if the declaration came before the invocation. - -For `function` expressions, you must define the function before it is used, otherwise it causes an error. Example: +For function expressions, you must define the function before it is used, otherwise it causes an error. Example: ```js doSomething(); // error! @@ -50,7 +50,9 @@ Due to these different behaviors, it is common to have guidelines as to which st ## Rule Details -This rule enforces a particular type of `function` style throughout a JavaScript file, either declarations or expressions. You can specify which you prefer in the configuration. +This rule enforces a particular type of function style, either `function` declarations or expressions assigned to variables. You can specify which you prefer in the configuration. + +Note: This rule does not apply to *all* functions. For example, a callback function passed as an argument to another function is not considered by this rule. ## Options diff --git a/lib/rules/func-style.js b/lib/rules/func-style.js index 9d52c73e103..d71f4ae80b2 100644 --- a/lib/rules/func-style.js +++ b/lib/rules/func-style.js @@ -14,7 +14,7 @@ module.exports = { type: "suggestion", docs: { - description: "Enforce the consistent use of either `function` declarations or expressions", + description: "Enforce the consistent use of either `function` declarations or expressions assigned to variables", recommended: false, url: "https://eslint.org/docs/latest/rules/func-style" }, From 7226ebd69df04a4cc5fe546641f3443b60ec47e9 Mon Sep 17 00:00:00 2001 From: Ali Rezvani <3788964+rzvxa@users.noreply.github.com> Date: Tue, 28 May 2024 05:58:43 +0330 Subject: [PATCH 152/166] fix: allow implicit undefined return in `no-constructor-return` (#18515) --- docs/src/rules/no-constructor-return.md | 9 ++++++++- lib/rules/no-constructor-return.js | 2 +- tests/lib/rules/no-constructor-return.js | 8 +++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/src/rules/no-constructor-return.md b/docs/src/rules/no-constructor-return.md index e429d386acc..12734d63a3c 100644 --- a/docs/src/rules/no-constructor-return.md +++ b/docs/src/rules/no-constructor-return.md @@ -8,7 +8,7 @@ In JavaScript, returning a value in the constructor of a class may be a mistake. ## Rule Details -This rule disallows return statements in the constructor of a class. Note that returning nothing with flow control is allowed. +This rule disallows return statements in the constructor of a class. Note that returning nothing is allowed. Examples of **incorrect** code for this rule: @@ -57,6 +57,13 @@ class D { f(); } } + +class E { + constructor() { + return; + } +} + ``` ::: diff --git a/lib/rules/no-constructor-return.js b/lib/rules/no-constructor-return.js index ccce10ad636..e9ef7385562 100644 --- a/lib/rules/no-constructor-return.js +++ b/lib/rules/no-constructor-return.js @@ -49,7 +49,7 @@ module.exports = { if ( last.parent.type === "MethodDefinition" && last.parent.kind === "constructor" && - (node.parent.parent === last || node.argument) + node.argument ) { context.report({ node, diff --git a/tests/lib/rules/no-constructor-return.js b/tests/lib/rules/no-constructor-return.js index e608367cbb0..34833e877ed 100644 --- a/tests/lib/rules/no-constructor-return.js +++ b/tests/lib/rules/no-constructor-return.js @@ -41,13 +41,11 @@ ruleTester.run("no-constructor-return", rule, { "class C { constructor(a) { if (!a) { return } else { a() } } }", "class C { constructor() { function fn() { return true } } }", "class C { constructor() { this.fn = function () { return true } } }", - "class C { constructor() { this.fn = () => { return true } } }" + "class C { constructor() { this.fn = () => { return true } } }", + "class C { constructor() { return } }", + "class C { constructor() { { return } } }" ], invalid: [ - { - code: "class C { constructor() { return } }", - errors - }, { code: "class C { constructor() { return '' } }", errors From f6534d14033e04f6c7c88a1f0c44a8077148ec6b Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 28 May 2024 11:55:42 +0200 Subject: [PATCH 153/166] fix: skip processor code blocks that match only universal patterns (#18507) Fixes #18493 Closes #15949 --- docs/src/use/configure/plugins.md | 36 +++++- lib/eslint/eslint.js | 2 +- tests/lib/eslint/eslint.js | 178 ++++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+), 2 deletions(-) diff --git a/docs/src/use/configure/plugins.md b/docs/src/use/configure/plugins.md index 7319c8ece00..6cff424b1f7 100644 --- a/docs/src/use/configure/plugins.md +++ b/docs/src/use/configure/plugins.md @@ -213,7 +213,41 @@ export default [ ]; ``` -ESLint only lints named code blocks when they are JavaScript files or if they match a `files` entry in a config object. Be sure to add a config object with a matching `files` entry if you want to lint non-JavaScript named code blocks. +ESLint only lints named code blocks when they are JavaScript files or if they match a `files` entry in a config object. Be sure to add a config object with a matching `files` entry if you want to lint non-JavaScript named code blocks. Also note that [global ignores](./ignore) apply to named code blocks as well. + +```js +// eslint.config.js +import markdown from "eslint-plugin-markdown"; + +export default [ + + // applies to Markdown files + { + files: ["**/*.md"], + plugins: { + markdown + }, + processor: "markdown/markdown" + }, + + // applies to all .jsx files, including jsx blocks inside of Markdown files + { + files: ["**/*.jsx"], + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true + } + } + } + }, + + // ignore jsx blocks inside of test.md files + { + ignores: ["**/test.md/*.jsx"] + } +]; +``` ## Common Problems diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 93efd149c64..5e97192abfb 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -509,7 +509,7 @@ function verifyText({ * @returns {boolean} `true` if the linter should adopt the code block. */ filterCodeBlock(blockFilename) { - return configs.isExplicitMatch(blockFilename); + return configs.getConfig(blockFilename) !== void 0; } } ); diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 708454fb248..0b5ca66f3d4 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -67,6 +67,12 @@ describe("ESLint", () => { } }; const examplePreprocessorName = "eslint-plugin-processor"; + const patternProcessor = require("../../fixtures/processors/pattern-processor"); + const exampleMarkdownPlugin = { + processors: { + markdown: patternProcessor.defineProcessor(/```(\w+)\n(.+?)\n```(?:\n|$)/gsu) + } + }; const originalDir = process.cwd(); const fixtureDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint/fixtures"); @@ -3761,6 +3767,178 @@ describe("ESLint", () => { assert(!Object.hasOwn(results[0], "output")); }); }); + + describe("matching and ignoring code blocks", () => { + const pluginConfig = { + files: ["**/*.md"], + plugins: { + markdown: exampleMarkdownPlugin + }, + processor: "markdown/markdown" + }; + const text = unIndent` + \`\`\`js + foo_js + \`\`\` + + \`\`\`ts + foo_ts + \`\`\` + + \`\`\`cjs + foo_cjs + \`\`\` + + \`\`\`mjs + foo_mjs + \`\`\` + `; + + it("should by default lint only .js, .mjs, and .cjs virtual files", async () => { + eslint = new ESLint({ + overrideConfigFile: true, + overrideConfig: [ + pluginConfig, + { + rules: { + "no-undef": 2 + } + } + ] + }); + const [result] = await eslint.lintText(text, { filePath: "foo.md" }); + + assert.strictEqual(result.messages.length, 3); + assert.strictEqual(result.messages[0].ruleId, "no-undef"); + assert.match(result.messages[0].message, /foo_js/u); + assert.strictEqual(result.messages[0].line, 2); + assert.strictEqual(result.messages[1].ruleId, "no-undef"); + assert.match(result.messages[1].message, /foo_cjs/u); + assert.strictEqual(result.messages[1].line, 10); + assert.strictEqual(result.messages[2].ruleId, "no-undef"); + assert.match(result.messages[2].message, /foo_mjs/u); + assert.strictEqual(result.messages[2].line, 14); + }); + + it("should lint additional virtual files that match non-universal patterns", async () => { + eslint = new ESLint({ + overrideConfigFile: true, + overrideConfig: [ + pluginConfig, + { + rules: { + "no-undef": 2 + } + }, + { + files: ["**/*.ts"] + } + ] + }); + const [result] = await eslint.lintText(text, { filePath: "foo.md" }); + + assert.strictEqual(result.messages.length, 4); + assert.strictEqual(result.messages[0].ruleId, "no-undef"); + assert.match(result.messages[0].message, /foo_js/u); + assert.strictEqual(result.messages[0].line, 2); + assert.strictEqual(result.messages[1].ruleId, "no-undef"); + assert.match(result.messages[1].message, /foo_ts/u); + assert.strictEqual(result.messages[1].line, 6); + assert.strictEqual(result.messages[2].ruleId, "no-undef"); + assert.match(result.messages[2].message, /foo_cjs/u); + assert.strictEqual(result.messages[2].line, 10); + assert.strictEqual(result.messages[3].ruleId, "no-undef"); + assert.match(result.messages[3].message, /foo_mjs/u); + assert.strictEqual(result.messages[3].line, 14); + }); + + // https://github.com/eslint/eslint/issues/18493 + it("should silently skip virtual files that match only universal patterns", async () => { + eslint = new ESLint({ + overrideConfigFile: true, + overrideConfig: [ + pluginConfig, + { + files: ["**/*"], + rules: { + "no-undef": 2 + } + } + ] + }); + const [result] = await eslint.lintText(text, { filePath: "foo.md" }); + + assert.strictEqual(result.messages.length, 3); + assert.strictEqual(result.messages[0].ruleId, "no-undef"); + assert.match(result.messages[0].message, /foo_js/u); + assert.strictEqual(result.messages[0].line, 2); + assert.strictEqual(result.messages[1].ruleId, "no-undef"); + assert.match(result.messages[1].message, /foo_cjs/u); + assert.strictEqual(result.messages[1].line, 10); + assert.strictEqual(result.messages[2].ruleId, "no-undef"); + assert.match(result.messages[2].message, /foo_mjs/u); + assert.strictEqual(result.messages[2].line, 14); + }); + + it("should silently skip virtual files that are ignored by global ignores", async () => { + eslint = new ESLint({ + overrideConfigFile: true, + overrideConfig: [ + pluginConfig, + { + rules: { + "no-undef": 2 + } + }, + { + ignores: ["**/*.cjs"] + } + ] + }); + const [result] = await eslint.lintText(text, { filePath: "foo.md" }); + + assert.strictEqual(result.messages.length, 2); + assert.strictEqual(result.messages[0].ruleId, "no-undef"); + assert.match(result.messages[0].message, /foo_js/u); + assert.strictEqual(result.messages[0].line, 2); + assert.strictEqual(result.messages[1].ruleId, "no-undef"); + assert.match(result.messages[1].message, /foo_mjs/u); + assert.strictEqual(result.messages[1].line, 14); + }); + + // https://github.com/eslint/eslint/issues/15949 + it("should silently skip virtual files that are ignored by global ignores even if they match non-universal patterns", async () => { + eslint = new ESLint({ + overrideConfigFile: true, + overrideConfig: [ + pluginConfig, + { + rules: { + "no-undef": 2 + } + }, + { + files: ["**/*.ts"] + }, + { + ignores: ["**/*.md/*.ts"] + } + ] + }); + const [result] = await eslint.lintText(text, { filePath: "foo.md" }); + + assert.strictEqual(result.messages.length, 3); + assert.strictEqual(result.messages[0].ruleId, "no-undef"); + assert.match(result.messages[0].message, /foo_js/u); + assert.strictEqual(result.messages[0].line, 2); + assert.strictEqual(result.messages[1].ruleId, "no-undef"); + assert.match(result.messages[1].message, /foo_cjs/u); + assert.strictEqual(result.messages[1].line, 10); + assert.strictEqual(result.messages[2].ruleId, "no-undef"); + assert.match(result.messages[2].message, /foo_mjs/u); + assert.strictEqual(result.messages[2].line, 14); + }); + }); }); describe("Patterns which match no file should throw errors.", () => { From 80747d23dec69b30ea2c3620a1198f7d06b012b8 Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Tue, 28 May 2024 20:21:20 +0530 Subject: [PATCH 154/166] docs: refactor `prefer-destructuring` rule (#18472) * docs: refactor prefer-destructuring rule * correct options description * update option description * update wording --- docs/src/rules/prefer-destructuring.md | 168 +++++++++++-------------- 1 file changed, 75 insertions(+), 93 deletions(-) diff --git a/docs/src/rules/prefer-destructuring.md b/docs/src/rules/prefer-destructuring.md index 4a313922ed3..30232228992 100644 --- a/docs/src/rules/prefer-destructuring.md +++ b/docs/src/rules/prefer-destructuring.md @@ -14,22 +14,30 @@ With JavaScript ES6, a new syntax was added for creating variables from an array ### Options -This rule takes two sets of configuration objects. The first object parameter determines what types of destructuring the rule applies to. +This rule takes two arguments, both of which are objects. The first object parameter determines what types of destructuring the rule applies to. -The two properties, `array` and `object`, can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are true. +In the first object, there are two properties, `array` and `object`, that can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are true. -Alternatively, you can use separate configurations for different assignment types. It accepts 2 other keys instead of `array` and `object`. - -One key is `VariableDeclarator` and the other is `AssignmentExpression`, which can be used to control the destructuring requirement for each of those types independently. Each property accepts an object that accepts two properties, `array` and `object`, which can be used to control the destructuring requirement for each of `array` and `object` independently for variable declarations and assignment expressions. By default, `array` and `object` are set to true for both `VariableDeclarator` and `AssignmentExpression`. - -The rule has a second object with a single key, `enforceForRenamedProperties`, which determines whether the `object` destructuring applies to renamed variables. - -**Note**: It is not possible to determine if a variable will be referring to an object or an array at runtime. This rule therefore guesses the assignment type by checking whether the key being accessed is an integer. This can lead to the following possibly confusing situations: +```json +{ + "rules": { + "prefer-destructuring": ["error", { + "array": true, + "object": true + }] + } +} +``` -* Accessing an object property whose key is an integer will fall under the category `array` destructuring. -* Accessing an array element through a computed index will fall under the category `object` destructuring. +For example, the following configuration enforces only object destructuring, but not array destructuring: -The `--fix` option on the command line fixes only problems reported in variable declarations, and among them only those that fall under the category `object` destructuring. Furthermore, the name of the declared variable has to be the same as the name used for non-computed member access in the initializer. For example, `var foo = object.foo` can be automatically fixed by this rule. Problems that involve computed member access (e.g., `var foo = object[foo]`) or renamed properties (e.g., `var foo = object.bar`) are not automatically fixed. +```json +{ + "rules": { + "prefer-destructuring": ["error", {"object": true, "array": false}] + } +} +``` Examples of **incorrect** code for this rule: @@ -73,138 +81,112 @@ let bar; ::: -Examples of **incorrect** code when `enforceForRenamedProperties` is enabled: +Alternatively, you can use separate configurations for different assignment types. The first argument accepts two other keys instead of `array` and `object`. -::: incorrect +One key is `VariableDeclarator` and the other is `AssignmentExpression`, which can be used to control the destructuring requirement for each of those types independently. Each property is an object containing two properties, `array` and `object`, which can be used to control the destructuring requirement for each of `array` and `object` independently for variable declarations and assignment expressions. By default, `array` and `object` are set to `true` for both `VariableDeclarator` and `AssignmentExpression`. -```javascript -/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ -var foo = object.bar; +```json +{ + "rules": { + "prefer-destructuring": ["error", { + "VariableDeclarator": { + "array": true, + "object": true + }, + "AssignmentExpression": { + "array": true, + "object": true + } + }] + } +} ``` -::: - -Examples of **correct** code when `enforceForRenamedProperties` is enabled: +Examples of **correct** code when object destructuring in `VariableDeclarator` is enforced: ::: correct ```javascript -/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ -var { bar: foo } = object; +/* eslint prefer-destructuring: ["error", {VariableDeclarator: {object: true}}] */ +var {bar: foo} = object; ``` ::: -Examples of additional **correct** code when `enforceForRenamedProperties` is enabled: +Examples of **correct** code when array destructuring in `AssignmentExpression` is enforced: ::: correct ```javascript -/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ -class C { - #x; - foo() { - const bar = this.#x; // private identifiers are not allowed in destructuring - } -} +/* eslint prefer-destructuring: ["error", {AssignmentExpression: {array: true}}] */ +[bar] = array; ``` ::: -An example configuration, with the defaults `array` and `object` filled in, looks like this: +#### enforceForRenamedProperties + +The rule has a second object argument with a single key, `enforceForRenamedProperties`, which determines whether the `object` destructuring applies to renamed variables. ```json { "rules": { - "prefer-destructuring": ["error", { - "array": true, + "prefer-destructuring": ["error", + { "object": true - }, { - "enforceForRenamedProperties": false + }, + { + "enforceForRenamedProperties": true }] } } ``` -The two properties, `array` and `object`, which can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are true. - -For example, the following configuration enforces only object destructuring, but not array destructuring: - -```json -{ - "rules": { - "prefer-destructuring": ["error", {"object": true, "array": false}] - } -} -``` +Examples of **incorrect** code when `enforceForRenamedProperties` is enabled: -An example configuration, with the defaults `VariableDeclarator` and `AssignmentExpression` filled in, looks like this: +::: incorrect -```json -{ - "rules": { - "prefer-destructuring": ["error", { - "VariableDeclarator": { - "array": false, - "object": true - }, - "AssignmentExpression": { - "array": true, - "object": true - } - }, { - "enforceForRenamedProperties": false - }] - } -} +```javascript +/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ +var foo = object.bar; ``` -The two properties, `VariableDeclarator` and `AssignmentExpression`, which can be used to turn on or off the destructuring requirement for `array` and `object`. By default, all values are true. - -For example, the following configuration enforces object destructuring in variable declarations and enforces array destructuring in assignment expressions. - -```json -{ - "rules": { - "prefer-destructuring": ["error", { - "VariableDeclarator": { - "array": false, - "object": true - }, - "AssignmentExpression": { - "array": true, - "object": false - } - }, { - "enforceForRenamedProperties": false - }] - } -} - -``` +::: -Examples of **correct** code when object destructuring in `VariableDeclarator` is enforced: +Examples of **correct** code when `enforceForRenamedProperties` is enabled: ::: correct ```javascript -/* eslint prefer-destructuring: ["error", {VariableDeclarator: {object: true}}] */ -var {bar: foo} = object; +/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ +var { bar: foo } = object; ``` ::: -Examples of **correct** code when array destructuring in `AssignmentExpression` is enforced: +Examples of additional **correct** code when `enforceForRenamedProperties` is enabled: ::: correct ```javascript -/* eslint prefer-destructuring: ["error", {AssignmentExpression: {array: true}}] */ -[bar] = array; +/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ +class C { + #x; + foo() { + const bar = this.#x; // private identifiers are not allowed in destructuring + } +} ``` ::: +**Note**: It is not possible to determine if a variable will be referring to an object or an array at runtime. This rule therefore guesses the assignment type by checking whether the key being accessed is an integer. This can lead to the following possibly confusing situations: + +* Accessing an object property whose key is an integer will fall under the category `array` destructuring. +* Accessing an array element through a computed index will fall under the category `object` destructuring. + +The `--fix` option on the command line fixes only problems reported in variable declarations, and among them only those that fall under the category `object` destructuring. Furthermore, the name of the declared variable has to be the same as the name used for non-computed member access in the initializer. For example, `var foo = object.foo` can be automatically fixed by this rule. Problems that involve computed member access (e.g., `var foo = object[foo]`) or renamed properties (e.g., `var foo = object.bar`) are not automatically fixed. + ## When Not To Use It If you want to be able to access array indices or object properties directly, you can either configure the rule to your tastes or disable the rule entirely. From 89a4a0a260b8eb11487fe3d5d4d80f4630933eb3 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Wed, 29 May 2024 20:03:12 +0530 Subject: [PATCH 155/166] feat: ignore IIFE's in the `no-loop-func` rule (#17528) * feat: ignore IIFE's in the `no-loop-func` rule * refactor: remove false negatives * docs: add note about IIFE * fix: correct hasUnsafeRefsInNonIIFE logic * fix: handle more cases * chore: fix lint issues * fix: report only unsafe ref names in IIFEs * fix: handle more cases * chore: update comments * refactor: remove unwanted code * refactor: remove unwanted code * refactor: update code * fix: avoid duplicate reports * chore: refactor code to avoid memory leak * chore: apply suggestions from code review Co-authored-by: Milos Djermanovic * test: add location --------- Co-authored-by: Milos Djermanovic --- docs/src/rules/no-loop-func.md | 41 ++++- lib/rules/no-loop-func.js | 290 ++++++++++++++++++-------------- tests/lib/rules/no-loop-func.js | 265 ++++++++++++++++++++++++++++- 3 files changed, 454 insertions(+), 142 deletions(-) diff --git a/docs/src/rules/no-loop-func.md b/docs/src/rules/no-loop-func.md index 7fe45e21647..7d79eda106a 100644 --- a/docs/src/rules/no-loop-func.md +++ b/docs/src/rules/no-loop-func.md @@ -32,7 +32,7 @@ In this case, each function created within the loop returns a different number a This error is raised to highlight a piece of code that may not work as you expect it to and could also indicate a misunderstanding of how the language works. Your code may run without any problems if you do not fix this error, but in some situations it could behave unexpectedly. -This rule disallows any function within a loop that contains unsafe references (e.g. to modified variables from the outer scope). +This rule disallows any function within a loop that contains unsafe references (e.g. to modified variables from the outer scope). This rule ignores IIFEs but not async or generator functions. Examples of **incorrect** code for this rule: @@ -41,10 +41,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-loop-func: "error"*/ -for (var i=10; i; i--) { - (function() { return i; })(); -} - var i = 0; while(i < 5) { var a = function() { return i; }; @@ -73,6 +69,25 @@ for (let i = 0; i < 10; ++i) { setTimeout(() => console.log(foo)); } foo = 100; + +var arr = []; + +for (var i = 0; i < 5; i++) { + arr.push((f => f)(() => i)); +} + +for (var i = 0; i < 5; i++) { + arr.push((() => { + return () => i; + })()); +} + +for (var i = 0; i < 5; i++) { + (function fun () { + if (arr.includes(fun)) return i; + else arr.push(fun); + })(); +} ``` ::: @@ -106,6 +121,22 @@ for (let i=10; i; i--) { a(); } //... no modifications of foo after this loop ... + +var arr = []; + +for (var i=10; i; i--) { + (function() { return i; })(); +} + +for (var i = 0; i < 5; i++) { + arr.push((f => f)((() => i)())); +} + +for (var i = 0; i < 5; i++) { + arr.push((() => { + return (() => i)(); + })()); +} ``` ::: diff --git a/lib/rules/no-loop-func.js b/lib/rules/no-loop-func.js index 48312fbf58a..ba372dbb5a8 100644 --- a/lib/rules/no-loop-func.js +++ b/lib/rules/no-loop-func.js @@ -9,140 +9,16 @@ // Helpers //------------------------------------------------------------------------------ -/** - * Gets the containing loop node of a specified node. - * - * We don't need to check nested functions, so this ignores those. - * `Scope.through` contains references of nested functions. - * @param {ASTNode} node An AST node to get. - * @returns {ASTNode|null} The containing loop node of the specified node, or - * `null`. - */ -function getContainingLoopNode(node) { - for (let currentNode = node; currentNode.parent; currentNode = currentNode.parent) { - const parent = currentNode.parent; - - switch (parent.type) { - case "WhileStatement": - case "DoWhileStatement": - return parent; - - case "ForStatement": - - // `init` is outside of the loop. - if (parent.init !== currentNode) { - return parent; - } - break; - - case "ForInStatement": - case "ForOfStatement": - - // `right` is outside of the loop. - if (parent.right !== currentNode) { - return parent; - } - break; - - case "ArrowFunctionExpression": - case "FunctionExpression": - case "FunctionDeclaration": - - // We don't need to check nested functions. - return null; - - default: - break; - } - } - - return null; -} /** - * Gets the containing loop node of a given node. - * If the loop was nested, this returns the most outer loop. - * @param {ASTNode} node A node to get. This is a loop node. - * @param {ASTNode|null} excludedNode A node that the result node should not - * include. - * @returns {ASTNode} The most outer loop node. + * Identifies is a node is a FunctionExpression which is part of an IIFE + * @param {ASTNode} node Node to test + * @returns {boolean} True if it's an IIFE */ -function getTopLoopNode(node, excludedNode) { - const border = excludedNode ? excludedNode.range[1] : 0; - let retv = node; - let containingLoopNode = node; - - while (containingLoopNode && containingLoopNode.range[0] >= border) { - retv = containingLoopNode; - containingLoopNode = getContainingLoopNode(containingLoopNode); - } - - return retv; +function isIIFE(node) { + return (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") && node.parent && node.parent.type === "CallExpression" && node.parent.callee === node; } -/** - * Checks whether a given reference which refers to an upper scope's variable is - * safe or not. - * @param {ASTNode} loopNode A containing loop node. - * @param {eslint-scope.Reference} reference A reference to check. - * @returns {boolean} `true` if the reference is safe or not. - */ -function isSafe(loopNode, reference) { - const variable = reference.resolved; - const definition = variable && variable.defs[0]; - const declaration = definition && definition.parent; - const kind = (declaration && declaration.type === "VariableDeclaration") - ? declaration.kind - : ""; - - // Variables which are declared by `const` is safe. - if (kind === "const") { - return true; - } - - /* - * Variables which are declared by `let` in the loop is safe. - * It's a different instance from the next loop step's. - */ - if (kind === "let" && - declaration.range[0] > loopNode.range[0] && - declaration.range[1] < loopNode.range[1] - ) { - return true; - } - - /* - * WriteReferences which exist after this border are unsafe because those - * can modify the variable. - */ - const border = getTopLoopNode( - loopNode, - (kind === "let") ? declaration : null - ).range[0]; - - /** - * Checks whether a given reference is safe or not. - * The reference is every reference of the upper scope's variable we are - * looking now. - * - * It's safe if the reference matches one of the following condition. - * - is readonly. - * - doesn't exist inside a local function and after the border. - * @param {eslint-scope.Reference} upperRef A reference to check. - * @returns {boolean} `true` if the reference is safe. - */ - function isSafeReference(upperRef) { - const id = upperRef.identifier; - - return ( - !upperRef.isWrite() || - variable.scope.variableScope === upperRef.from.variableScope && - id.range[0] < border - ); - } - - return Boolean(variable) && variable.references.every(isSafeReference); -} //------------------------------------------------------------------------------ // Rule Definition @@ -168,8 +44,147 @@ module.exports = { create(context) { + const SKIPPED_IIFE_NODES = new Set(); const sourceCode = context.sourceCode; + /** + * Gets the containing loop node of a specified node. + * + * We don't need to check nested functions, so this ignores those, with the exception of IIFE. + * `Scope.through` contains references of nested functions. + * @param {ASTNode} node An AST node to get. + * @returns {ASTNode|null} The containing loop node of the specified node, or + * `null`. + */ + function getContainingLoopNode(node) { + for (let currentNode = node; currentNode.parent; currentNode = currentNode.parent) { + const parent = currentNode.parent; + + switch (parent.type) { + case "WhileStatement": + case "DoWhileStatement": + return parent; + + case "ForStatement": + + // `init` is outside of the loop. + if (parent.init !== currentNode) { + return parent; + } + break; + + case "ForInStatement": + case "ForOfStatement": + + // `right` is outside of the loop. + if (parent.right !== currentNode) { + return parent; + } + break; + + case "ArrowFunctionExpression": + case "FunctionExpression": + case "FunctionDeclaration": + + // We need to check nested functions only in case of IIFE. + if (SKIPPED_IIFE_NODES.has(parent)) { + break; + } + + return null; + default: + break; + } + } + + return null; + } + + /** + * Gets the containing loop node of a given node. + * If the loop was nested, this returns the most outer loop. + * @param {ASTNode} node A node to get. This is a loop node. + * @param {ASTNode|null} excludedNode A node that the result node should not + * include. + * @returns {ASTNode} The most outer loop node. + */ + function getTopLoopNode(node, excludedNode) { + const border = excludedNode ? excludedNode.range[1] : 0; + let retv = node; + let containingLoopNode = node; + + while (containingLoopNode && containingLoopNode.range[0] >= border) { + retv = containingLoopNode; + containingLoopNode = getContainingLoopNode(containingLoopNode); + } + + return retv; + } + + /** + * Checks whether a given reference which refers to an upper scope's variable is + * safe or not. + * @param {ASTNode} loopNode A containing loop node. + * @param {eslint-scope.Reference} reference A reference to check. + * @returns {boolean} `true` if the reference is safe or not. + */ + function isSafe(loopNode, reference) { + const variable = reference.resolved; + const definition = variable && variable.defs[0]; + const declaration = definition && definition.parent; + const kind = (declaration && declaration.type === "VariableDeclaration") + ? declaration.kind + : ""; + + // Variables which are declared by `const` is safe. + if (kind === "const") { + return true; + } + + /* + * Variables which are declared by `let` in the loop is safe. + * It's a different instance from the next loop step's. + */ + if (kind === "let" && + declaration.range[0] > loopNode.range[0] && + declaration.range[1] < loopNode.range[1] + ) { + return true; + } + + /* + * WriteReferences which exist after this border are unsafe because those + * can modify the variable. + */ + const border = getTopLoopNode( + loopNode, + (kind === "let") ? declaration : null + ).range[0]; + + /** + * Checks whether a given reference is safe or not. + * The reference is every reference of the upper scope's variable we are + * looking now. + * + * It's safe if the reference matches one of the following condition. + * - is readonly. + * - doesn't exist inside a local function and after the border. + * @param {eslint-scope.Reference} upperRef A reference to check. + * @returns {boolean} `true` if the reference is safe. + */ + function isSafeReference(upperRef) { + const id = upperRef.identifier; + + return ( + !upperRef.isWrite() || + variable.scope.variableScope === upperRef.from.variableScope && + id.range[0] < border + ); + } + + return Boolean(variable) && variable.references.every(isSafeReference); + } + /** * Reports functions which match the following condition: * @@ -186,6 +201,23 @@ module.exports = { } const references = sourceCode.getScope(node).through; + + // Check if the function is not asynchronous or a generator function + if (!(node.async || node.generator)) { + if (isIIFE(node)) { + + const isFunctionExpression = node.type === "FunctionExpression"; + + // Check if the function is referenced elsewhere in the code + const isFunctionReferenced = isFunctionExpression && node.id ? references.some(r => r.identifier.name === node.id.name) : false; + + if (!isFunctionReferenced) { + SKIPPED_IIFE_NODES.add(node); + return; + } + } + } + const unsafeRefs = references.filter(r => r.resolved && !isSafe(loopNode, r)).map(r => r.identifier.name); if (unsafeRefs.length > 0) { diff --git a/tests/lib/rules/no-loop-func.js b/tests/lib/rules/no-loop-func.js index a8a30b0e83b..64a288a4eb9 100644 --- a/tests/lib/rules/no-loop-func.js +++ b/tests/lib/rules/no-loop-func.js @@ -156,6 +156,57 @@ ruleTester.run("no-loop-func", rule, { { code: "for (let i = 0; i < 10; ++i) { for (let x in xs.filter(x => x != undeclared)) { } }", languageOptions: { ecmaVersion: 6 } + }, + + // IIFE + { + code: ` + let current = getStart(); + while (current) { + (() => { + current; + current.a; + current.b; + current.c; + current.d; + })(); + + current = current.upper; + } + `, + languageOptions: { ecmaVersion: 6 } + }, + "for (var i=0; (function() { i; })(), i{ i;})() }", + languageOptions: { ecmaVersion: 6 } + }, + { + code: "for (var i = 0; i < 10; ++i) { (function a(){i;})() }", + languageOptions: { ecmaVersion: 6 } + }, + { + code: ` + var arr = []; + + for (var i = 0; i < 5; i++) { + arr.push((f => f)((() => i)())); + } + `, + languageOptions: { ecmaVersion: 6 } + }, + { + code: ` + var arr = []; + + for (var i = 0; i < 5; i++) { + arr.push((() => { + return (() => i)(); + })()); + } + `, + languageOptions: { ecmaVersion: 6 } } ], @@ -190,14 +241,6 @@ ruleTester.run("no-loop-func", rule, { code: "for (var i=0; i < l; i++) { function a() { i; }; a(); }", errors: [{ messageId: "unsafeRefs", data: { varNames: "'i'" }, type: "FunctionDeclaration" }] }, - { - code: "for (var i=0; (function() { i; })(), i { + await someDelay(); + current; + })(); + + arr.push(p); + current = current.upper; + } + `, + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unsafeRefs", data: { varNames: "'current'" }, type: "ArrowFunctionExpression" }] + }, + { + code: ` + var arr = []; + + for (var i = 0; i < 5; i++) { + arr.push((f => f)( + () => i + )); + } + `, + languageOptions: { ecmaVersion: 6 }, + errors: [{ messageId: "unsafeRefs", data: { varNames: "'i'" }, type: "ArrowFunctionExpression", line: 6 }] + }, + { + code: ` + var arr = []; + + for (var i = 0; i < 5; i++) { + arr.push((() => { + return () => i; + })()); + } + `, + languageOptions: { ecmaVersion: 6 }, + errors: [{ messageId: "unsafeRefs", data: { varNames: "'i'" }, type: "ArrowFunctionExpression", line: 6 }] + }, + { + code: ` + var arr = []; + + for (var i = 0; i < 5; i++) { + arr.push((() => { + return () => { return i }; + })()); + } + `, + languageOptions: { ecmaVersion: 6 }, + errors: [{ messageId: "unsafeRefs", data: { varNames: "'i'" }, type: "ArrowFunctionExpression", line: 6 }] + }, + { + code: ` + var arr = []; + + for (var i = 0; i < 5; i++) { + arr.push((() => { + return () => { + return () => i + }; + })()); + } + `, + languageOptions: { ecmaVersion: 6 }, + errors: [{ messageId: "unsafeRefs", data: { varNames: "'i'" }, type: "ArrowFunctionExpression", line: 6 }] + }, + { + code: ` + var arr = []; + + for (var i = 0; i < 5; i++) { + arr.push((() => { + return () => + (() => i)(); + })()); + } + `, + languageOptions: { ecmaVersion: 6 }, + errors: [{ messageId: "unsafeRefs", data: { varNames: "'i'" }, type: "ArrowFunctionExpression", line: 6 }] + }, + { + code: ` + var arr = []; + + for (var i = 0; i < 5; i ++) { + (() => { + arr.push((async () => { + await 1; + return i; + })()); + })(); + } + `, + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unsafeRefs", data: { varNames: "'i'" }, type: "ArrowFunctionExpression", line: 6 }] + }, + { + code: ` + var arr = []; + + for (var i = 0; i < 5; i ++) { + (() => { + (function f() { + if (!arr.includes(f)) { + arr.push(f); + } + return i; + })(); + })(); + + } + `, + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unsafeRefs", data: { varNames: "'i'" }, type: "FunctionExpression" }] + }, + { + code: ` + var arr1 = [], arr2 = []; + + for (var [i, j] of ["a", "b", "c"].entries()) { + (() => { + arr1.push((() => i)()); + arr2.push(() => j); + })(); + } + `, + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unsafeRefs", data: { varNames: "'j'" }, type: "ArrowFunctionExpression", line: 7 }] + }, + { + code: ` + var arr = []; + + for (var i = 0; i < 5; i ++) { + ((f) => { + arr.push(f); + })(() => { + return (() => i)(); + }); + + } + `, + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unsafeRefs", data: { varNames: "'i'" }, type: "ArrowFunctionExpression", line: 7 }] + }, + { + code: ` + for (var i = 0; i < 5; i++) { + (async () => { + () => i; + })(); + } + `, + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unsafeRefs", data: { varNames: "'i'" }, type: "ArrowFunctionExpression", line: 3 }] } ] }); From 525fdffde4cb34010bc503f6d54855b3f9d07811 Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Thu, 30 May 2024 21:13:07 +0530 Subject: [PATCH 156/166] docs: fix components files (#18519) * docs: update components files * update * update --- docs/src/library/code-blocks.md | 2 ++ docs/src/library/rule.md | 15 +++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/src/library/code-blocks.md b/docs/src/library/code-blocks.md index 222d3e68519..2d45736cbc1 100644 --- a/docs/src/library/code-blocks.md +++ b/docs/src/library/code-blocks.md @@ -18,6 +18,7 @@ function() { const another = []; } `` ` + ::: ::: incorrect @@ -27,6 +28,7 @@ function() { const another = []; } `` ` + ::: ``` diff --git a/docs/src/library/rule.md b/docs/src/library/rule.md index 45bccea16a5..096668462f7 100644 --- a/docs/src/library/rule.md +++ b/docs/src/library/rule.md @@ -37,9 +37,9 @@ A rule has a: {% from 'components/rule.macro.html' import rule %} {{ rule({ - name: "getter-return", + name: "array-bracket-newline", deprecated: true, - description: 'Enforce `return` statements in getters.', + description: 'Enforces line breaks after opening and before closing array brackets.', categories: { recommended: true, fixable: true, @@ -48,13 +48,13 @@ A rule has a: }) }} {{ rule({ - name: "getter-return", + name: "no-arrow-condition", removed: true, - description: 'Enforce `return` statements in getters.', - replacedBy: "other-rule-here", + description: 'Disallows arrow functions where test conditions are expected.', + replacedBy: ["no-confusing-arrow", "no-constant-condition"], categories: { - recommended: true, - fixable: true, + recommended: false, + fixable: false, hasSuggestions: false } }) }} @@ -68,5 +68,4 @@ A rule has a: fixable: false, hasSuggestions: false } - }) }} From 594145f493d913e2b7e25a27accf33c44e1d4687 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 31 May 2024 15:07:33 +0200 Subject: [PATCH 157/166] refactor: switch to `@eslint/config-array` (#18527) * chore: switch to `@eslint/config-array` * remove redundant check --- lib/config/flat-config-array.js | 2 +- lib/eslint/eslint-helpers.js | 14 +++++++------- lib/eslint/eslint.js | 9 --------- lib/rule-tester/rule-tester.js | 2 +- package.json | 2 +- 5 files changed, 10 insertions(+), 19 deletions(-) diff --git a/lib/config/flat-config-array.js b/lib/config/flat-config-array.js index 4312066d18d..699cd4a9018 100644 --- a/lib/config/flat-config-array.js +++ b/lib/config/flat-config-array.js @@ -9,7 +9,7 @@ // Requirements //----------------------------------------------------------------------------- -const { ConfigArray, ConfigArraySymbol } = require("@humanwhocodes/config-array"); +const { ConfigArray, ConfigArraySymbol } = require("@eslint/config-array"); const { flatConfigSchema } = require("./flat-config-schema"); const { RuleValidator } = require("./rule-validator"); const { defaultConfig } = require("./default-config"); diff --git a/lib/eslint/eslint-helpers.js b/lib/eslint/eslint-helpers.js index 65dfbfb83f4..092c153d020 100644 --- a/lib/eslint/eslint-helpers.js +++ b/lib/eslint/eslint-helpers.js @@ -335,15 +335,15 @@ async function globSearch({ /* * We updated the unmatched patterns set only if the path - * matches and the file isn't ignored. If the file is - * ignored, that means there wasn't a match for the + * matches and the file has a config. If the file has no + * config, that means there wasn't a match for the * pattern so it should not be removed. * - * Performance note: isFileIgnored() aggressively caches + * Performance note: `getConfig()` aggressively caches * results so there is no performance penalty for calling - * it twice with the same argument. + * it multiple times with the same argument. */ - if (pathMatches && !configs.isFileIgnored(entry.path)) { + if (pathMatches && configs.getConfig(entry.path)) { unmatchedPatterns.delete(matcher.pattern); } @@ -351,7 +351,7 @@ async function globSearch({ }, false) : matchers.some(matcher => matcher.match(relativePath)); - return matchesPattern && !configs.isFileIgnored(entry.path); + return matchesPattern && configs.getConfig(entry.path) !== void 0; }) }, (error, entries) => { @@ -545,7 +545,7 @@ async function findFiles({ if (stat.isFile()) { results.push({ filePath, - ignored: configs.isFileIgnored(filePath) + ignored: !configs.getConfig(filePath) }); } diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 5e97192abfb..e9e6239ae79 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -881,15 +881,6 @@ class ESLint { 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 = diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index a22888c83d3..4057ff1f6ab 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -28,7 +28,7 @@ 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"); +const { ConfigArraySymbol } = require("@eslint/config-array"); const { isSerializable } = require("../shared/serialization"); //------------------------------------------------------------------------------ diff --git a/package.json b/package.json index f0b10c13207..8a615577251 100644 --- a/package.json +++ b/package.json @@ -68,9 +68,9 @@ "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", + "@eslint/config-array": "^0.15.1", "@eslint/eslintrc": "^3.1.0", "@eslint/js": "9.3.0", - "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", From d7ab6f589d39c64bc5daaef4be3a972032f04c05 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sat, 1 Jun 2024 01:35:51 +0530 Subject: [PATCH 158/166] docs: update theme when when `prefers-color-scheme` changes (#18510) docs: update theme when when prefers-color-scheme changes --- docs/src/assets/js/themes.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/src/assets/js/themes.js b/docs/src/assets/js/themes.js index e6071b21983..7a48c1b7575 100644 --- a/docs/src/assets/js/themes.js +++ b/docs/src/assets/js/themes.js @@ -26,23 +26,29 @@ disableToggle(light_theme_toggle); } - light_theme_toggle.addEventListener("click", function() { + var activateDarkMode = function() { + enableToggle(dark_theme_toggle); + + document.documentElement.setAttribute('data-theme', "dark"); + window.localStorage.setItem("theme", "dark"); + + disableToggle(light_theme_toggle); + } + + var activateLightMode = function() { enableToggle(light_theme_toggle); - theme = this.getAttribute('data-theme'); - document.documentElement.setAttribute('data-theme', theme); - window.localStorage.setItem("theme", theme); + document.documentElement.setAttribute('data-theme', "light"); + window.localStorage.setItem("theme", "light"); + disableToggle(dark_theme_toggle); - }, false); + } - dark_theme_toggle.addEventListener("click", function() { - enableToggle(dark_theme_toggle); - theme = this.getAttribute('data-theme'); - document.documentElement.setAttribute('data-theme', theme); - window.localStorage.setItem("theme", theme); + var darkModePreference = window.matchMedia("(prefers-color-scheme: dark)"); + darkModePreference.addEventListener("change", e => e.matches ? activateDarkMode() : activateLightMode()); - disableToggle(light_theme_toggle); - }, false); + light_theme_toggle.addEventListener("click", activateLightMode, false); + dark_theme_toggle.addEventListener("click", activateDarkMode, false); }, false); })(); From 5e1b5dc9a3d839737125571c8fd4e239d81608de Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 31 May 2024 20:08:50 +0000 Subject: [PATCH 159/166] chore: package.json update for @eslint/js release --- packages/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/package.json b/packages/js/package.json index 5bd4e24613e..7f2ddac1b8b 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "9.3.0", + "version": "9.4.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 010dd2ef50456a1ba5892152192b6c9d9d5fd470 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 31 May 2024 22:22:18 +0200 Subject: [PATCH 160/166] chore: upgrade to `@eslint/js@9.4.0` (#18534) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8a615577251..dc127248dfb 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@eslint-community/regexpp": "^4.6.1", "@eslint/config-array": "^0.15.1", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.3.0", + "@eslint/js": "9.4.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", From 8c6d0c3436ed9828b6945721e8ba3f121fb16b40 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 31 May 2024 20:33:53 +0000 Subject: [PATCH 161/166] Build: changelog update for 9.4.0 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9957a59e51e..4ec210eda8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +v9.4.0 - May 31, 2024 + +* [`010dd2e`](https://github.com/eslint/eslint/commit/010dd2ef50456a1ba5892152192b6c9d9d5fd470) chore: upgrade to `@eslint/js@9.4.0` (#18534) (Francesco Trotta) +* [`5e1b5dc`](https://github.com/eslint/eslint/commit/5e1b5dc9a3d839737125571c8fd4e239d81608de) chore: package.json update for @eslint/js release (Jenkins) +* [`d7ab6f5`](https://github.com/eslint/eslint/commit/d7ab6f589d39c64bc5daaef4be3a972032f04c05) docs: update theme when when `prefers-color-scheme` changes (#18510) (Nitin Kumar) +* [`594145f`](https://github.com/eslint/eslint/commit/594145f493d913e2b7e25a27accf33c44e1d4687) refactor: switch to `@eslint/config-array` (#18527) (Francesco Trotta) +* [`525fdff`](https://github.com/eslint/eslint/commit/525fdffde4cb34010bc503f6d54855b3f9d07811) docs: fix components files (#18519) (Tanuj Kanti) +* [`89a4a0a`](https://github.com/eslint/eslint/commit/89a4a0a260b8eb11487fe3d5d4d80f4630933eb3) feat: ignore IIFE's in the `no-loop-func` rule (#17528) (Nitin Kumar) +* [`80747d2`](https://github.com/eslint/eslint/commit/80747d23dec69b30ea2c3620a1198f7d06b012b8) docs: refactor `prefer-destructuring` rule (#18472) (Tanuj Kanti) +* [`f6534d1`](https://github.com/eslint/eslint/commit/f6534d14033e04f6c7c88a1f0c44a8077148ec6b) fix: skip processor code blocks that match only universal patterns (#18507) (Milos Djermanovic) +* [`7226ebd`](https://github.com/eslint/eslint/commit/7226ebd69df04a4cc5fe546641f3443b60ec47e9) fix: allow implicit undefined return in `no-constructor-return` (#18515) (Ali Rezvani) +* [`f06e0b5`](https://github.com/eslint/eslint/commit/f06e0b5f51ae1aad8957d27aa0ea4d6d0ad51455) docs: clarify func-style (#18477) (Cameron Steffen) +* [`389744b`](https://github.com/eslint/eslint/commit/389744be255717c507fafc158746e579ac08d77e) fix: use `@eslint/config-inspector@latest` (#18483) (唯然) +* [`70118a5`](https://github.com/eslint/eslint/commit/70118a5b11860fce364028d3c515393b6a586aae) fix: `func-style` false positive with arrow functions and `super` (#18473) (Milos Djermanovic) + v9.3.0 - May 17, 2024 * [`58e2719`](https://github.com/eslint/eslint/commit/58e271924aeb8ac2b8864845cd787ef3f9239939) chore: update dependencies for v9.3.0 release (#18469) (Francesco Trotta) From a5f7e589eca05a8a30bd2532380c304759cc8225 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 31 May 2024 20:33:54 +0000 Subject: [PATCH 162/166] 9.4.0 --- docs/package.json | 2 +- docs/src/_data/rules.json | 2 +- docs/src/_data/rules_meta.json | 2 +- docs/src/_data/versions.json | 2 +- docs/src/use/formatters/html-formatter-example.html | 2 +- package.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/package.json b/docs/package.json index 3f9ec19ac2a..96384c50569 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "9.3.0", + "version": "9.4.0", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 646a4751e46..441cac6df69 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -530,7 +530,7 @@ }, { "name": "func-style", - "description": "Enforce the consistent use of either `function` declarations or expressions", + "description": "Enforce the consistent use of either `function` declarations or expressions assigned to variables", "recommended": false, "fixable": false, "hasSuggestions": false diff --git a/docs/src/_data/rules_meta.json b/docs/src/_data/rules_meta.json index 63a294a32c2..ebb32dcb378 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -333,7 +333,7 @@ "func-style": { "type": "suggestion", "docs": { - "description": "Enforce the consistent use of either `function` declarations or expressions", + "description": "Enforce the consistent use of either `function` declarations or expressions assigned to variables", "recommended": false, "url": "https://eslint.org/docs/latest/rules/func-style" } diff --git a/docs/src/_data/versions.json b/docs/src/_data/versions.json index 76d0db5c45f..d5ffe0974a0 100644 --- a/docs/src/_data/versions.json +++ b/docs/src/_data/versions.json @@ -6,7 +6,7 @@ "path": "/docs/head/" }, { - "version": "9.3.0", + "version": "9.4.0", "branch": "latest", "path": "/docs/latest/" }, diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index 88cb0f2dbde..62ea919b699 100644 --- a/docs/src/use/formatters/html-formatter-example.html +++ b/docs/src/use/formatters/html-formatter-example.html @@ -118,7 +118,7 @@

ESLint Report

- 8 problems (4 errors, 4 warnings) - Generated on Fri May 17 2024 20:45:52 GMT+0000 (Coordinated Universal Time) + 8 problems (4 errors, 4 warnings) - Generated on Fri May 31 2024 20:33:55 GMT+0000 (Coordinated Universal Time)
diff --git a/package.json b/package.json index dc127248dfb..f45e7bef5dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "9.3.0", + "version": "9.4.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 2c8fd34bf1471efbd6e616b50d4e25ea858a6989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Mon, 3 Jun 2024 17:19:38 +0800 Subject: [PATCH 163/166] ci: pin @wdio/browser-runner v8.36.0 (#18540) * ci: pin @wdio/browser-runner v8.36.0 the browser testing was failing: https://github.com/eslint/eslint/actions/runs/9324019940/job/25668389482. the commit aims to fix it for now. * fix: alias node modules --- package.json | 2 +- wdio.conf.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index f45e7bef5dc..ea9cf999cef 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "@eslint-community/eslint-plugin-eslint-comments": "^4.3.0", "@types/estree": "^1.0.5", "@types/node": "^20.11.5", - "@wdio/browser-runner": "^8.14.6", + "@wdio/browser-runner": "8.36.0", "@wdio/cli": "^8.14.6", "@wdio/concise-reporter": "^8.14.0", "@wdio/globals": "^8.14.6", diff --git a/wdio.conf.js b/wdio.conf.js index 8703052e88f..cca5f31acea 100644 --- a/wdio.conf.js +++ b/wdio.conf.js @@ -18,6 +18,7 @@ exports.config = { alias: { util: "rollup-plugin-node-polyfills/polyfills/util", path: "rollup-plugin-node-polyfills/polyfills/path", + "node:path": "rollup-plugin-node-polyfills/polyfills/path", assert: "rollup-plugin-node-polyfills/polyfills/assert" } }, From 73408de08dbe1873bf6b5564533c0d81134cfeee Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 3 Jun 2024 17:07:18 +0200 Subject: [PATCH 164/166] docs: add link to configuration file docs before examples (#18535) --- docs/src/use/configure/language-options.md | 6 +++--- docs/src/use/configure/plugins.md | 4 ++-- docs/src/use/configure/rules.md | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/src/use/configure/language-options.md b/docs/src/use/configure/language-options.md index 0398d17145a..9b0b661cbb1 100644 --- a/docs/src/use/configure/language-options.md +++ b/docs/src/use/configure/language-options.md @@ -23,7 +23,7 @@ ESLint allows you to specify the JavaScript language options you want to support * `commonjs` - CommonJS module (useful if your code uses `require()`). Your code has a top-level function scope and runs in non-strict mode. * `script` - non-module. Your code has a shared global scope and runs in non-strict mode. -Here's an example configuration file you might use when linting ECMAScript 5 code: +Here's an example [configuration file](./configuration-files#configuration-file) you might use when linting ECMAScript 5 code: ```js // eslint.config.js @@ -47,7 +47,7 @@ If you are using the built-in ESLint parser, you can additionally change how ESL * `impliedStrict` - enable global [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) (if `ecmaVersion` is `5` or greater) * `jsx` - enable [JSX](https://facebook.github.io/jsx/) -Here's an example configuration file that enables JSX parsing in the default parser: +Here's an example [configuration file](./configuration-files#configuration-file) that enables JSX parsing in the default parser: ```js // eslint.config.js @@ -88,7 +88,7 @@ This defines two global variables, `var1` and `var2`. If you want to optionally ### Using configuration files -To configure global variables inside of a configuration file, set the `languageOptions.globals` configuration property to an object containing keys named for each of the global variables you want to use. For each global variable key, set the corresponding value equal to `"writable"` to allow the variable to be overwritten or `"readonly"` to disallow overwriting. For example: +To configure global variables inside of a [configuration file](./configuration-files#configuration-file), set the `languageOptions.globals` configuration property to an object containing keys named for each of the global variables you want to use. For each global variable key, set the corresponding value equal to `"writable"` to allow the variable to be overwritten or `"readonly"` to disallow overwriting. For example: ```js // eslint.config.js diff --git a/docs/src/use/configure/plugins.md b/docs/src/use/configure/plugins.md index 6cff424b1f7..c50be1f5e2d 100644 --- a/docs/src/use/configure/plugins.md +++ b/docs/src/use/configure/plugins.md @@ -22,7 +22,7 @@ You can extend ESLint with plugins in a variety of different ways. Plugins can i ESLint supports the use of third-party plugins. Plugins are simply objects that conform to a specific interface that ESLint recognizes. -To configure plugins inside of a configuration file, use the `plugins` key, which contains an object with properties representing plugin namespaces and values equal to the plugin object. +To configure plugins inside of a [configuration file](./configuration-files#configuration-file), use the `plugins` key, which contains an object with properties representing plugin namespaces and values equal to the plugin object. ```js // eslint.config.js @@ -162,7 +162,7 @@ This configuration object uses `jsd` as the prefix plugin instead of `jsdoc`. Plugins may provide processors. Processors can extract JavaScript code from other kinds of files, then let ESLint lint the JavaScript code. Alternatively, processors can convert JavaScript code during preprocessing. -To specify processors in a configuration file, use the `processor` key and assign the name of processor in the format `namespace/processor-name`. For example, the following uses the processor from `eslint-plugin-markdown` for `*.md` files. +To specify processors in a [configuration file](./configuration-files#configuration-file), use the `processor` key and assign the name of processor in the format `namespace/processor-name`. For example, the following uses the processor from `eslint-plugin-markdown` for `*.md` files. ```js // eslint.config.js diff --git a/docs/src/use/configure/rules.md b/docs/src/use/configure/rules.md index 6e439e4491b..d18e9173656 100644 --- a/docs/src/use/configure/rules.md +++ b/docs/src/use/configure/rules.md @@ -75,7 +75,7 @@ Configuration comments can include descriptions to explain why the comment is ne ### Using Configuration Files -To configure rules inside of a configuration file, use the `rules` key along with an error level and any options you want to use. For example: +To configure rules inside of a [configuration file](./configuration-files#configuration-file), use the `rules` key along with an error level and any options you want to use. For example: ```js export default [ @@ -133,7 +133,7 @@ Rules configured via configuration comments have the highest priority and are ap To configure a rule that is defined within a plugin, prefix the rule ID with the plugin namespace and `/`. -In a configuration file, for example: +In a [configuration file](./configuration-files#configuration-file), for example: ```js // eslint.config.js @@ -304,7 +304,7 @@ console.log('hello'); ### Using configuration files -To disable rules inside of a configuration file for a group of files, use a subsequent config object with a `files` key. For example: +To disable rules inside of a [configuration file](./configuration-files#configuration-file) for a group of files, use a subsequent config object with a `files` key. For example: ```js // eslint.config.js From d16a6599cad35726f62eb230bb95af463611c6c6 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 3 Jun 2024 17:07:55 +0200 Subject: [PATCH 165/166] docs: add link to migration guide for `--ext` CLI option (#18537) --- docs/src/use/command-line-interface.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/src/use/command-line-interface.md b/docs/src/use/command-line-interface.md index ab1bb682bee..1d99c4e1a44 100644 --- a/docs/src/use/command-line-interface.md +++ b/docs/src/use/command-line-interface.md @@ -189,7 +189,9 @@ npx eslint --env browser --env node file.js #### `--ext` -**eslintrc Mode Only.** This option allows you to specify which file extensions ESLint uses when searching for target files in the directories you specify. +**eslintrc Mode Only.** If you are using flat config (`eslint.config.js`), please see [migration guide](./configure/migration-guide#--ext). + +This option allows you to specify which file extensions ESLint uses when searching for target files in the directories you specify. * **Argument Type**: String. File extension. * **Multiple Arguments**: Yes From e49202de73b477fd84c27043a69d8ab9cec0d972 Mon Sep 17 00:00:00 2001 From: Tanuj Kanti Date: Sun, 25 Feb 2024 14:24:53 +0530 Subject: [PATCH 166/166] feat: limit the references of Object.assign --- docs/src/rules/prefer-object-spread.md | 3 +++ lib/rules/prefer-object-spread.js | 15 +++++++++++++++ tests/lib/rules/prefer-object-spread.js | 4 ++++ 3 files changed, 22 insertions(+) diff --git a/docs/src/rules/prefer-object-spread.md b/docs/src/rules/prefer-object-spread.md index b1927350ece..b427bee0480 100644 --- a/docs/src/rules/prefer-object-spread.md +++ b/docs/src/rules/prefer-object-spread.md @@ -55,6 +55,9 @@ Object.assign(foo, bar); Object.assign(foo, { bar, baz }); Object.assign(foo, { ...baz }); + +var foo = Object.assign; +var bar = foo({}, baz); ``` ::: diff --git a/lib/rules/prefer-object-spread.js b/lib/rules/prefer-object-spread.js index 60b0c3175c0..bbe63f60134 100644 --- a/lib/rules/prefer-object-spread.js +++ b/lib/rules/prefer-object-spread.js @@ -239,6 +239,20 @@ function defineFixer(node, sourceCode) { }; } +/** + * Limit the reference to the Object.assign. + * @param {ASTNode|null} node The node that the rule warns on, i.e. the Object.assign call + * @returns {boolean} true if the reference has Object.assign + */ +function isObjectAssignReference(node) { + return ( + ( + (node.callee.object && (node.callee.object.name === "Object" || node.callee.object.property.name === "Object")) && + (node.callee.property && node.callee.property.name === "assign") + ) + ); +} + /** @type {import('../shared/types').Rule} */ module.exports = { meta: { @@ -276,6 +290,7 @@ module.exports = { // Iterate all calls of `Object.assign` (only of the global variable `Object`). for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { if ( + isObjectAssignReference(refNode) && refNode.arguments.length >= 1 && refNode.arguments[0].type === "ObjectExpression" && !hasArraySpread(refNode) && diff --git a/tests/lib/rules/prefer-object-spread.js b/tests/lib/rules/prefer-object-spread.js index ad346062d62..8488d475571 100644 --- a/tests/lib/rules/prefer-object-spread.js +++ b/tests/lib/rules/prefer-object-spread.js @@ -86,6 +86,10 @@ ruleTester.run("prefer-object-spread", rule, { code: "class C { #assign; foo() { Object.#assign({}, foo); } }", languageOptions: { ecmaVersion: 2022 } }, + { + code: "var foo = Object.assign; \n var bar = foo({}, baz);", + languageOptions: { ecmaVersion: 2022 } + }, // ignore Object.assign() with > 1 arguments if any of the arguments is an object expression with a getter/setter "Object.assign({ get a() {} }, {})",