From 37432f27dc15817d66cf42377792197dc2aeb8b2 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 22 May 2023 04:34:02 +0200 Subject: [PATCH 001/248] chore: update descriptions in key-spacing tests (#17195) --- tests/lib/rules/key-spacing.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/lib/rules/key-spacing.js b/tests/lib/rules/key-spacing.js index 9ac345d39c19..96103129e9fb 100644 --- a/tests/lib/rules/key-spacing.js +++ b/tests/lib/rules/key-spacing.js @@ -942,10 +942,10 @@ ruleTester.run("key-spacing", rule, { { code: ` var foo = { - "๐ŸŒท": "bar", // 2 code points - "๐ŸŽ": "baz", // 2 code points - "๐Ÿ‡ฎ๐Ÿ‡ณ": "qux", // 4 code points - "๐Ÿณ๏ธโ€๐ŸŒˆ": "xyz", // 6 code points + "๐ŸŒท": "bar", // 1 grapheme, 1 code point, 2 code units + "๐ŸŽ": "baz", // 1 grapheme, 1 code point, 2 code units + "๐Ÿ‡ฎ๐Ÿ‡ณ": "qux", // 1 grapheme, 2 code points, 4 code units + "๐Ÿณ๏ธโ€๐ŸŒˆ": "xyz", // 1 grapheme, 4 code points, 6 code units }; `, options: [{ @@ -2467,18 +2467,18 @@ ruleTester.run("key-spacing", rule, { { code: ` var foo = { - "๐ŸŒท": "bar", // 2 code points - "๐ŸŽ": "baz", // 2 code points - "๐Ÿ‡ฎ๐Ÿ‡ณ": "qux", // 4 code points - "๐Ÿณ๏ธโ€๐ŸŒˆ": "xyz", // 6 code points + "๐ŸŒท": "bar", // 1 grapheme, 1 code point, 2 code units + "๐ŸŽ": "baz", // 1 grapheme, 1 code point, 2 code units + "๐Ÿ‡ฎ๐Ÿ‡ณ": "qux", // 1 grapheme, 2 code points, 4 code units + "๐Ÿณ๏ธโ€๐ŸŒˆ": "xyz", // 1 grapheme, 4 code points, 6 code units }; `, output: ` var foo = { - "๐ŸŒท": "bar", // 2 code points - "๐ŸŽ": "baz", // 2 code points - "๐Ÿ‡ฎ๐Ÿ‡ณ": "qux", // 4 code points - "๐Ÿณ๏ธโ€๐ŸŒˆ": "xyz", // 6 code points + "๐ŸŒท": "bar", // 1 grapheme, 1 code point, 2 code units + "๐ŸŽ": "baz", // 1 grapheme, 1 code point, 2 code units + "๐Ÿ‡ฎ๐Ÿ‡ณ": "qux", // 1 grapheme, 2 code points, 4 code units + "๐Ÿณ๏ธโ€๐ŸŒˆ": "xyz", // 1 grapheme, 4 code points, 6 code units }; `, options: [{ From b8448ff1ae1adf26a81dea07f340caa5b5c2f257 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Tue, 23 May 2023 15:57:44 +0530 Subject: [PATCH 002/248] feat: correct no-useless-return behaviour in try statements (#16996) * feat: correct `no-useless-return` behaviour in try statements * test: add more test cases for no-useless-return * refactor: fix formatting * refactor: fix formatting * fix: check all try block statements in the list * Fix a false positive --------- Co-authored-by: Francesco Trotta --- lib/rules/no-useless-return.js | 42 +++++-- tests/lib/rules/no-useless-return.js | 176 +++++++++++++++++++++++++-- 2 files changed, 198 insertions(+), 20 deletions(-) diff --git a/lib/rules/no-useless-return.js b/lib/rules/no-useless-return.js index db1ccae97393..f89523153d47 100644 --- a/lib/rules/no-useless-return.js +++ b/lib/rules/no-useless-return.js @@ -82,7 +82,6 @@ module.exports = { create(context) { const segmentInfoMap = new WeakMap(); - const usedUnreachableSegments = new WeakSet(); const sourceCode = context.sourceCode; let scopeInfo = null; @@ -152,24 +151,44 @@ module.exports = { * This behavior would simulate code paths for the case that the return * statement does not exist. * @param {CodePathSegment} segment The segment to get return statements. + * @param {Set} usedUnreachableSegments A set of segments that have already been traversed in this call. * @returns {void} */ - function markReturnStatementsOnSegmentAsUsed(segment) { + function markReturnStatementsOnSegmentAsUsed(segment, usedUnreachableSegments) { if (!segment.reachable) { usedUnreachableSegments.add(segment); segment.allPrevSegments .filter(isReturned) .filter(prevSegment => !usedUnreachableSegments.has(prevSegment)) - .forEach(markReturnStatementsOnSegmentAsUsed); + .forEach(prevSegment => markReturnStatementsOnSegmentAsUsed(prevSegment, usedUnreachableSegments)); return; } const info = segmentInfoMap.get(segment); - for (const node of info.uselessReturns) { + info.uselessReturns = info.uselessReturns.filter(node => { + if (scopeInfo.traversedTryBlockStatements && scopeInfo.traversedTryBlockStatements.length > 0) { + const returnInitialRange = node.range[0]; + const returnFinalRange = node.range[1]; + + const areBlocksInRange = scopeInfo.traversedTryBlockStatements.some(tryBlockStatement => { + const blockInitialRange = tryBlockStatement.range[0]; + const blockFinalRange = tryBlockStatement.range[1]; + + return ( + returnInitialRange >= blockInitialRange && + returnFinalRange <= blockFinalRange + ); + }); + + if (areBlocksInRange) { + return true; + } + } + remove(scopeInfo.uselessReturns, node); - } - info.uselessReturns = []; + return false; + }); } /** @@ -188,7 +207,7 @@ module.exports = { scopeInfo .codePath .currentSegments - .forEach(markReturnStatementsOnSegmentAsUsed); + .forEach(segment => markReturnStatementsOnSegmentAsUsed(segment, new Set())); } //---------------------------------------------------------------------- @@ -202,6 +221,7 @@ module.exports = { scopeInfo = { upper: scopeInfo, uselessReturns: [], + traversedTryBlockStatements: [], codePath }; }, @@ -275,6 +295,14 @@ module.exports = { scopeInfo.uselessReturns.push(node); }, + "TryStatement > BlockStatement.block:exit"(node) { + scopeInfo.traversedTryBlockStatements.push(node); + }, + + "TryStatement:exit"() { + scopeInfo.traversedTryBlockStatements.pop(); + }, + /* * Registers for all statement nodes except FunctionDeclaration, BlockStatement, BreakStatement. * Removes return statements of the current segments from the useless return statement list. diff --git a/tests/lib/rules/no-useless-return.js b/tests/lib/rules/no-useless-return.js index 434700cc2d80..5b090dbb3656 100644 --- a/tests/lib/rules/no-useless-return.js +++ b/tests/lib/rules/no-useless-return.js @@ -19,7 +19,6 @@ const rule = require("../../../lib/rules/no-useless-return"), const ruleTester = new RuleTester(); ruleTester.run("no-useless-return", rule, { - valid: [ "function foo() { return 5; }", "function foo() { return null; }", @@ -96,6 +95,26 @@ ruleTester.run("no-useless-return", rule, { } } `, + ` + function foo() { + try { + bar(); + return; + } catch (err) {} + baz(); + } + `, + ` + function foo() { + if (something) { + try { + bar(); + return; + } catch (err) {} + } + baz(); + } + `, ` function foo() { return; @@ -176,6 +195,19 @@ ruleTester.run("no-useless-return", rule, { } console.log(arg); } + `, + + // https://github.com/eslint/eslint/pull/16996#discussion_r1138622844 + ` + function foo() { + try { + bar(); + return; + } finally { + baz(); + } + qux(); + } ` ], @@ -386,12 +418,120 @@ ruleTester.run("no-useless-return", rule, { } ` }, - - /* - * FIXME: Re-add this case (removed due to https://github.com/eslint/eslint/issues/7481): - * https://github.com/eslint/eslint/blob/261d7287820253408ec87c344beccdba2fe829a4/tests/lib/rules/no-useless-return.js#L308-L329 - */ - + { + code: ` + function foo() { + try { + foo(); + return; + } catch (err) { + return 5; + } + } + `, + output: ` + function foo() { + try { + foo(); + + } catch (err) { + return 5; + } + } + ` + }, + { + code: ` + function foo() { + if (something) { + try { + bar(); + return; + } catch (err) {} + } + } + `, + output: ` + function foo() { + if (something) { + try { + bar(); + + } catch (err) {} + } + } + ` + }, + { + code: ` + function foo() { + try { + return; + } catch (err) { + foo(); + } + } + `, + output: ` + function foo() { + try { + + } catch (err) { + foo(); + } + } + ` + }, + { + code: ` + function foo() { + try { + return; + } finally { + bar(); + } + } + `, + output: ` + function foo() { + try { + + } finally { + bar(); + } + } + ` + }, + { + code: ` + function foo() { + try { + bar(); + } catch (e) { + try { + baz(); + return; + } catch (e) { + qux(); + } + } + } + `, + output: ` + function foo() { + try { + bar(); + } catch (e) { + try { + baz(); + + } catch (e) { + qux(); + } + } + } + ` + }, { code: ` function foo() { @@ -438,11 +578,21 @@ ruleTester.run("no-useless-return", rule, { { code: "function foo() { return; return; }", output: "function foo() { return; }", - errors: [{ - messageId: "unnecessaryReturn", - type: "ReturnStatement", - column: 18 - }] + errors: [ + { + messageId: "unnecessaryReturn", + type: "ReturnStatement", + column: 18 + } + ] } - ].map(invalidCase => Object.assign({ errors: [{ messageId: "unnecessaryReturn", type: "ReturnStatement" }] }, invalidCase)) + ].map(invalidCase => + Object.assign( + { + errors: [ + { messageId: "unnecessaryReturn", type: "ReturnStatement" } + ] + }, + invalidCase + )) }); From 5b68d51e3e6bd003d6cf74d3434f7165691b4f4d Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 23 May 2023 13:01:08 +0200 Subject: [PATCH 003/248] chore: Fix `fixedsize` attribute in code path analysis DOT debug output (#17202) --- lib/linter/code-path-analysis/debug-helpers.js | 2 +- tests/fixtures/code-path-analysis/if-4.js | 2 +- tests/fixtures/code-path-analysis/try--try-catch-2.js | 2 +- tests/fixtures/code-path-analysis/try--try-catch-4.js | 2 +- tests/fixtures/code-path-analysis/try--try-finally-1.js | 2 +- tests/fixtures/code-path-analysis/try--try-finally-3.js | 2 +- tests/fixtures/code-path-analysis/try--try-finally-4.js | 2 +- tests/fixtures/code-path-analysis/try--try-finally-5.js | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/linter/code-path-analysis/debug-helpers.js b/lib/linter/code-path-analysis/debug-helpers.js index e06b6cde5f1d..c0e01a8248b4 100644 --- a/lib/linter/code-path-analysis/debug-helpers.js +++ b/lib/linter/code-path-analysis/debug-helpers.js @@ -109,7 +109,7 @@ module.exports = { text += "final[label=\"\",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];\n"; } if (codePath.thrownSegments.length > 0) { - text += "thrown[label=\"โœ˜\",shape=circle,width=0.3,height=0.3,fixedsize];\n"; + text += "thrown[label=\"โœ˜\",shape=circle,width=0.3,height=0.3,fixedsize=true];\n"; } const traceMap = Object.create(null); diff --git a/tests/fixtures/code-path-analysis/if-4.js b/tests/fixtures/code-path-analysis/if-4.js index def278a43c4e..6229eddcbd00 100644 --- a/tests/fixtures/code-path-analysis/if-4.js +++ b/tests/fixtures/code-path-analysis/if-4.js @@ -16,7 +16,7 @@ digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; - thrown[label="โœ˜",shape=circle,width=0.3,height=0.3,fixedsize]; + thrown[label="โœ˜",shape=circle,width=0.3,height=0.3,fixedsize=true]; s1_1[label="Program\nIfStatement\nIdentifier (a)"]; s1_2[label="BlockStatement\nReturnStatement\nLiteral (0)"]; s1_3[label="BlockStatement:exit"]; diff --git a/tests/fixtures/code-path-analysis/try--try-catch-2.js b/tests/fixtures/code-path-analysis/try--try-catch-2.js index e6e86b4a4f3e..d9b97979969e 100644 --- a/tests/fixtures/code-path-analysis/try--try-catch-2.js +++ b/tests/fixtures/code-path-analysis/try--try-catch-2.js @@ -16,7 +16,7 @@ digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; - thrown[label="โœ˜",shape=circle,width=0.3,height=0.3,fixedsize]; + thrown[label="โœ˜",shape=circle,width=0.3,height=0.3,fixedsize=true]; s1_1[label="Program\nTryStatement\nBlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)"]; s1_2[label="CallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; s1_3[label="CatchClause\nIdentifier (err)\nBlockStatement\nThrowStatement\nIdentifier (err)"]; diff --git a/tests/fixtures/code-path-analysis/try--try-catch-4.js b/tests/fixtures/code-path-analysis/try--try-catch-4.js index b96d220a0c5a..cb9f4b2d8833 100644 --- a/tests/fixtures/code-path-analysis/try--try-catch-4.js +++ b/tests/fixtures/code-path-analysis/try--try-catch-4.js @@ -29,7 +29,7 @@ digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; - thrown[label="โœ˜",shape=circle,width=0.3,height=0.3,fixedsize]; + thrown[label="โœ˜",shape=circle,width=0.3,height=0.3,fixedsize=true]; s1_1[label="Program\nTryStatement\nBlockStatement\nTryStatement\nBlockStatement\nIfStatement\nIdentifier (a)"]; s1_3[label="BlockStatement\nThrowStatement\nIdentifier (err)"]; s1_4[style="rounded,dashed,filled",fillcolor="#FF9800",label="<>\nBlockStatement:exit"]; diff --git a/tests/fixtures/code-path-analysis/try--try-finally-1.js b/tests/fixtures/code-path-analysis/try--try-finally-1.js index dcaa74d3dcb0..94374220394b 100644 --- a/tests/fixtures/code-path-analysis/try--try-finally-1.js +++ b/tests/fixtures/code-path-analysis/try--try-finally-1.js @@ -19,7 +19,7 @@ digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; - thrown[label="โœ˜",shape=circle,width=0.3,height=0.3,fixedsize]; + thrown[label="โœ˜",shape=circle,width=0.3,height=0.3,fixedsize=true]; s1_1[label="Program\nTryStatement\nBlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)"]; s1_2[label="CallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; s1_3[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nExpressionStatement\nCallExpression\nIdentifier (last)"]; diff --git a/tests/fixtures/code-path-analysis/try--try-finally-3.js b/tests/fixtures/code-path-analysis/try--try-finally-3.js index b141013076af..7ecc766c4503 100644 --- a/tests/fixtures/code-path-analysis/try--try-finally-3.js +++ b/tests/fixtures/code-path-analysis/try--try-finally-3.js @@ -17,7 +17,7 @@ last(); digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; - thrown[label="โœ˜",shape=circle,width=0.3,height=0.3,fixedsize]; + thrown[label="โœ˜",shape=circle,width=0.3,height=0.3,fixedsize=true]; s1_1[label="Program\nTryStatement\nBlockStatement\nThrowStatement\nIdentifier (err)"]; s1_2[label="ThrowStatement:exit"]; s1_3[style="rounded,dashed,filled",fillcolor="#FF9800",label="<>\nBlockStatement:exit"]; diff --git a/tests/fixtures/code-path-analysis/try--try-finally-4.js b/tests/fixtures/code-path-analysis/try--try-finally-4.js index 8fb73a39f80d..4408e1a584d8 100644 --- a/tests/fixtures/code-path-analysis/try--try-finally-4.js +++ b/tests/fixtures/code-path-analysis/try--try-finally-4.js @@ -32,7 +32,7 @@ digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; - thrown[label="โœ˜",shape=circle,width=0.3,height=0.3,fixedsize]; + thrown[label="โœ˜",shape=circle,width=0.3,height=0.3,fixedsize=true]; s1_1[label="Program\nTryStatement\nBlockStatement\nTryStatement\nBlockStatement\nIfStatement\nIdentifier (a)"]; s1_3[label="BlockStatement\nReturnStatement"]; s1_4[style="rounded,dashed,filled",fillcolor="#FF9800",label="<>\nBlockStatement:exit"]; diff --git a/tests/fixtures/code-path-analysis/try--try-finally-5.js b/tests/fixtures/code-path-analysis/try--try-finally-5.js index 0081ed8777e3..ecf8a6ec5f59 100644 --- a/tests/fixtures/code-path-analysis/try--try-finally-5.js +++ b/tests/fixtures/code-path-analysis/try--try-finally-5.js @@ -31,7 +31,7 @@ digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; - thrown[label="โœ˜",shape=circle,width=0.3,height=0.3,fixedsize]; + thrown[label="โœ˜",shape=circle,width=0.3,height=0.3,fixedsize=true]; s1_1[label="Program\nTryStatement\nBlockStatement\nTryStatement\nBlockStatement\nIfStatement\nIdentifier (a)"]; s1_3[label="BlockStatement\nReturnStatement"]; s1_4[style="rounded,dashed,filled",fillcolor="#FF9800",label="<>\nBlockStatement:exit"]; From c4fad173c7149dbcd25695c19c68663102b9ec6b Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Wed, 24 May 2023 19:12:56 +0200 Subject: [PATCH 004/248] fix: Correct ignore message for "node_modules" subfolders (#17217) * fix: Correct ignore message for "node_modules" subfolders * Use `path.sep` as a separator Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- lib/eslint/eslint-helpers.js | 2 +- .../cli-engine/node_modules_cleaner.js | 1 + tests/lib/eslint/flat-eslint.js | 40 ++++++++++++++++++- 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/cli-engine/node_modules_cleaner.js diff --git a/lib/eslint/eslint-helpers.js b/lib/eslint/eslint-helpers.js index ff3695dc644f..9bfd751cb2b6 100644 --- a/lib/eslint/eslint-helpers.js +++ b/lib/eslint/eslint-helpers.js @@ -591,7 +591,7 @@ function isErrorMessage(message) { */ function createIgnoreResult(filePath, baseDir) { let message; - const isInNodeModules = baseDir && path.relative(baseDir, filePath).startsWith("node_modules"); + const isInNodeModules = baseDir && path.dirname(path.relative(baseDir, filePath)).split(path.sep).includes("node_modules"); if (isInNodeModules) { message = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override."; diff --git a/tests/fixtures/cli-engine/node_modules_cleaner.js b/tests/fixtures/cli-engine/node_modules_cleaner.js new file mode 100644 index 000000000000..ca451e212a7d --- /dev/null +++ b/tests/fixtures/cli-engine/node_modules_cleaner.js @@ -0,0 +1 @@ +// not implemented diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index 00a906b0fb73..2e23e4db2cd5 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -1191,7 +1191,7 @@ describe("FlatESLint", () => { describe("Ignoring Files", () => { - it("should report on all files passed explicitly, even if ignored by default", async () => { + it("should report on a file in the node_modules folder passed explicitly, even if ignored by default", async () => { eslint = new FlatESLint({ cwd: getFixturePath("cli-engine") }); @@ -1204,6 +1204,44 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].fatalErrorCount, 0); assert.strictEqual(results[0].fixableErrorCount, 0); assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should report on a file in a node_modules subfolder passed explicitly, even if ignored by default", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("cli-engine") + }); + const results = await eslint.lintFiles(["nested_node_modules/subdir/node_modules/text.js"]); + const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override."; + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should report on an ignored file with \"node_modules\" in its name", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("cli-engine"), + ignorePatterns: ["*.js"] + }); + const results = await eslint.lintFiles(["node_modules_cleaner.js"]); + const expectedMsg = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."; + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].severity, 1); assert.strictEqual(results[0].messages[0].message, expectedMsg); assert.strictEqual(results[0].suppressedMessages.length, 0); }); From e5182b723ff82bb3b55c50c06d64626055414b31 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Fri, 26 May 2023 08:06:32 +0000 Subject: [PATCH 005/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 50602564541f..d722d2c933b1 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

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

+

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

## Technology Sponsors From f67d2984c3c3f26497842a04d5166707587c1fca Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 29 May 2023 20:48:12 +0200 Subject: [PATCH 006/248] test: Add `FlatESLint` tests with missing config files (#17164) * test: Add `FlatESLint` tests with missing config files * assert ENOENT --- tests/lib/eslint/flat-eslint.js | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index 2e23e4db2cd5..24c3ab5a6c9e 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -682,6 +682,37 @@ describe("FlatESLint", () => { ); }); + it("should throw if eslint.config.js file is not present", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("..") + }); + await assert.rejects(() => eslint.lintText("var foo = 'bar';"), /Could not find config file/u); + }); + + it("should not throw if eslint.config.js file is not present and overrideConfigFile is `true`", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath(".."), + overrideConfigFile: true + }); + await eslint.lintText("var foo = 'bar';"); + }); + + it("should not throw if eslint.config.js file is not present and overrideConfigFile is path to a config file", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath(".."), + overrideConfigFile: "fixtures/configurations/quotes-error.js" + }); + await eslint.lintText("var foo = 'bar';"); + }); + + it("should throw if overrideConfigFile is path to a file that doesn't exist", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath(""), + overrideConfigFile: "does-not-exist.js" + }); + await assert.rejects(() => eslint.lintText("var foo = 'bar';"), { code: "ENOENT" }); + }); + it("should throw if non-string value is given to 'code' parameter", async () => { eslint = new FlatESLint(); await assert.rejects(() => eslint.lintText(100), /'code' must be a string/u); @@ -797,6 +828,37 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].suppressedMessages.length, 0); }); + it("should throw if eslint.config.js file is not present", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("..") + }); + await assert.rejects(() => eslint.lintFiles("fixtures/undef*.js"), /Could not find config file/u); + }); + + it("should not throw if eslint.config.js file is not present and overrideConfigFile is `true`", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath(".."), + overrideConfigFile: true + }); + await eslint.lintFiles("fixtures/undef*.js"); + }); + + it("should not throw if eslint.config.js file is not present and overrideConfigFile is path to a config file", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath(".."), + overrideConfigFile: "fixtures/configurations/quotes-error.js" + }); + await eslint.lintFiles("fixtures/undef*.js"); + }); + + it("should throw if overrideConfigFile is path to a file that doesn't exist", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath(), + overrideConfigFile: "does-not-exist.js" + }); + await assert.rejects(() => eslint.lintFiles("undef*.js"), { code: "ENOENT" }); + }); + it("should throw an error when given a config file and a valid file and invalid parser", async () => { eslint = new FlatESLint({ overrideConfig: { From 01d7142642c87241135699571e8010f5e8fcda4f Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Wed, 31 May 2023 08:06:19 +0000 Subject: [PATCH 007/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d722d2c933b1..3a98714268ab 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

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

+

King Billy Slots ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

## Technology Sponsors From a5896360c3faa1e7d1fe81a9907a434b8b8f6b60 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 2 Jun 2023 20:58:09 +0200 Subject: [PATCH 008/248] fix: Config with `ignores` and without `files` should not always apply (#17181) * fix: Config with `ignores` and without `files` should not always apply Fixes #17103 * use @humanwhocodes/config-array@0.11.9 * use @humanwhocodes/config-array@0.11.10 --- package.json | 2 +- tests/lib/eslint/flat-eslint.js | 93 +++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ae6e8b35fb8b..6b08af451a9f 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.3", "@eslint/js": "8.41.0", - "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index 24c3ab5a6c9e..b68dabc2837c 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -4631,6 +4631,99 @@ describe("FlatESLint", () => { }); }); + describe("configs with 'ignores' and without 'files'", () => { + + // https://github.com/eslint/eslint/issues/17103 + describe("config with ignores: ['error.js']", () => { + const cwd = getFixturePath("config-with-ignores-without-files"); + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd, + files: { + "eslint.config.js": `module.exports = [ + { + rules: { + "no-unused-vars": "error", + }, + }, + { + ignores: ["error.js"], + rules: { + "no-unused-vars": "warn", + }, + }, + ];`, + "error.js": "let unusedVar;", + "warn.js": "let unusedVar;" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should apply to all files except for 'error.js'", async () => { + const engine = new FlatESLint({ + cwd + }); + + 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']", () => { + const cwd = getFixturePath("config-with-ignores-without-files"); + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd, + files: { + "eslint.config.js": `module.exports = [ + { + rules: { + "no-undef": "error", + }, + }, + { + ignores: ["**/*.json"], + rules: { + "no-unused-vars": "error", + }, + }, + ];`, + "foo.js": "", + "foo.json": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should not add json files as lint targets", async () => { + const engine = new FlatESLint({ + cwd + }); + + const results = await engine.lintFiles("foo*"); + + // should not lint `foo.json` + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, path.join(getPath(), "foo.js")); + }); + }); + + }); + describe("with ignores config", () => { const root = getFixturePath("cli-engine/ignore-patterns"); From 0892412556b2ba6c3d1b85152dafe47a3f4cba72 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 2 Jun 2023 22:28:02 +0200 Subject: [PATCH 009/248] refactor: remove `Identifier` listener in no-irregular-whitespace (#17235) --- lib/rules/no-irregular-whitespace.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/rules/no-irregular-whitespace.js b/lib/rules/no-irregular-whitespace.js index 67519b5e851d..0badc58b87a8 100644 --- a/lib/rules/no-irregular-whitespace.js +++ b/lib/rules/no-irregular-whitespace.js @@ -100,12 +100,12 @@ module.exports = { } /** - * Checks identifier or literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors + * Checks literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors * @param {ASTNode} node to check for matching errors. * @returns {void} * @private */ - function removeInvalidNodeErrorsInIdentifierOrLiteral(node) { + function removeInvalidNodeErrorsInLiteral(node) { const shouldCheckStrings = skipStrings && (typeof node.value === "string"); const shouldCheckRegExps = skipRegExps && Boolean(node.regex); @@ -237,8 +237,7 @@ module.exports = { checkForIrregularLineTerminators(node); }; - nodes.Identifier = removeInvalidNodeErrorsInIdentifierOrLiteral; - nodes.Literal = removeInvalidNodeErrorsInIdentifierOrLiteral; + nodes.Literal = removeInvalidNodeErrorsInLiteral; nodes.TemplateElement = skipTemplates ? removeInvalidNodeErrorsInTemplateLiteral : noop; nodes["Program:exit"] = function() { if (skipComments) { From 67fc5e730e4dfc372dea11e15d3f5165bc812491 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 2 Jun 2023 17:26:27 -0400 Subject: [PATCH 010/248] 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 5d65d79b78c8..0dce8203fe56 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "8.41.0", + "version": "8.42.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 6ca5b7ca3bac9e10c6cfee4cdc78446e94eb7607 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 2 Jun 2023 23:51:53 +0200 Subject: [PATCH 011/248] chore: upgrade @eslint/js@8.42.0 (#17236) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6b08af451a9f..f210905b760e 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.41.0", + "@eslint/js": "8.42.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", From 40b34afd01045ab6ec5ed91573d0e11e5aec8c97 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 2 Jun 2023 19:46:58 -0400 Subject: [PATCH 012/248] Build: changelog update for 8.42.0 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a103b11c6e53..0e4ac13f0245 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +v8.42.0 - June 2, 2023 + +* [`6ca5b7c`](https://github.com/eslint/eslint/commit/6ca5b7ca3bac9e10c6cfee4cdc78446e94eb7607) chore: upgrade @eslint/js@8.42.0 (#17236) (Milos Djermanovic) +* [`67fc5e7`](https://github.com/eslint/eslint/commit/67fc5e730e4dfc372dea11e15d3f5165bc812491) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`0892412`](https://github.com/eslint/eslint/commit/0892412556b2ba6c3d1b85152dafe47a3f4cba72) refactor: remove `Identifier` listener in no-irregular-whitespace (#17235) (Milos Djermanovic) +* [`a589636`](https://github.com/eslint/eslint/commit/a5896360c3faa1e7d1fe81a9907a434b8b8f6b60) fix: Config with `ignores` and without `files` should not always apply (#17181) (Milos Djermanovic) +* [`01d7142`](https://github.com/eslint/eslint/commit/01d7142642c87241135699571e8010f5e8fcda4f) docs: Update README (GitHub Actions Bot) +* [`f67d298`](https://github.com/eslint/eslint/commit/f67d2984c3c3f26497842a04d5166707587c1fca) test: Add `FlatESLint` tests with missing config files (#17164) (Milos Djermanovic) +* [`e5182b7`](https://github.com/eslint/eslint/commit/e5182b723ff82bb3b55c50c06d64626055414b31) docs: Update README (GitHub Actions Bot) +* [`c4fad17`](https://github.com/eslint/eslint/commit/c4fad173c7149dbcd25695c19c68663102b9ec6b) fix: Correct ignore message for "node_modules" subfolders (#17217) (Francesco Trotta) +* [`5b68d51`](https://github.com/eslint/eslint/commit/5b68d51e3e6bd003d6cf74d3434f7165691b4f4d) chore: Fix `fixedsize` attribute in code path analysis DOT debug output (#17202) (Milos Djermanovic) +* [`b8448ff`](https://github.com/eslint/eslint/commit/b8448ff1ae1adf26a81dea07f340caa5b5c2f257) feat: correct no-useless-return behaviour in try statements (#16996) (Nitin Kumar) +* [`37432f2`](https://github.com/eslint/eslint/commit/37432f27dc15817d66cf42377792197dc2aeb8b2) chore: update descriptions in key-spacing tests (#17195) (Milos Djermanovic) + v8.41.0 - May 19, 2023 * [`f43216a`](https://github.com/eslint/eslint/commit/f43216a8c77ab6cf1d0823978e8c728786b4cba7) chore: upgrade @eslint/js@8.41.0 (#17200) (Milos Djermanovic) From 96ad0dbc5e0072e40004ee7d938b576ffcb8af8d Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 2 Jun 2023 19:46:59 -0400 Subject: [PATCH 013/248] 8.42.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 7be17b974369..cefdc39ca13b 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.41.0", + "version": "8.42.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 1bc28e193893..1cb369185950 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

- 9 problems (5 errors, 4 warnings) - Generated on Fri May 19 2023 16:52:13 GMT-0400 (Eastern Daylight Time) + 9 problems (5 errors, 4 warnings) - Generated on Fri Jun 02 2023 19:47:00 GMT-0400 (Eastern Daylight Time)
diff --git a/package.json b/package.json index f210905b760e..7a34beb8fa7a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.41.0", + "version": "8.42.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 03fc4aa847bd0445e7b3ea81bcc9523b1847facc Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Sat, 3 Jun 2023 08:06:28 +0000 Subject: [PATCH 014/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a98714268ab..ace716e4924d 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

King Billy Slots ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

+

BairesDev King Billy Slots ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

## Technology Sponsors From 3ef58140550cf8ff34af35fc4d9a1f9a124fe0e6 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 5 Jun 2023 22:48:12 +0200 Subject: [PATCH 015/248] docs: Revert all changes after the license change (#17227) --- docs/src/extend/code-path-analysis.md | 3 +- docs/src/extend/custom-processors.md | 10 +- docs/src/extend/custom-rules.md | 495 ++++++++---------- docs/src/extend/plugins.md | 30 +- docs/src/integrate/nodejs-api.md | 4 +- docs/src/maintain/manage-issues.md | 20 +- docs/src/rules/callback-return.md | 2 +- docs/src/rules/global-require.md | 2 +- docs/src/rules/handle-callback-err.md | 2 +- docs/src/rules/id-match.md | 4 +- docs/src/rules/no-buffer-constructor.md | 2 +- docs/src/rules/no-div-regex.md | 4 +- docs/src/rules/no-mixed-requires.md | 2 +- docs/src/rules/no-new-require.md | 2 +- docs/src/rules/no-new.md | 2 +- docs/src/rules/no-path-concat.md | 2 +- docs/src/rules/no-process-env.md | 2 +- docs/src/rules/no-process-exit.md | 2 +- docs/src/rules/no-restricted-modules.md | 2 +- docs/src/rules/no-sync.md | 2 +- docs/src/rules/semi.md | 77 +-- .../use/configure/configuration-files-new.md | 36 +- docs/src/use/configure/configuration-files.md | 4 - docs/src/use/configure/language-options.md | 4 +- docs/src/use/formatters/index.md | 2 +- docs/src/use/integrations.md | 21 +- docs/src/use/migrate-to-8.0.0.md | 10 +- docs/src/use/migrating-to-5.0.0.md | 2 +- docs/src/use/migrating-to-7.0.0.md | 2 +- 29 files changed, 306 insertions(+), 446 deletions(-) diff --git a/docs/src/extend/code-path-analysis.md b/docs/src/extend/code-path-analysis.md index 7344f8647adf..6cb7d2ac571e 100644 --- a/docs/src/extend/code-path-analysis.md +++ b/docs/src/extend/code-path-analysis.md @@ -259,8 +259,7 @@ Please use a map of information instead. ```js function hasCb(node, context) { if (node.type.indexOf("Function") !== -1) { - const sourceCode = context.sourceCode; - return sourceCode.getDeclaredVariables(node).some(function(v) { + return context.getDeclaredVariables(node).some(function(v) { return v.type === "Parameter" && v.name === "cb"; }); } diff --git a/docs/src/extend/custom-processors.md b/docs/src/extend/custom-processors.md index d112d7246885..8f330883b03b 100644 --- a/docs/src/extend/custom-processors.md +++ b/docs/src/extend/custom-processors.md @@ -59,19 +59,19 @@ Reported problems have the following location information in each lint message: type LintMessage = { /// The 1-based line number where the message occurs. - line?: number; + line: number; /// The 1-based column number where the message occurs. - column?: number; + column: number; /// The 1-based line number of the end location. - endLine?: number; + endLine: number; /// The 1-based column number of the end location. - endColumn?: number; + endColumn: number; /// If `true`, this is a fatal error. - fatal?: boolean; + fatal: boolean; /// Information for an autofix. fix: Fix; diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index 35b04fdbf8e4..0118757c6a4d 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -34,43 +34,44 @@ module.exports = { }; ``` -## Rule Structure +## Rule Basics -The source file for a rule exports an object with the following properties. Both custom rules and core rules follow this format. +The source file for a rule exports an object with the following properties. -`meta`: (`object`) Contains metadata for the rule: +`meta` (object) contains metadata for the rule: -* `type`: (`string`) Indicates the type of rule, which is one of `"problem"`, `"suggestion"`, or `"layout"`: +* `type` (string) indicates the type of rule, which is one of `"problem"`, `"suggestion"`, or `"layout"`: + * `"problem"` means the rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. + * `"suggestion"` means the rule is identifying something that could be done in a better way but no errors will occur if the code isn't changed. + * `"layout"` means the rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren't specified in the AST. - * `"problem"`: The rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. - * `"suggestion"`: The rule is identifying something that could be done in a better way but no errors will occur if the code isn't changed. - * `"layout"`: The rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren't specified in the AST. +* `docs` (object) is required for core rules of ESLint: -* `docs`: (`object`) Required for core rules and optional for custom rules. Core rules have specific entries inside of `docs` while custom rules can include any properties that you need. The following properties are only relevant when working on core rules. + * `description` (string) provides the short description of the rule in the [rules index](../rules/) + * `recommended` (boolean) is whether the `"extends": "eslint:recommended"` property in a [configuration file](../use/configure/configuration-files#extending-configuration-files) enables the rule + * `url` (string) specifies the URL at which the full documentation can be accessed (enabling code editors to provide a helpful link on highlighted rule violations) - * `description`: (`string`) Provides the short description of the rule in the [rules index](../rules/). - * `recommended`: (`boolean`) Specifies whether the `"extends": "eslint:recommended"` property in a [configuration file](../use/configure/configuration-files#extending-configuration-files) enables the rule. - * `url`: (`string`) Specifies the URL at which the full documentation can be accessed (enabling code editors to provide a helpful link on highlighted rule violations). + In a custom rule or plugin, you can omit `docs` or include any properties that you need in it. -* `fixable`: (`string`) Either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../use/command-line-interface#--fix) automatically fixes problems reported by the rule. +* `fixable` (string) is either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../use/command-line-interface#--fix) automatically fixes problems reported by the rule - **Important:** the `fixable` property is mandatory for fixable rules. If this property isn't specified, ESLint will throw an error whenever the rule attempts to produce a fix. Omit the `fixable` property if the rule is not fixable. + **Important:** the `fixable` property is mandatory for fixable rules. If this property isn't specified, ESLint will throw an error whenever the rule attempts to produce a fix. Omit the `fixable` property if the rule is not fixable. -* `hasSuggestions`: (`boolean`) Specifies whether rules can return suggestions (defaults to `false` if omitted). +* `hasSuggestions` (boolean) specifies whether rules can return suggestions (defaults to `false` if omitted) - **Important:** the `hasSuggestions` property is mandatory for rules that provide suggestions. If this property isn't set to `true`, ESLint will throw an error whenever the rule attempts to produce a suggestion. Omit the `hasSuggestions` property if the rule does not provide suggestions. + **Important:** the `hasSuggestions` property is mandatory for rules that provide suggestions. If this property isn't set to `true`, ESLint will throw an error whenever the rule attempts to produce a suggestion. Omit the `hasSuggestions` property if the rule does not provide suggestions. -* `schema`: (`object | array`) Specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../use/configure/rules). +* `schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../use/configure/rules) -* `deprecated`: (`boolean`) Indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated. +* `deprecated` (boolean) indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated. -* `replacedBy`: (`array`) In the case of a deprecated rule, specify replacement rule(s). +* `replacedBy` (array) in the case of a deprecated rule, specifies replacement rule(s) -`create()`: Returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code: +`create` (function) returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code: -* If a key is a node type or a [selector](./selectors), ESLint calls that **visitor** function while going **down** the tree. -* If a key is a node type or a [selector](./selectors) plus `:exit`, ESLint calls that **visitor** function while going **up** the tree. -* If a key is an event name, ESLint calls that **handler** function for [code path analysis](code-path-analysis). +* if a key is a node type or a [selector](./selectors), ESLint calls that **visitor** function while going **down** the tree +* if a key is a node type or a [selector](./selectors) plus `:exit`, ESLint calls that **visitor** function while going **up** the tree +* if a key is an event name, ESLint calls that **handler** function for [code path analysis](code-path-analysis) A rule can use the current node and its surrounding tree to report or fix problems. @@ -105,40 +106,20 @@ module.exports = { ## The Context Object -The `context` object is the only argument of the `create` method in a rule. For example: +The `context` object contains additional functionality that is helpful for rules to do their jobs. As the name implies, the `context` object contains information that is relevant to the context of the rule. The `context` object has the following properties: -```js -// customRule.js - -module.exports = { - meta: { ... }, - // `context` object is the argument - create(context) { - // ... - } -}; -``` - -As the name implies, the `context` object contains information that is relevant to the context of the rule. - -The `context` object has the following properties: - -* `id`: (`string`) The rule ID. -* `filename`: (`string`) The filename associated with the source. -* `physicalFilename`: (`string`) When linting a file, it provides the full path of the file on disk without any code block information. When linting text, it provides the value passed to `โ€”stdin-filename` or `` if not specified. -* `cwd`: (`string`) The `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. -* `options`: (`array`) An array of the [configured options](../use/configure/rules) for this rule. This array does not include the rule severity (see the [dedicated section](#accessing-options-passed-to-a-rule)). -* `sourceCode`: (`object`) A `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). -* `settings`: (`object`) The [shared settings](../use/configure/configuration-files#adding-shared-settings) from the configuration. -* `parserPath`: (`string`) The name of the `parser` from the configuration. -* `parserServices`: (`object`) Contains parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) -* `parserOptions`: The parser options configured for this run (more details [here](../use/configure/language-options#specifying-parser-options)). +* `parserOptions` - the parser options configured for this run (more details [here](../use/configure/language-options#specifying-parser-options)). +* `id` - the rule ID. +* `options` - an array of the [configured options](../use/configure/rules) for this rule. This array does not include the rule severity. For more information, see [here](#contextoptions). +* `settings` - the [shared settings](../use/configure/configuration-files#adding-shared-settings) from configuration. +* `parserPath` - the name of the `parser` from configuration. +* `parserServices` - an object containing parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) Additionally, the `context` object has the following methods: -* `getAncestors()`: (**Deprecated:** Use `SourceCode#getAncestors(node)` instead.) Returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. -* `getCwd()`: (**Deprecated:** Use `context.cwd` instead.) Returns the `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. -* `getDeclaredVariables(node)`: (**Deprecated:** Use `SourceCode#getDeclaredVariables(node)` instead.) Returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. +* `getAncestors()` - returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. +* `getCwd()` - returns the `cwd` passed to [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered as the current working directory. +* `getDeclaredVariables(node)` - returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. * If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. * If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. * If the node is a `FunctionDeclaration` or `FunctionExpression`, the variable for the function name is returned, in addition to variables for the function parameters. @@ -148,30 +129,79 @@ Additionally, the `context` object has the following methods: * If the node is an `ImportDeclaration`, variables for all of its specifiers are returned. * If the node is an `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`, the declared variable is returned. * Otherwise, if the node does not declare any variables, an empty array is returned. -* `getFilename()`: (**Deprecated:** Use `context.filename` instead.) Returns the filename associated with the source. -* `getPhysicalFilename()`: (**Deprecated:** Use `context.physicalFilename` instead.) When linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `โ€”stdin-filename` or `` if not specified. -* `getScope()`: (**Deprecated:** Use `SourceCode#getScope(node)` instead.) Returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. -* `getSourceCode()`: (**Deprecated:** Use `context.sourceCode` instead.) Returns a `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). -* `markVariableAsUsed(name)`: (**Deprecated:** Use `SourceCode#markVariableAsUsed(name, node)` instead.) Marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. -* `report(descriptor)`. Reports a problem in the code (see the [dedicated section](#reporting-problems)). +* `getFilename()` - returns the filename associated with the source. +* `getPhysicalFilename()` - when linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `โ€”stdin-filename` or `` if not specified. +* `getScope()` - returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. +* `getSourceCode()` - returns a [`SourceCode`](#contextgetsourcecode) object that you can use to work with the source that was passed to ESLint. +* `markVariableAsUsed(name)` - marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. +* `report(descriptor)` - reports a problem in the code (see the [dedicated section](#contextreport)). **Note:** Earlier versions of ESLint supported additional methods on the `context` object. Those methods were removed in the new format and should not be relied upon. -### Reporting Problems +### context.getScope() -The main method you'll use when writing custom rules is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties: +This method returns the scope of the current node. It is a useful method for finding information about the variables in a given scope, and how they are used in other scopes. -* `message`: (`string`) The problem message. -* `node`: (optional `object`) The AST node related to the problem. If present and `loc` is not specified, then the starting location of the node is used as the location of the problem. -* `loc`: (optional `object`) Specifies the location of the problem. If both `loc` and `node` are specified, then the location is used from `loc` instead of `node`. - * `start`: An object of the start location. - * `line`: (`number`) The 1-based line number at which the problem occurred. - * `column`: (`number`) The 0-based column number at which the problem occurred. - * `end`: An object of the end location. - * `line`: (`number`) The 1-based line number at which the problem occurred. - * `column`: (`number`) The 0-based column number at which the problem occurred. -* `data`: (optional `object`) [Placeholder](#using-message-placeholders) data for `message`. -* `fix(fixer)`: (optional `function`) Applies a [fix](#applying-fixes) to resolve the problem. +#### Scope types + +The following table contains a list of AST node types and the scope type that they correspond to. For more information about the scope types, refer to the [`Scope` object documentation](./scope-manager-interface#scope-interface). + +| AST Node Type | Scope Type | +|:--------------------------|:-----------| +| `Program` | `global` | +| `FunctionDeclaration` | `function` | +| `FunctionExpression` | `function` | +| `ArrowFunctionExpression` | `function` | +| `ClassDeclaration` | `class` | +| `ClassExpression` | `class` | +| `BlockStatement` โ€ป1 | `block` | +| `SwitchStatement` โ€ป1 | `switch` | +| `ForStatement` โ€ป2 | `for` | +| `ForInStatement` โ€ป2 | `for` | +| `ForOfStatement` โ€ป2 | `for` | +| `WithStatement` | `with` | +| `CatchClause` | `catch` | +| others | โ€ป3 | + +**โ€ป1** Only if the configured parser provided the block-scope feature. The default parser provides the block-scope feature if `parserOptions.ecmaVersion` is not less than `6`.
+**โ€ป2** Only if the `for` statement defines the iteration variable as a block-scoped variable (E.g., `for (let i = 0;;) {}`).
+**โ€ป3** The scope of the closest ancestor node which has own scope. If the closest ancestor node has multiple scopes then it chooses the innermost scope (E.g., the `Program` node has a `global` scope and a `module` scope if `Program#sourceType` is `"module"`. The innermost scope is the `module` scope.). + +#### Scope Variables + +The `Scope#variables` property contains an array of [`Variable` objects](./scope-manager-interface#variable-interface). These are the variables declared in current scope. You can use these `Variable` objects to track references to a variable throughout the entire module. + +Inside of each `Variable`, the `Variable#references` property contains an array of [`Reference` objects](./scope-manager-interface#reference-interface). The `Reference` array contains all the locations where the variable is referenced in the module's source code. + +Also inside of each `Variable`, the `Variable#defs` property contains an array of [`Definition` objects](./scope-manager-interface#definition-interface). You can use the `Definitions` to find where the variable was defined. + +Global variables have the following additional properties: + +* `Variable#writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only. +* `Variable#eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file. +* `Variable#eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments. +* `Variable#eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments. + +For examples of using `context.getScope()` to track variables, refer to the source code for the following built-in rules: + +* [no-shadow](https://github.com/eslint/eslint/blob/main/lib/rules/no-shadow.js): Calls `context.getScopes()` at the global scope and parses all child scopes to make sure a variable name is not reused at a lower scope. ([no-shadow](../rules/no-shadow) documentation) +* [no-redeclare](https://github.com/eslint/eslint/blob/main/lib/rules/no-redeclare.js): Calls `context.getScope()` at each scope to make sure that a variable is not declared twice at that scope. ([no-redeclare](../rules/no-redeclare) documentation) + +### context.report() + +The main method you'll use is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties: + +* `message` - the problem message. +* `node` - (optional) the AST node related to the problem. If present and `loc` is not specified, then the starting location of the node is used as the location of the problem. +* `loc` - (optional) an object specifying the location of the problem. If both `loc` and `node` are specified, then the location is used from `loc` instead of `node`. + * `start` - An object of the start location. + * `line` - the 1-based line number at which the problem occurred. + * `column` - the 0-based column number at which the problem occurred. + * `end` - An object of the end location. + * `line` - the 1-based line number at which the problem occurred. + * `column` - the 0-based column number at which the problem occurred. +* `data` - (optional) [placeholder](#using-message-placeholders) data for `message`. +* `fix` - (optional) a function that applies a [fix](#applying-fixes) to resolve the problem. Note that at least one of `node` or `loc` is required. @@ -184,9 +214,9 @@ context.report({ }); ``` -The node contains all the information necessary to figure out the line and column number of the offending text as well as the source text representing the node. +The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node. -#### Using Message Placeholders +### Using message placeholders You can also use placeholders in the message and provide `data`: @@ -204,20 +234,17 @@ context.report({ Note that leading and trailing whitespace is optional in message parameters. -The node contains all the information necessary to figure out the line and column number of the offending text as well as the source text representing the node. +The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node. -#### `messageId`s +### `messageId`s Instead of typing out messages in both the `context.report()` call and your tests, you can use `messageId`s instead. This allows you to avoid retyping error messages. It also prevents errors reported in different sections of your rule from having out-of-date messages. -Rule file: - ```js {% raw %} -// avoid-name.js - +// in your rule module.exports = { meta: { messages: { @@ -240,28 +267,18 @@ module.exports = { }; } }; -{% endraw %} -``` - -In the file to lint: -```javascript -// someFile.js +// in the file to lint: var foo = 2; // ^ error: Avoid using variables named 'foo' -``` - -In your tests: -```javascript -// avoid-name.test.js - -var rule = require("../../../lib/rules/avoid-name"); +// In your tests: +var rule = require("../../../lib/rules/my-rule"); var RuleTester = require("eslint").RuleTester; var ruleTester = new RuleTester(); -ruleTester.run("avoid-name", rule, { +ruleTester.run("my-rule", rule, { valid: ["bar", "baz"], invalid: [ { @@ -274,9 +291,10 @@ ruleTester.run("avoid-name", rule, { } ] }); +{% endraw %} ``` -#### Applying Fixes +### Applying Fixes If you'd like ESLint to attempt to fix the problem you're reporting, you can do so by specifying the `fix` function when using `context.report()`. The `fix` function receives a single argument, a `fixer` object, that you can use to apply a fix. For example: @@ -284,28 +302,28 @@ If you'd like ESLint to attempt to fix the problem you're reporting, you can do context.report({ node: node, message: "Missing semicolon", - fix(fixer) { + fix: function(fixer) { return fixer.insertTextAfter(node, ";"); } }); ``` -Here, the `fix()` function is used to insert a semicolon after the node. Note that a fix is not immediately applied, and may not be applied at all if there are conflicts with other fixes. After applying fixes, ESLint will run all the enabled rules again on the fixed code, potentially applying more fixes. This process will repeat up to 10 times, or until no more fixable problems are found. Afterward, any remaining problems will be reported as usual. +Here, the `fix()` function is used to insert a semicolon after the node. Note that a fix is not immediately applied, and may not be applied at all if there are conflicts with other fixes. After applying fixes, ESLint will run all of the enabled rules again on the fixed code, potentially applying more fixes. This process will repeat up to 10 times, or until no more fixable problems are found. Afterwards, any remaining problems will be reported as usual. -**Important:** The `meta.fixable` property is mandatory for fixable rules. ESLint will throw an error if a rule that implements `fix` functions does not [export](#rule-structure) the `meta.fixable` property. +**Important:** The `meta.fixable` property is mandatory for fixable rules. ESLint will throw an error if a rule that implements `fix` functions does not [export](#rule-basics) the `meta.fixable` property. The `fixer` object has the following methods: -* `insertTextAfter(nodeOrToken, text)`: Insert text after the given node or token. -* `insertTextAfterRange(range, text)`: Insert text after the given range. -* `insertTextBefore(nodeOrToken, text)`: Insert text before the given node or token. -* `insertTextBeforeRange(range, text)`: Insert text before the given range. -* `remove(nodeOrToken)`: Remove the given node or token. -* `removeRange(range)`: Remove text in the given range. -* `replaceText(nodeOrToken, text)`: Replace the text in the given node or token. -* `replaceTextRange(range, text)`: Replace the text in the given range. +* `insertTextAfter(nodeOrToken, text)` - inserts text after the given node or token +* `insertTextAfterRange(range, text)` - inserts text after the given range +* `insertTextBefore(nodeOrToken, text)` - inserts text before the given node or token +* `insertTextBeforeRange(range, text)` - inserts text before the given range +* `remove(nodeOrToken)` - removes the given node or token +* `removeRange(range)` - removes text in the given range +* `replaceText(nodeOrToken, text)` - replaces the text in the given node or token +* `replaceTextRange(range, text)` - replaces the text in the given range -A `range` is a two-item array containing character indices inside the source code. The first item is the start of the range (inclusive) and the second item is the end of the range (exclusive). Every node and token has a `range` property to identify the source code range they represent. +A range is a two-item array containing character indices inside of the source code. The first item is the start of the range (inclusive) and the second item is the end of the range (exclusive). Every node and token has a `range` property to identify the source code range they represent. The above methods return a `fixing` object. The `fix()` function can return the following values: @@ -363,11 +381,11 @@ There is no way to specify which of the conflicting fixes is applied. For example, if two fixes want to modify characters 0 through 5, only one is applied. -#### Providing Suggestions +### Providing Suggestions In some cases fixes aren't appropriate to be automatically applied, for example, if a fix potentially changes functionality or if there are multiple valid ways to fix a rule depending on the implementation intent (see the best practices for [applying fixes](#applying-fixes) listed above). In these cases, there is an alternative `suggest` option on `context.report()` that allows other tools, such as editors, to expose helpers for users to manually apply a suggestion. -To provide suggestions, use the `suggest` key in the report argument with an array of suggestion objects. The suggestion objects represent individual suggestions that could be applied and require either a `desc` key string that describes what applying the suggestion would do or a `messageId` key (see [below](#suggestion-messageids)), and a `fix` key that is a function defining the suggestion result. This `fix` function follows the same API as regular fixes (described above in [applying fixes](#applying-fixes)). +In order to provide suggestions, use the `suggest` key in the report argument with an array of suggestion objects. The suggestion objects represent individual suggestions that could be applied and require either a `desc` key string that describes what applying the suggestion would do or a `messageId` key (see [below](#suggestion-messageids)), and a `fix` key that is a function defining the suggestion result. This `fix` function follows the same API as regular fixes (described above in [applying fixes](#applying-fixes)). ```js {% raw %} @@ -393,9 +411,9 @@ context.report({ {% endraw %} ``` -**Important:** The `meta.hasSuggestions` property is mandatory for rules that provide suggestions. ESLint will throw an error if a rule attempts to produce a suggestion but does not [export](#rule-structure) this property. +**Important:** The `meta.hasSuggestions` property is mandatory for rules that provide suggestions. ESLint will throw an error if a rule attempts to produce a suggestion but does not [export](#rule-basics) this property. -**Note:** Suggestions are applied as stand-alone changes, without triggering multipass fixes. Each suggestion should focus on a singular change in the code and should not try to conform to user-defined styles. For example, if a suggestion is adding a new statement into the codebase, it should not try to match correct indentation or conform to user preferences on the presence/absence of semicolons. All of those things can be corrected by multipass autofix when the user triggers it. +Note: Suggestions will be applied as a stand-alone change, without triggering multipass fixes. Each suggestion should focus on a singular change in the code and should not try to conform to user defined styles. For example, if a suggestion is adding a new statement into the codebase, it should not try to match correct indentation, or conform to user preferences on presence/absence of semicolons. All of those things can be corrected by multipass autofix when the user triggers it. Best practices for suggestions: @@ -406,7 +424,7 @@ Suggestions are intended to provide fixes. ESLint will automatically remove the #### Suggestion `messageId`s -Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageids)). Here is an example of how to use a suggestion `messageId` in a rule: +Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageids)). Here is an example of how to use it in a rule: ```js {% raw %} @@ -427,13 +445,13 @@ module.exports = { data: { character }, suggest: [ { - messageId: "removeEscape", // suggestion messageId + messageId: "removeEscape", fix: function(fixer) { return fixer.removeRange(range); } }, { - messageId: "escapeBackslash", // suggestion messageId + messageId: "escapeBackslash", fix: function(fixer) { return fixer.insertTextBeforeRange(range, "\\"); } @@ -445,7 +463,7 @@ module.exports = { {% endraw %} ``` -#### Placeholders in Suggestion Messages +#### Placeholders in suggestion messages You can also use placeholders in the suggestion message. This works the same way as placeholders for the overall error (see [using message placeholders](#using-message-placeholders)). @@ -482,9 +500,9 @@ module.exports = { {% endraw %} ``` -### Accessing Options Passed to a Rule +### context.options -Some rules require options in order to function correctly. These options appear in configuration (`.eslintrc`, command line interface, or comments). For example: +Some rules require options in order to function correctly. These options appear in configuration (`.eslintrc`, command line, or in comments). For example: ```json { @@ -508,123 +526,95 @@ Since `context.options` is just an array, you can use it to determine how many o When using options, make sure that your rule has some logical defaults in case the options are not provided. -### Accessing the Source Code +### context.getSourceCode() -The `SourceCode` object is the main object for getting more information about the source code being linted. You can retrieve the `SourceCode` object at any time by using the `context.sourceCode` property: +The `SourceCode` object is the main object for getting more information about the source code being linted. You can retrieve the `SourceCode` object at any time by using the `getSourceCode()` method: ```js module.exports = { create: function(context) { - var sourceCode = context.sourceCode; + var sourceCode = context.getSourceCode(); // ... } }; ``` -**Deprecated:** The `context.getSourceCode()` method is deprecated; make sure to use `context.sourceCode` property instead. - Once you have an instance of `SourceCode`, you can use the following methods on it to work with the code: -* `getText(node)`: Returns the source code for the given node. Omit `node` to get the whole source (see the [dedicated section](#accessing-the-source-text)). -* `getAllComments()`: Returns an array of all comments in the source (see the [dedicated section](#accessing-comments)). -* `getCommentsBefore(nodeOrToken)`: Returns an array of comment tokens that occur directly before the given node or token (see the [dedicated section](#accessing-comments)). -* `getCommentsAfter(nodeOrToken)`: Returns an array of comment tokens that occur directly after the given node or token (see the [dedicated section](#accessing-comments)). -* `getCommentsInside(node)`: Returns an array of all comment tokens inside a given node (see the [dedicated section](#accessing-comments)). -* `isSpaceBetween(nodeOrToken, nodeOrToken)`: Returns true if there is a whitespace character between the two tokens or, if given a node, the last token of the first node and the first token of the second node. -* `getFirstToken(node, skipOptions)`: Returns the first token representing the given node. -* `getFirstTokens(node, countOptions)`: Returns the first `count` tokens representing the given node. -* `getLastToken(node, skipOptions)`: Returns the last token representing the given node. -* `getLastTokens(node, countOptions)`: Returns the last `count` tokens representing the given node. -* `getTokenAfter(nodeOrToken, skipOptions)`: Returns the first token after the given node or token. -* `getTokensAfter(nodeOrToken, countOptions)`: Returns `count` tokens after the given node or token. -* `getTokenBefore(nodeOrToken, skipOptions)`: Returns the first token before the given node or token. -* `getTokensBefore(nodeOrToken, countOptions)`: Returns `count` tokens before the given node or token. -* `getFirstTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)`: Returns the first token between two nodes or tokens. -* `getFirstTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)`: Returns the first `count` tokens between two nodes or tokens. -* `getLastTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)`: Returns the last token between two nodes or tokens. -* `getLastTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)`: Returns the last `count` tokens between two nodes or tokens. -* `getTokens(node)`: Returns all tokens for the given node. -* `getTokensBetween(nodeOrToken1, nodeOrToken2)`: Returns all tokens between two nodes. -* `getTokenByRangeStart(index, rangeOptions)`: Returns the token whose range starts at the given index in the source. -* `getNodeByRangeIndex(index)`: Returns the deepest node in the AST containing the given source index. -* `getLocFromIndex(index)`: Returns an object with `line` and `column` properties, corresponding to the location of the given source index. `line` is 1-based and `column` is 0-based. -* `getIndexFromLoc(loc)`: Returns the index of a given location in the source code, where `loc` is an object with a 1-based `line` key and a 0-based `column` key. -* `commentsExistBetween(nodeOrToken1, nodeOrToken2)`: Returns `true` if comments exist between two nodes. +* `getText(node)` - returns the source code for the given node. Omit `node` to get the whole source. +* `getAllComments()` - returns an array of all comments in the source. +* `getCommentsBefore(nodeOrToken)` - returns an array of comment tokens that occur directly before the given node or token. +* `getCommentsAfter(nodeOrToken)` - returns an array of comment tokens that occur directly after the given node or token. +* `getCommentsInside(node)` - returns an array of all comment tokens inside a given node. +* `isSpaceBetween(nodeOrToken, nodeOrToken)` - returns true if there is a whitespace character between the two tokens or, if given a node, the last token of the first node and the first token of the second node. +* `getFirstToken(node, skipOptions)` - returns the first token representing the given node. +* `getFirstTokens(node, countOptions)` - returns the first `count` tokens representing the given node. +* `getLastToken(node, skipOptions)` - returns the last token representing the given node. +* `getLastTokens(node, countOptions)` - returns the last `count` tokens representing the given node. +* `getTokenAfter(nodeOrToken, skipOptions)` - returns the first token after the given node or token. +* `getTokensAfter(nodeOrToken, countOptions)` - returns `count` tokens after the given node or token. +* `getTokenBefore(nodeOrToken, skipOptions)` - returns the first token before the given node or token. +* `getTokensBefore(nodeOrToken, countOptions)` - returns `count` tokens before the given node or token. +* `getFirstTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)` - returns the first token between two nodes or tokens. +* `getFirstTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)` - returns the first `count` tokens between two nodes or tokens. +* `getLastTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)` - returns the last token between two nodes or tokens. +* `getLastTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)` - returns the last `count` tokens between two nodes or tokens. +* `getTokens(node)` - returns all tokens for the given node. +* `getTokensBetween(nodeOrToken1, nodeOrToken2)` - returns all tokens between two nodes. +* `getTokenByRangeStart(index, rangeOptions)` - returns the token whose range starts at the given index in the source. +* `getNodeByRangeIndex(index)` - returns the deepest node in the AST containing the given source index. +* `getLocFromIndex(index)` - returns an object with `line` and `column` properties, corresponding to the location of the given source index. `line` is 1-based and `column` is 0-based. +* `getIndexFromLoc(loc)` - returns the index of a given location in the source code, where `loc` is an object with a 1-based `line` key and a 0-based `column` key. +* `commentsExistBetween(nodeOrToken1, nodeOrToken2)` - returns `true` if comments exist between two nodes. `skipOptions` is an object which has 3 properties; `skip`, `includeComments`, and `filter`. Default is `{skip: 0, includeComments: false, filter: null}`. -* `skip`: (`number`) Positive integer, the number of skipping tokens. If `filter` option is given at the same time, it doesn't count filtered tokens as skipped. -* `includeComments`: (`boolean`) The flag to include comment tokens into the result. -* `filter(token)`: Function which gets a token as the first argument. If the function returns `false` then the result excludes the token. +* `skip` is a positive integer, the number of skipping tokens. If `filter` option is given at the same time, it doesn't count filtered tokens as skipped. +* `includeComments` is a boolean value, the flag to include comment tokens into the result. +* `filter` is a function which gets a token as the first argument, if the function returns `false` then the result excludes the token. `countOptions` is an object which has 3 properties; `count`, `includeComments`, and `filter`. Default is `{count: 0, includeComments: false, filter: null}`. -* `count`: (`number`) Positive integer, the maximum number of returning tokens. -* `includeComments`: (`boolean`) The flag to include comment tokens into the result. -* `filter(token)`: Function which gets a token as the first argument, if the function returns `false` then the result excludes the token. +* `count` is a positive integer, the maximum number of returning tokens. +* `includeComments` is a boolean value, the flag to include comment tokens into the result. +* `filter` is a function which gets a token as the first argument, if the function returns `false` then the result excludes the token. -`rangeOptions` is an object that has 1 property, `includeComments`. Default is `{includeComments: false}`. +`rangeOptions` is an object which has 1 property: `includeComments`. -* `includeComments`: (`boolean`) The flag to include comment tokens into the result. +* `includeComments` is a boolean value, the flag to include comment tokens into the result. There are also some properties you can access: -* `hasBOM`: (`boolean`) The flag to indicate whether the source code has Unicode BOM. -* `text`: (`string`) The full text of the code being linted. Unicode BOM has been stripped from this text. -* `ast`: (`object`) `Program` node of the AST for the code being linted. -* `scopeManager`: [ScopeManager](./scope-manager-interface#scopemanager-interface) object of the code. -* `visitorKeys`: (`object`) Visitor keys to traverse this AST. -* `lines`: (`array`) Array of lines, split according to the specification's definition of line breaks. +* `hasBOM` - the flag to indicate whether or not the source code has Unicode BOM. +* `text` - the full text of the code being linted. Unicode BOM has been stripped from this text. +* `ast` - the `Program` node of the AST for the code being linted. +* `scopeManager` - the [ScopeManager](./scope-manager-interface#scopemanager-interface) object of the code. +* `visitorKeys` - the visitor keys to traverse this AST. +* `lines` - an array of lines, split according to the specification's definition of line breaks. You should use a `SourceCode` object whenever you need to get more information about the code being linted. -#### Accessing the Source Text +#### Deprecated -If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows: +Please note that the following methods have been deprecated and will be removed in a future version of ESLint: -```js - -// get all source -var source = sourceCode.getText(); - -// get source for just this AST node -var nodeSource = sourceCode.getText(node); - -// get source for AST node plus previous two characters -var nodeSourceWithPrev = sourceCode.getText(node, 2); - -// get source for AST node plus following two characters -var nodeSourceWithFollowing = sourceCode.getText(node, 0, 2); -``` - -In this way, you can look for patterns in the JavaScript text itself when the AST isn't providing the appropriate data (such as the location of commas, semicolons, parentheses, etc.). - -#### Accessing Comments - -While comments are not technically part of the AST, ESLint provides the `sourceCode.getAllComments()`, `sourceCode.getCommentsBefore()`, `sourceCode.getCommentsAfter()`, and `sourceCode.getCommentsInside()` to access them. - -`sourceCode.getCommentsBefore()`, `sourceCode.getCommentsAfter()`, and `sourceCode.getCommentsInside()` are useful for rules that need to check comments in relation to a given node or token. - -Keep in mind that the results of these methods are calculated on demand. - -You can also access comments through many of `sourceCode`'s methods using the `includeComments` option. +* `getComments()` - replaced by `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` +* `getTokenOrCommentBefore()` - replaced by `getTokenBefore()` with the `{ includeComments: true }` option +* `getTokenOrCommentAfter()` - replaced by `getTokenAfter()` with the `{ includeComments: true }` option +* `isSpaceBetweenTokens()` - replaced by `isSpaceBetween()` +* `getJSDocComment()` ### Options Schemas -Rules may export a `schema` property, which is a [JSON Schema](https://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. - -There are two formats for a rule's exported `schema`: +Rules may export a `schema` property, which is a [JSON schema](https://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. -1. A full JSON Schema object describing all possible options the rule accepts. -2. An array of JSON Schema objects for each optional positional argument. +There are two formats for a rule's exported `schema`. The first is a full JSON Schema object describing all possible options the rule accepts, including the rule's error level as the first argument and any optional arguments thereafter. -In both cases, these should exclude the [severity](../use/configure/rules#rule-severities), as ESLint automatically validates this first. - -For example, the `yoda` rule accepts a primary mode argument of `"always"` or `"never"`, as well as an extra options object with an optional property `exceptRange`: +However, to simplify schema creation, rules may also export an array of schemas for each optional positional argument, and ESLint will automatically validate the required error level first. For example, the `yoda` rule accepts a primary mode argument, as well as an extra options object with named properties. ```js -// "yoda": ["error", "never", { "exceptRange": true }] +// "yoda": [2, "never", { "exceptRange": true }] module.exports = { meta: { schema: [ @@ -645,108 +635,61 @@ module.exports = { }; ``` -**Note:** If your rule schema uses JSON schema [`$ref`](https://json-schema.org/understanding-json-schema/structuring.html#ref) properties, you must use the full JSON Schema object rather than the array of positional property schemas. This is because ESLint transforms the array shorthand into a single schema without updating references that makes them incorrect (they are ignored). - -To learn more about JSON Schema, we recommend looking at some examples on the [JSON Schema website](https://json-schema.org/learn/), or reading the free [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) ebook. - -### Accessing Shebangs - -[Shebangs (#!)](https://en.wikipedia.org/wiki/Shebang_(Unix)) are represented by the unique tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined in the [Accessing Comments](#accessing-comments) section, such as `sourceCode.getAllComments()`. - -### Accessing Variable Scopes +In the preceding example, the error level is assumed to be the first argument. It is followed by the first optional argument, a string which may be either `"always"` or `"never"`. The final optional argument is an object, which may have a Boolean property named `exceptRange`. -The `SourceCode#getScope(node)` method returns the scope of the given node. It is a useful method for finding information about the variables in a given scope and how they are used in other scopes. +To learn more about JSON Schema, we recommend looking at some examples in [website](https://json-schema.org/learn/) to start, and also reading [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) (a free ebook). -**Deprecated:** The `context.getScope()` is deprecated; make sure to use `SourceCode#getScope(node)` instead. +**Note:** Currently you need to use full JSON Schema object rather than array in case your schema has references ($ref), because in case of array format ESLint transforms this array into a single schema without updating references that makes them incorrect (they are ignored). -#### Scope types - -The following table contains a list of AST node types and the scope type that they correspond to. For more information about the scope types, refer to the [`Scope` object documentation](./scope-manager-interface#scope-interface). - -| AST Node Type | Scope Type | -|:--------------------------|:-----------| -| `Program` | `global` | -| `FunctionDeclaration` | `function` | -| `FunctionExpression` | `function` | -| `ArrowFunctionExpression` | `function` | -| `ClassDeclaration` | `class` | -| `ClassExpression` | `class` | -| `BlockStatement` โ€ป1 | `block` | -| `SwitchStatement` โ€ป1 | `switch` | -| `ForStatement` โ€ป2 | `for` | -| `ForInStatement` โ€ป2 | `for` | -| `ForOfStatement` โ€ป2 | `for` | -| `WithStatement` | `with` | -| `CatchClause` | `catch` | -| others | โ€ป3 | +### Getting the Source -**โ€ป1** Only if the configured parser provided the block-scope feature. The default parser provides the block-scope feature if `parserOptions.ecmaVersion` is not less than `6`.
-**โ€ป2** Only if the `for` statement defines the iteration variable as a block-scoped variable (E.g., `for (let i = 0;;) {}`).
-**โ€ป3** The scope of the closest ancestor node which has own scope. If the closest ancestor node has multiple scopes then it chooses the innermost scope (E.g., the `Program` node has a `global` scope and a `module` scope if `Program#sourceType` is `"module"`. The innermost scope is the `module` scope.). +If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows: -#### Scope Variables +```js -The `Scope#variables` property contains an array of [`Variable` objects](./scope-manager-interface#variable-interface). These are the variables declared in current scope. You can use these `Variable` objects to track references to a variable throughout the entire module. +// get all source +var source = sourceCode.getText(); -Inside of each `Variable`, the `Variable#references` property contains an array of [`Reference` objects](./scope-manager-interface#reference-interface). The `Reference` array contains all the locations where the variable is referenced in the module's source code. +// get source for just this AST node +var nodeSource = sourceCode.getText(node); -Also inside of each `Variable`, the `Variable#defs` property contains an array of [`Definition` objects](./scope-manager-interface#definition-interface). You can use the `Definitions` to find where the variable was defined. +// get source for AST node plus previous two characters +var nodeSourceWithPrev = sourceCode.getText(node, 2); -Global variables have the following additional properties: +// get source for AST node plus following two characters +var nodeSourceWithFollowing = sourceCode.getText(node, 0, 2); +``` -* `Variable#writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only. -* `Variable#eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file. -* `Variable#eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments. -* `Variable#eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments. +In this way, you can look for patterns in the JavaScript text itself when the AST isn't providing the appropriate data (such as location of commas, semicolons, parentheses, etc.). -For examples of using `SourceCode#getScope()` to track variables, refer to the source code for the following built-in rules: +### Accessing Comments -* [no-shadow](https://github.com/eslint/eslint/blob/main/lib/rules/no-shadow.js): Calls `sourceCode.getScope()` at the `Program` node and inspects all child scopes to make sure a variable name is not reused at a lower scope. ([no-shadow](../rules/no-shadow) documentation) -* [no-redeclare](https://github.com/eslint/eslint/blob/main/lib/rules/no-redeclare.js): Calls `sourceCode.getScope()` at each scope to make sure that a variable is not declared twice in the same scope. ([no-redeclare](../rules/no-redeclare) documentation) +While comments are not technically part of the AST, ESLint provides a few ways for rules to access them: -### Marking Variables as Used +#### sourceCode.getAllComments() -**Deprecated:** The `context.markVariableAsUsed()` method is deprecated in favor of `sourceCode.markVariableAsUsed()`. +This method returns an array of all the comments found in the program. This is useful for rules that need to check all comments regardless of location. -Certain ESLint rules, such as [`no-unused-vars`](../rules/no-unused-vars), check to see if a variable has been used. ESLint itself only knows about the standard rules of variable access and so custom ways of accessing variables may not register as "used". +#### sourceCode.getCommentsBefore(), sourceCode.getCommentsAfter(), and sourceCode.getCommentsInside() -To help with this, you can use the `sourceCode.markVariableAsUsed()` method. This method takes two arguments: the name of the variable to mark as used and an option reference node indicating the scope in which you are working. Here's an example: +These methods return an array of comments that appear directly before, directly after, and inside nodes, respectively. They are useful for rules that need to check comments in relation to a given node or token. -```js -module.exports = { - create: function(context) { - var sourceCode = context.sourceCode; +Keep in mind that the results of this method are calculated on demand. - return { - ReturnStatement(node) { +#### Token traversal methods - // look in the scope of the function for myCustomVar and mark as used - sourceCode.markVariableAsUsed("myCustomVar", node); +Finally, comments can be accessed through many of `sourceCode`'s methods using the `includeComments` option. - // or: look in the global scope for myCustomVar and mark as used - sourceCode.markVariableAsUsed("myCustomVar"); - } - } - // ... - } -}; -``` +### Accessing Shebangs -Here, the `myCustomVar` variable is marked as used relative to a `ReturnStatement` node, which means ESLint will start searching from the scope closest to that node. If you omit the second argument, then the top-level scope is used. (For ESM files, the top-level scope is the module scope; for CommonJS files, the top-level scope is the first function scope.) +Shebangs are represented by tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined above. ### Accessing Code Paths -ESLint analyzes code paths while traversing AST. You can access code path objects with five events related to code paths. For more information, refer to [Code Path Analysis](code-path-analysis). - -### Deprecated `SourceCode` Methods +ESLint analyzes code paths while traversing AST. +You can access that code path objects with five events related to code paths. -Please note that the following `SourceCode` methods have been deprecated and will be removed in a future version of ESLint: - -* `getComments()`: Replaced by `SourceCode#getCommentsBefore()`, `SourceCode#getCommentsAfter()`, and `SourceCode#getCommentsInside()`. -* `getTokenOrCommentBefore()`: Replaced by `SourceCode#getTokenBefore()` with the `{ includeComments: true }` option. -* `getTokenOrCommentAfter()`: Replaced by `SourceCode#getTokenAfter()` with the `{ includeComments: true }` option. -* `isSpaceBetweenTokens()`: Replaced by `SourceCode#isSpaceBetween()` -* `getJSDocComment()` +[details here](code-path-analysis) ## Rule Unit Tests @@ -754,11 +697,11 @@ ESLint provides the [`RuleTester`](../integrate/nodejs-api#ruletester) utility t ## Rule Naming Conventions -While you can give a custom rule any name you'd like, the core rules have naming conventions. It could be clearer to apply these same naming conventions to your custom rule. To learn more, refer to the [Core Rule Naming Conventions](../contribute/core-rules#rule-naming-conventions) documentation. +While you can give a custom rule any name you'd like, the core rules have naming conventions that it could be clearer to apply to your custom rule. To learn more, refer to the [Core Rule Naming Conventions](../contribute/core-rules#rule-naming-conventions) documentation. ## Runtime Rules -The thing that makes ESLint different from other linters is the ability to define custom rules at runtime. This is perfect for rules that are specific to your project or company and wouldn't make sense for ESLint to ship with or be included in a plugin. Just write your rules and include them at runtime. +The thing that makes ESLint different from other linters is the ability to define custom rules at runtime. This is perfect for rules that are specific to your project or company and wouldn't make sense for ESLint to ship with. With runtime rules, you don't have to wait for the next version of ESLint or be disappointed that your rule isn't general enough to apply to the larger JavaScript community, just write your rules and include them at runtime. Runtime rules are written in the same format as all other rules. Create your rule as you would any other and then follow these steps: diff --git a/docs/src/extend/plugins.md b/docs/src/extend/plugins.md index 70bc3ee7c746..f180c034faa9 100644 --- a/docs/src/extend/plugins.md +++ b/docs/src/extend/plugins.md @@ -18,34 +18,6 @@ Each plugin is an npm module with a name in the format of `eslint-plugin-). ### Linter#verify diff --git a/docs/src/maintain/manage-issues.md b/docs/src/maintain/manage-issues.md index 803451b4c633..6cfdbc93b191 100644 --- a/docs/src/maintain/manage-issues.md +++ b/docs/src/maintain/manage-issues.md @@ -30,7 +30,7 @@ The first goal when evaluating an issue is to determine which category the issue ## Triaging Process -All of ESLint's issues, across all GitHub repositories, are managed on our [Triage Project](https://github.com/orgs/eslint/projects/3). Please use the Triage project instead of the issues list when reviewing issues to determine what to work on. The Triage project has several columns: +All of ESLint's issues, across all GitHub repositories, are managed on our [Triage Project](https://github.com/orgs/eslint/projects/2). Please use the Triage project instead of the issues list when reviewing issues to determine what to work on. The Triage project has several columns: * **Needs Triage**: Issues that have not yet been reviewed by anyone * **Triaging**: Issues that someone has reviewed but has not been able to fully triage yet @@ -41,7 +41,7 @@ All of ESLint's issues, across all GitHub repositories, are managed on our [Tria * **RFC Opened**: An RFC is opened to address these issues * **Blocked**: The issue can't move forward due to some dependency * **Ready to Implement**: These issues have all the details necessary to start implementation -* **Implementing**: There is an open pull request for each of these issues +* **PR Opened**: There is an open pull request for each of these issues * **Completed**: The issue has been closed (either via pull request merge or by the team manually closing the issue) We make every attempt to automate movement between as many columns as we can, but sometimes moving issues needs to be done manually. @@ -64,7 +64,7 @@ The steps for triaging an issue are: * If the issue is actually a question (rather than something the dev team needs to change), please [convert it to a discussion](https://docs.github.com/en/free-pro-team@latest/discussions/managing-discussions-for-your-community/moderating-discussions#converting-an-issue-to-a-discussion). You can continue the conversation as a discussion. * If the issue is reporting a bug, try to reproduce the issue following the instructions in the issue. If you can reproduce the bug, please add the "repro:yes" label. (The bot will automatically remove the "repro:needed" label.) If you can't reproduce the bug, ask the author for more information about their environment or to clarify reproduction steps. * If the issue is reporting something that works as intended, please add the "works as intended" label and close the issue. - * Please add labels describing the part of ESLint affected: + * For all issues, please add labels describing the part of ESLint affected: * **3rd party plugin**: Related to third-party functionality (plugins, parsers, rules, etc.) * **build**: Related to commands run during a build (testing, linting, release scripts, etc.) * **cli**: Related to command line input or output, or to `CLIEngine` @@ -72,16 +72,6 @@ The steps for triaging an issue are: * **documentation**: Related to content on eslint.org * **infrastructure**: Related to resources needed for builds or deployment (VMs, CI tools, bots, etc.) * **rule**: Related to core rules - * Please assign an initial priority based on the importance of the issue. If you're not sure, use your best judgment. We can always change the priority later. - * **P1**: Urgent and important, we need to address this immediately. - * **P2**: Important but not urgent. Should be handled by a TSC member or reviewer. - * **P3**: Nice to have but not important. Can be handled by any team member. - * **P4**: A good idea that we'd like to have but may take a while for the team to get to it. - * **P5**: A good idea that the core team can't commit to. Will likely need to be done by an outside contributor. - * Please assign an initial impact assessement (make your best guess): - * **Low**: Doesn't affect many users. - * **Medium**: Affects most users or has a noticeable effect on user experience. - * **High**: Affects a lot of users, is a breaking change, or otherwise will be very noticeable to users. * If you can't properly triage the issue, move the issue back to the "Needs Triage" column in the Triage project so someone else can triage it. * If you have triaged the issue, move the issue to the "Ready for Dev Team" column in the Triage project. @@ -130,8 +120,8 @@ Consensus is reached on issues when there are at least three team members who be If consensus cannot be reached on an issue, or an issue's progress has been stalled and it's not clear if the issue should be closed, then you can refer the issue to the TSC for resolution. To do so, add the "tsc agenda" label to the issue and add a comment including the following information: -1. A one-paragraph summary of the discussion to this point. This should begin with "TSC Summary:". -2. The question you would like the TSC to answer. This should begin with "TSC Question:". +1. A one-paragraph summary of the discussion to this point. +2. The question you would like the TSC to answer. The issue will be discussed at the next TSC meeting and the resolution will be posted back to the issue. diff --git a/docs/src/rules/callback-return.md b/docs/src/rules/callback-return.md index 92ec02628f31..d3d621814a30 100644 --- a/docs/src/rules/callback-return.md +++ b/docs/src/rules/callback-return.md @@ -9,7 +9,7 @@ further_reading: --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). The callback pattern is at the heart of most I/O and event-driven programming in JavaScript. diff --git a/docs/src/rules/global-require.md b/docs/src/rules/global-require.md index e960468b3807..4a2d20a75d74 100644 --- a/docs/src/rules/global-require.md +++ b/docs/src/rules/global-require.md @@ -4,7 +4,7 @@ rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). In Node.js, module dependencies are included using the `require()` function, such as: diff --git a/docs/src/rules/handle-callback-err.md b/docs/src/rules/handle-callback-err.md index 3f66ad2031ef..fddce708b462 100644 --- a/docs/src/rules/handle-callback-err.md +++ b/docs/src/rules/handle-callback-err.md @@ -7,7 +7,7 @@ further_reading: --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). In Node.js, a common pattern for dealing with asynchronous behavior is called the callback pattern. This pattern expects an `Error` object or `null` as the first argument of the callback. diff --git a/docs/src/rules/id-match.md b/docs/src/rules/id-match.md index 08d5ff3ba9c4..468078d2d54f 100644 --- a/docs/src/rules/id-match.md +++ b/docs/src/rules/id-match.md @@ -91,10 +91,10 @@ This rule has an object option: * `"properties": false` (default) does not check object properties * `"properties": true` requires object literal properties and member expression assignment properties to match the specified regular expression -* `"classFields": false` (default) does not check class field names +* `"classFields": false` (default) does not class field names * `"classFields": true` requires class field names to match the specified regular expression * `"onlyDeclarations": false` (default) requires all variable names to match the specified regular expression -* `"onlyDeclarations": true` requires only `var`, `const`, `let`, `function`, and `class` declarations to match the specified regular expression +* `"onlyDeclarations": true` requires only `var`, `function`, and `class` declarations to match the specified regular expression * `"ignoreDestructuring": false` (default) enforces `id-match` for destructured identifiers * `"ignoreDestructuring": true` does not check destructured identifiers diff --git a/docs/src/rules/no-buffer-constructor.md b/docs/src/rules/no-buffer-constructor.md index 97ea1918d0b2..59b9dd8772d3 100644 --- a/docs/src/rules/no-buffer-constructor.md +++ b/docs/src/rules/no-buffer-constructor.md @@ -8,7 +8,7 @@ further_reading: --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). In Node.js, the behavior of the `Buffer` constructor is different depending on the type of its argument. Passing an argument from user input to `Buffer()` without validating its type can lead to security vulnerabilities such as remote memory disclosure and denial of service. As a result, the `Buffer` constructor has been deprecated and should not be used. Use the producer methods `Buffer.from`, `Buffer.alloc`, and `Buffer.allocUnsafe` instead. diff --git a/docs/src/rules/no-div-regex.md b/docs/src/rules/no-div-regex.md index cb30420e7000..ccc7287dcbe8 100644 --- a/docs/src/rules/no-div-regex.md +++ b/docs/src/rules/no-div-regex.md @@ -8,7 +8,7 @@ related_rules: -Characters `/=` at the beginning of a regular expression literal can be confused with a division assignment operator. +Require regex literals to escape division operators. ```js function bar() { return /=foo/; } @@ -16,7 +16,7 @@ function bar() { return /=foo/; } ## Rule Details -This rule forbids equal signs (`=`) after the slash (`/`) at the beginning of a regular expression literal, because the characters `/=` can be confused with a division assignment operator. +This is used to disambiguate the division operator to not confuse users. Examples of **incorrect** code for this rule: diff --git a/docs/src/rules/no-mixed-requires.md b/docs/src/rules/no-mixed-requires.md index f747ebd6362a..3dcf6384fd46 100644 --- a/docs/src/rules/no-mixed-requires.md +++ b/docs/src/rules/no-mixed-requires.md @@ -4,7 +4,7 @@ rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). In the Node.js community it is often customary to separate initializations with calls to `require` modules from other variable declarations, sometimes also grouping them by the type of module. This rule helps you enforce this convention. diff --git a/docs/src/rules/no-new-require.md b/docs/src/rules/no-new-require.md index 494d4881da2f..1aa663e6669d 100644 --- a/docs/src/rules/no-new-require.md +++ b/docs/src/rules/no-new-require.md @@ -4,7 +4,7 @@ rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). The `require` function is used to include modules that exist in separate files, such as: diff --git a/docs/src/rules/no-new.md b/docs/src/rules/no-new.md index c8cea29cb0ff..9eeda70095a9 100644 --- a/docs/src/rules/no-new.md +++ b/docs/src/rules/no-new.md @@ -43,7 +43,7 @@ Examples of **correct** code for this rule: var thing = new Thing(); -Foo(); +Thing(); ``` ::: diff --git a/docs/src/rules/no-path-concat.md b/docs/src/rules/no-path-concat.md index 1042f3179208..840b80c35207 100644 --- a/docs/src/rules/no-path-concat.md +++ b/docs/src/rules/no-path-concat.md @@ -4,7 +4,7 @@ rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). In Node.js, the `__dirname` and `__filename` global variables contain the directory path and the file path of the currently executing script file, respectively. Sometimes, developers try to use these variables to create paths to other files, such as: diff --git a/docs/src/rules/no-process-env.md b/docs/src/rules/no-process-env.md index 99cd1f41a0e9..2130a89443f5 100644 --- a/docs/src/rules/no-process-env.md +++ b/docs/src/rules/no-process-env.md @@ -7,7 +7,7 @@ further_reading: --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). The `process.env` object in Node.js is used to store deployment/configuration parameters. Littering it through out a project could lead to maintenance issues as it's another kind of global dependency. As such, it could lead to merge conflicts in a multi-user setup and deployment issues in a multi-server setup. Instead, one of the best practices is to define all those parameters in a single configuration/settings file which could be accessed throughout the project. diff --git a/docs/src/rules/no-process-exit.md b/docs/src/rules/no-process-exit.md index 90e312b2d452..6928a0d4843a 100644 --- a/docs/src/rules/no-process-exit.md +++ b/docs/src/rules/no-process-exit.md @@ -4,7 +4,7 @@ rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). The `process.exit()` method in Node.js is used to immediately stop the Node.js process and exit. This is a dangerous operation because it can occur in any method at any point in time, potentially stopping a Node.js application completely when an error occurs. For example: diff --git a/docs/src/rules/no-restricted-modules.md b/docs/src/rules/no-restricted-modules.md index eb2ca03ffc86..3ed5bd861170 100644 --- a/docs/src/rules/no-restricted-modules.md +++ b/docs/src/rules/no-restricted-modules.md @@ -4,7 +4,7 @@ rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). A module in Node.js is a simple or complex functionality organized in a JavaScript file which can be reused throughout the Node.js application. The keyword `require` is used in Node.js/CommonJS to import modules into an application. This way you can have dynamic loading where the loaded module name isn't predefined /static, or where you conditionally load a module only if it's "truly required". diff --git a/docs/src/rules/no-sync.md b/docs/src/rules/no-sync.md index 254974411e43..33fbec47fc04 100644 --- a/docs/src/rules/no-sync.md +++ b/docs/src/rules/no-sync.md @@ -4,7 +4,7 @@ rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). In Node.js, most I/O is done through asynchronous methods. However, there are often synchronous versions of the asynchronous methods. For example, `fs.exists()` and `fs.existsSync()`. In some contexts, using synchronous operations is okay (if, as with ESLint, you are writing a command line utility). However, in other contexts the use of synchronous operations is considered a bad practice that should be avoided. For example, if you are running a high-travel web server on Node.js, you should consider carefully if you want to allow any synchronous operations that could lock up the server. diff --git a/docs/src/rules/semi.md b/docs/src/rules/semi.md index 6fd05501cd1f..86c4e5d84fe1 100644 --- a/docs/src/rules/semi.md +++ b/docs/src/rules/semi.md @@ -76,18 +76,17 @@ This rule has two options, a string option and an object option. String option: * `"always"` (default) requires semicolons at the end of statements -* `"never"` disallows semicolons at the end of statements (except to disambiguate statements beginning with `[`, `(`, `/`, `+`, or `-`) +* `"never"` disallows semicolons as the end of statements (except to disambiguate statements beginning with `[`, `(`, `/`, `+`, or `-`) Object option (when `"always"`): -* `"omitLastInOneLineBlock": true` disallows the last semicolon in a block in which its braces (and therefore the content of the block) are in the same line -* `"omitLastInOneLineClassBody": true` disallows the last semicolon in a class body in which its braces (and therefore the content of the class body) are in the same line +* `"omitLastInOneLineBlock": true` ignores the last semicolon in a block in which its braces (and therefore the content of the block) are in the same line Object option (when `"never"`): * `"beforeStatementContinuationChars": "any"` (default) ignores semicolons (or lacking semicolon) at the end of statements if the next line starts with `[`, `(`, `/`, `+`, or `-`. * `"beforeStatementContinuationChars": "always"` requires semicolons at the end of statements if the next line starts with `[`, `(`, `/`, `+`, or `-`. -* `"beforeStatementContinuationChars": "never"` disallows semicolons at the end of statements if it doesn't make ASI hazard even if the next line starts with `[`, `(`, `/`, `+`, or `-`. +* `"beforeStatementContinuationChars": "never"` disallows semicolons as the end of statements if it doesn't make ASI hazard even if the next line starts with `[`, `(`, `/`, `+`, or `-`. **Note:** `beforeStatementContinuationChars` does not apply to class fields because class fields are not statements. @@ -133,52 +132,6 @@ class Foo { ::: -#### omitLastInOneLineBlock - -Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineBlock": true }` options: - -::: correct - -```js -/*eslint semi: ["error", "always", { "omitLastInOneLineBlock": true}] */ - -if (foo) { bar() } - -if (foo) { bar(); baz() } - -function f() { bar(); baz() } - -class C { - foo() { bar(); baz() } - - static { bar(); baz() } -} -``` - -::: - -#### omitLastInOneLineClassBody - -Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineClassBody": true }` options: - -::: correct - -```js -/*eslint semi: ["error", "always", { "omitLastInOneLineClassBody": true}] */ - -export class SomeClass{ - logType(){ - console.log(this.type); - console.log(this.anotherType); - } -} - -export class Variant1 extends SomeClass{type=1} -export class Variant2 extends SomeClass{type=2; anotherType=3} -``` - -::: - ### never Examples of **incorrect** code for this rule with the `"never"` option: @@ -237,6 +190,30 @@ class Foo { ::: +#### omitLastInOneLineBlock + +Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineBlock": true }` options: + +::: correct + +```js +/*eslint semi: ["error", "always", { "omitLastInOneLineBlock": true}] */ + +if (foo) { bar() } + +if (foo) { bar(); baz() } + +function f() { bar(); baz() } + +class C { + foo() { bar(); baz() } + + static { bar(); baz() } +} +``` + +::: + #### beforeStatementContinuationChars Examples of additional **incorrect** code for this rule with the `"never", { "beforeStatementContinuationChars": "always" }` options: diff --git a/docs/src/use/configure/configuration-files-new.md b/docs/src/use/configure/configuration-files-new.md index bf854f08ecaa..a57bbf0f235f 100644 --- a/docs/src/use/configure/configuration-files-new.md +++ b/docs/src/use/configure/configuration-files-new.md @@ -9,7 +9,7 @@ eleventyNavigation: --- ::: warning -This config system is feature complete but not enabled by default. To opt-in, place an `eslint.config.js` file in the root of your project or set the `ESLINT_USE_FLAT_CONFIG` environment variable to `true`. To opt-out, even in the presence of an `eslint.config.js` file, set the environment variable to `false`. If you are using the API, you can use the configuration system described on this page by using the `FlatESLint` class, the `FlatRuleTester` class, or by setting `configType: "flat"` in the `Linter` class. +This is an experimental feature. To opt-in, place an `eslint.config.js` file in the root of your project or set the `ESLINT_USE_FLAT_CONFIG` environment variable to `true`. To opt-out, even in the presence of an `eslint.config.js` file, set the environment variable to `false`. If you are using the API, you can use the configuration system described on this page by using the `FlatESLint` class, the `FlatRuleTester` class, or by setting `configType: "flat"` in the `Linter` class. ::: You can put your ESLint project configuration in a configuration file. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. @@ -143,21 +143,7 @@ export default [ ]; ``` -This configuration specifies that all of the files in the `.config` directory should be ignored. This pattern is added after the default patterns, which are `["**/node_modules/", ".git/"]`. - -You can also unignore files and directories that are ignored by the default patterns. For example, this config unignores `node_modules/mylibrary`: - -```js -export default [ - { - ignores: [ - "!node_modules/", // unignore `node_modules/` directory - "node_modules/*", // ignore its content - "!node_modules/mylibrary/" // unignore `node_modules/mylibrary` directory - ] - } -]; -``` +This configuration specifies that all of the files in the `.config` directory should be ignored. This pattern is added after the default patterns, which are `["**/node_modules/**", ".git/**"]`. #### Cascading configuration objects @@ -342,24 +328,6 @@ export default [ For historical reasons, the boolean value `false` and the string value `"readable"` are equivalent to `"readonly"`. Similarly, the boolean value `true` and the string value `"writeable"` are equivalent to `"writable"`. However, the use of older values is deprecated. -##### Predefined global variables - -Apart from the ECMAScript standard built-in globals, which are automatically enabled based on the configured `languageOptions.ecmaVersion`, ESLint doesn't provide predefined sets of global variables. You can use the [`globals`](https://www.npmjs.com/package/globals) package to additionally enable all globals for a specific environment. For example, here is how you can add `console`, amongst other browser globals, into your configuration. - -```js -import globals from "globals"; - -export default [ - { - languageOptions: { - globals: { - ...globals.browser - } - } - } -]; -``` - ### Using plugins in your configuration Plugins are used to share rules, processors, configurations, parsers, and more across ESLint projects. diff --git a/docs/src/use/configure/configuration-files.md b/docs/src/use/configure/configuration-files.md index b2d51e09a3be..f9c7aa341f3a 100644 --- a/docs/src/use/configure/configuration-files.md +++ b/docs/src/use/configure/configuration-files.md @@ -8,10 +8,6 @@ eleventyNavigation: --- -::: warning -We are transitioning to a new config system in ESLint v9.0.0. The config system shared on this page is currently the default but will be deprecated in v9.0.0. You can opt-in to the new config system by following the instructions in the [documentation](configuration-files-new). -::: - You can put your ESLint project configuration in a configuration file. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. ## Configuration File Formats diff --git a/docs/src/use/configure/language-options.md b/docs/src/use/configure/language-options.md index 4f8fa148bce8..ef21c35d57e4 100644 --- a/docs/src/use/configure/language-options.md +++ b/docs/src/use/configure/language-options.md @@ -27,7 +27,7 @@ An environment provides predefined global variables. The available environments * `es2021` - adds all ECMAScript 2021 globals and automatically sets the `ecmaVersion` parser option to 12. * `es2022` - adds all ECMAScript 2022 globals and automatically sets the `ecmaVersion` parser option to 13. * `worker` - web workers global variables. -* `amd` - defines `require()` and `define()` as global variables as per the [amd](https://github.com/amdjs/amdjs-api/blob/master/AMD.md) spec. +* `amd` - defines `require()` and `define()` as global variables as per the [amd](https://github.com/amdjs/amdjs-api/wiki/AMD) spec. * `mocha` - adds all of the Mocha testing global variables. * `jasmine` - adds all of the Jasmine testing global variables for version 1.3 and 2.0. * `jest` - Jest global variables. @@ -187,7 +187,7 @@ For historical reasons, the boolean value `false` and the string value `"readabl ESLint allows you to specify the JavaScript language options you want to support. By default, ESLint expects ECMAScript 5 syntax. You can override that setting to enable support for other ECMAScript versions and JSX using parser options. -Please note that supporting JSX syntax is not the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) if you are using React. +Please note that supporting JSX syntax is not the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) if you are using React. By the same token, supporting ES6 syntax is not the same as supporting new ES6 globals (e.g., new types such as `Set`). For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 global variables, use `{ "env": { "es6": true } }`. Setting `{ "env": { "es6": true } }` enables ES6 syntax automatically, but `{ "parserOptions": { "ecmaVersion": 6 } }` does not enable ES6 globals automatically. diff --git a/docs/src/use/formatters/index.md b/docs/src/use/formatters/index.md index ec6ce537ed71..b5bb8be723d9 100644 --- a/docs/src/use/formatters/index.md +++ b/docs/src/use/formatters/index.md @@ -122,7 +122,7 @@ Alternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) Example output: ```text -{"results":[{"filePath":"/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'addOne' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":16},{"ruleId":"use-isnan","severity":2,"message":"Use the isNaN function to compare with NaN.","line":2,"column":9,"nodeType":"BinaryExpression","messageId":"comparisonWithNaN","endLine":2,"endColumn":17},{"ruleId":"space-unary-ops","severity":2,"message":"Unexpected space before unary operator '++'.","line":3,"column":16,"nodeType":"UpdateExpression","messageId":"unexpectedBefore","endLine":3,"endColumn":20,"fix":{"range":[57,58],"text":""}},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":3,"column":20,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":4,"endColumn":1,"fix":{"range":[60,60],"text":";"}},{"ruleId":"no-else-return","severity":1,"message":"Unnecessary 'else' after 'return'.","line":4,"column":12,"nodeType":"BlockStatement","messageId":"unexpected","endLine":6,"endColumn":6,"fix":{"range":[0,94],"text":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}"}},{"ruleId":"indent","severity":1,"message":"Expected indentation of 8 spaces but found 6.","line":5,"column":1,"nodeType":"Keyword","messageId":"wrongIndentation","endLine":5,"endColumn":7,"fix":{"range":[74,80],"text":" "}},{"ruleId":"consistent-return","severity":2,"message":"Function 'addOne' expected a return value.","line":5,"column":7,"nodeType":"ReturnStatement","messageId":"missingReturnValue","endLine":5,"endColumn":13},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":13,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":6,"endColumn":1,"fix":{"range":[86,86],"text":";"}},{"ruleId":"no-extra-semi","severity":2,"message":"Unnecessary semicolon.","line":7,"column":2,"nodeType":"EmptyStatement","messageId":"unexpected","endLine":7,"endColumn":3,"fix":{"range":[93,95],"text":"}"}}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":2,"fixableWarningCount":4,"source":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};"}],"metadata":{"rulesMeta":{"no-else-return":{"type":"suggestion","docs":{"description":"Disallow `else` blocks after `return` statements in `if` statements","recommended":false,"url":"https://eslint.org/docs/latest/rules/no-else-return"},"schema":[{"type":"object","properties":{"allowElseIf":{"type":"boolean","default":true}},"additionalProperties":false}],"fixable":"code","messages":{"unexpected":"Unnecessary 'else' after 'return'."}},"indent":{"type":"layout","docs":{"description":"Enforce consistent indentation","recommended":false,"url":"https://eslint.org/docs/latest/rules/indent"},"fixable":"whitespace","schema":[{"oneOf":[{"enum":["tab"]},{"type":"integer","minimum":0}]},{"type":"object","properties":{"SwitchCase":{"type":"integer","minimum":0,"default":0},"VariableDeclarator":{"oneOf":[{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},{"type":"object","properties":{"var":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"let":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"const":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false}]},"outerIIFEBody":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"MemberExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"FunctionDeclaration":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"FunctionExpression":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"StaticBlock":{"type":"object","properties":{"body":{"type":"integer","minimum":0}},"additionalProperties":false},"CallExpression":{"type":"object","properties":{"arguments":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false},"ArrayExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ObjectExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ImportDeclaration":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"flatTernaryExpressions":{"type":"boolean","default":false},"offsetTernaryExpressions":{"type":"boolean","default":false},"ignoredNodes":{"type":"array","items":{"type":"string","not":{"pattern":":exit$"}}},"ignoreComments":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"wrongIndentation":"Expected indentation of {{expected}} but found {{actual}}."}},"space-unary-ops":{"type":"layout","docs":{"description":"Enforce consistent spacing before or after unary operators","recommended":false,"url":"https://eslint.org/docs/latest/rules/space-unary-ops"},"fixable":"whitespace","schema":[{"type":"object","properties":{"words":{"type":"boolean","default":true},"nonwords":{"type":"boolean","default":false},"overrides":{"type":"object","additionalProperties":{"type":"boolean"}}},"additionalProperties":false}],"messages":{"unexpectedBefore":"Unexpected space before unary operator '{{operator}}'.","unexpectedAfter":"Unexpected space after unary operator '{{operator}}'.","unexpectedAfterWord":"Unexpected space after unary word operator '{{word}}'.","wordOperator":"Unary word operator '{{word}}' must be followed by whitespace.","operator":"Unary operator '{{operator}}' must be followed by whitespace.","beforeUnaryExpressions":"Space is required before unary expressions '{{token}}'."}},"semi":{"type":"layout","docs":{"description":"Require or disallow semicolons instead of ASI","recommended":false,"url":"https://eslint.org/docs/latest/rules/semi"},"fixable":"code","schema":{"anyOf":[{"type":"array","items":[{"enum":["never"]},{"type":"object","properties":{"beforeStatementContinuationChars":{"enum":["always","any","never"]}},"additionalProperties":false}],"minItems":0,"maxItems":2},{"type":"array","items":[{"enum":["always"]},{"type":"object","properties":{"omitLastInOneLineBlock":{"type":"boolean"},"omitLastInOneLineClassBody":{"type":"boolean"}},"additionalProperties":false}],"minItems":0,"maxItems":2}]},"messages":{"missingSemi":"Missing semicolon.","extraSemi":"Extra semicolon."}},"consistent-return":{"type":"suggestion","docs":{"description":"Require `return` statements to either always or never specify values","recommended":false,"url":"https://eslint.org/docs/latest/rules/consistent-return"},"schema":[{"type":"object","properties":{"treatUndefinedAsUnspecified":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"missingReturn":"Expected to return a value at the end of {{name}}.","missingReturnValue":"{{name}} expected a return value.","unexpectedReturnValue":"{{name}} expected no return value."}}}}} +{"results":[{"filePath":"/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'addOne' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":16},{"ruleId":"use-isnan","severity":2,"message":"Use the isNaN function to compare with NaN.","line":2,"column":9,"nodeType":"BinaryExpression","messageId":"comparisonWithNaN","endLine":2,"endColumn":17},{"ruleId":"space-unary-ops","severity":2,"message":"Unexpected space before unary operator '++'.","line":3,"column":16,"nodeType":"UpdateExpression","messageId":"unexpectedBefore","endLine":3,"endColumn":20,"fix":{"range":[57,58],"text":""}},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":3,"column":20,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":4,"endColumn":1,"fix":{"range":[60,60],"text":";"}},{"ruleId":"no-else-return","severity":1,"message":"Unnecessary 'else' after 'return'.","line":4,"column":12,"nodeType":"BlockStatement","messageId":"unexpected","endLine":6,"endColumn":6,"fix":{"range":[0,94],"text":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}"}},{"ruleId":"indent","severity":1,"message":"Expected indentation of 8 spaces but found 6.","line":5,"column":1,"nodeType":"Keyword","messageId":"wrongIndentation","endLine":5,"endColumn":7,"fix":{"range":[74,80],"text":" "}},{"ruleId":"consistent-return","severity":2,"message":"Function 'addOne' expected a return value.","line":5,"column":7,"nodeType":"ReturnStatement","messageId":"missingReturnValue","endLine":5,"endColumn":13},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":13,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":6,"endColumn":1,"fix":{"range":[86,86],"text":";"}},{"ruleId":"no-extra-semi","severity":2,"message":"Unnecessary semicolon.","line":7,"column":2,"nodeType":"EmptyStatement","messageId":"unexpected","endLine":7,"endColumn":3,"fix":{"range":[93,95],"text":"}"}}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":2,"fixableWarningCount":4,"source":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};"}],"metadata":{"rulesMeta":{"no-else-return":{"type":"suggestion","docs":{"description":"Disallow `else` blocks after `return` statements in `if` statements","recommended":false,"url":"https://eslint.org/docs/rules/no-else-return"},"schema":[{"type":"object","properties":{"allowElseIf":{"type":"boolean","default":true}},"additionalProperties":false}],"fixable":"code","messages":{"unexpected":"Unnecessary 'else' after 'return'."}},"indent":{"type":"layout","docs":{"description":"Enforce consistent indentation","recommended":false,"url":"https://eslint.org/docs/rules/indent"},"fixable":"whitespace","schema":[{"oneOf":[{"enum":["tab"]},{"type":"integer","minimum":0}]},{"type":"object","properties":{"SwitchCase":{"type":"integer","minimum":0,"default":0},"VariableDeclarator":{"oneOf":[{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},{"type":"object","properties":{"var":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"let":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"const":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false}]},"outerIIFEBody":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"MemberExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"FunctionDeclaration":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"FunctionExpression":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"StaticBlock":{"type":"object","properties":{"body":{"type":"integer","minimum":0}},"additionalProperties":false},"CallExpression":{"type":"object","properties":{"arguments":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false},"ArrayExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ObjectExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ImportDeclaration":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"flatTernaryExpressions":{"type":"boolean","default":false},"offsetTernaryExpressions":{"type":"boolean","default":false},"ignoredNodes":{"type":"array","items":{"type":"string","not":{"pattern":":exit$"}}},"ignoreComments":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"wrongIndentation":"Expected indentation of {{expected}} but found {{actual}}."}},"space-unary-ops":{"type":"layout","docs":{"description":"Enforce consistent spacing before or after unary operators","recommended":false,"url":"https://eslint.org/docs/rules/space-unary-ops"},"fixable":"whitespace","schema":[{"type":"object","properties":{"words":{"type":"boolean","default":true},"nonwords":{"type":"boolean","default":false},"overrides":{"type":"object","additionalProperties":{"type":"boolean"}}},"additionalProperties":false}],"messages":{"unexpectedBefore":"Unexpected space before unary operator '{{operator}}'.","unexpectedAfter":"Unexpected space after unary operator '{{operator}}'.","unexpectedAfterWord":"Unexpected space after unary word operator '{{word}}'.","wordOperator":"Unary word operator '{{word}}' must be followed by whitespace.","operator":"Unary operator '{{operator}}' must be followed by whitespace.","beforeUnaryExpressions":"Space is required before unary expressions '{{token}}'."}},"semi":{"type":"layout","docs":{"description":"Require or disallow semicolons instead of ASI","recommended":false,"url":"https://eslint.org/docs/rules/semi"},"fixable":"code","schema":{"anyOf":[{"type":"array","items":[{"enum":["never"]},{"type":"object","properties":{"beforeStatementContinuationChars":{"enum":["always","any","never"]}},"additionalProperties":false}],"minItems":0,"maxItems":2},{"type":"array","items":[{"enum":["always"]},{"type":"object","properties":{"omitLastInOneLineBlock":{"type":"boolean"}},"additionalProperties":false}],"minItems":0,"maxItems":2}]},"messages":{"missingSemi":"Missing semicolon.","extraSemi":"Extra semicolon."}},"consistent-return":{"type":"suggestion","docs":{"description":"Require `return` statements to either always or never specify values","recommended":false,"url":"https://eslint.org/docs/rules/consistent-return"},"schema":[{"type":"object","properties":{"treatUndefinedAsUnspecified":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"missingReturn":"Expected to return a value at the end of {{name}}.","missingReturnValue":"{{name}} expected a return value.","unexpectedReturnValue":"{{name}} expected no return value."}}}}} ``` ### json diff --git a/docs/src/use/integrations.md b/docs/src/use/integrations.md index 427377ed7a05..892a5080e359 100644 --- a/docs/src/use/integrations.md +++ b/docs/src/use/integrations.md @@ -15,10 +15,10 @@ If you would like to recommend an integration to be added to this page, [submit ## Editors * Sublime Text 3: - * [SublimeLinter-eslint](https://github.com/SublimeLinter/SublimeLinter-eslint) + * [SublimeLinter-eslint](https://github.com/roadhump/SublimeLinter-eslint) * [Build Next](https://github.com/albertosantini/sublimetext-buildnext) * Vim: - * [ALE](https://github.com/dense-analysis/ale) + * [ALE](https://github.com/w0rp/ale) * [Syntastic](https://github.com/vim-syntastic/syntastic/tree/master/syntax_checkers/javascript) * Emacs: [Flycheck](http://www.flycheck.org/) supports ESLint with the [javascript-eslint](http://www.flycheck.org/en/latest/languages.html#javascript) checker. * Eclipse Orion: ESLint is the [default linter](https://dev.eclipse.org/mhonarc/lists/orion-dev/msg02718.html) @@ -26,6 +26,9 @@ If you would like to recommend an integration to be added to this page, [submit * TextMate 2: * [eslint.tmbundle](https://github.com/ryanfitzer/eslint.tmbundle) * [javascript-eslint.tmbundle](https://github.com/natesilva/javascript-eslint.tmbundle) +* Atom: + * [linter-eslint](https://atom.io/packages/linter-eslint) + * [fast-eslint-8](https://atom.io/packages/fast-eslint-8) * IntelliJ IDEA, WebStorm, PhpStorm, PyCharm, RubyMine, and other JetBrains IDEs: [How to use ESLint](https://www.jetbrains.com/help/webstorm/eslint.html) * Visual Studio: [Linting JavaScript in VS](https://learn.microsoft.com/en-us/visualstudio/javascript/linting-javascript?view=vs-2022) * Visual Studio Code: [ESLint Extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) @@ -34,8 +37,16 @@ If you would like to recommend an integration to be added to this page, [submit ## Build tools * Grunt: [grunt-eslint](https://www.npmjs.com/package/grunt-eslint) +* Gulp: [gulp-eslint](https://www.npmjs.com/package/gulp-eslint) +* Mimosa: [mimosa-eslint](https://www.npmjs.com/package/mimosa-eslint) +* Broccoli: [broccoli-eslint](https://www.npmjs.com/package/broccoli-eslint) +* Browserify: [eslintify](https://www.npmjs.com/package/eslintify) * Webpack: [eslint-webpack-plugin](https://www.npmjs.com/package/eslint-webpack-plugin) * Rollup: [@rollup/plugin-eslint](https://www.npmjs.com/package/@rollup/plugin-eslint) +* Ember-cli: [ember-cli-eslint](https://www.npmjs.com/package/ember-cli-eslint) +* Sails.js: [sails-hook-lint](https://www.npmjs.com/package/sails-hook-lint), [sails-eslint](https://www.npmjs.com/package/sails-eslint) +* Start: [@start/plugin-lib-eslint](https://www.npmjs.com/package/@start/plugin-lib-eslint) +* Brunch: [eslint-brunch](https://www.npmjs.com/package/eslint-brunch) ## Command Line Tools @@ -48,7 +59,11 @@ If you would like to recommend an integration to be added to this page, [submit * [Git Precommit Hook](https://coderwall.com/p/zq8jlq/eslint-pre-commit-hook) * [Git pre-commit hook that only lints staged changes](https://gist.github.com/dahjelle/8ddedf0aebd488208a9a7c829f19b9e8) * [overcommit Git hook manager](https://github.com/brigade/overcommit) -* [Mega-Linter](https://megalinter.io/latest/): Linters aggregator for CI, [embedding eslint](https://megalinter.io/latest/descriptors/javascript_eslint/) +* [Mega-Linter](https://nvuillam.github.io/mega-linter): Linters aggregator for CI, [embedding eslint](https://nvuillam.github.io/mega-linter/descriptors/javascript_eslint/) + +## Testing + +* Mocha.js: [mocha-eslint](https://www.npmjs.com/package/mocha-eslint) ## Other Integration Lists diff --git a/docs/src/use/migrate-to-8.0.0.md b/docs/src/use/migrate-to-8.0.0.md index 9fac8cd597ae..71d92c52087a 100644 --- a/docs/src/use/migrate-to-8.0.0.md +++ b/docs/src/use/migrate-to-8.0.0.md @@ -55,7 +55,7 @@ Node.js 10, 13, 15 all reached end of life either in 2020 or early 2021. ESLint ESLint v8.0.0 has removed the `codeframe` and `table` formatters from the core. These formatters required dependencies that weren't used anywhere else in ESLint, and removing them allows us to reduce the size of ESLint, allowing for faster installation. -**To address:** If you are using the `codeframe` or `table` formatters, you'll need to install the standalone [`eslint-formatter-codeframe`](https://github.com/eslint-community/eslint-formatter-codeframe) or [`eslint-formatter-table`](https://github.com/eslint-community/eslint-formatter-table) packages, respectively, to be able to use them in ESLint v8.0.0. +**To address:** If you are using the `codeframe` or `table` formatters, you'll need to install the standalone [`eslint-formatter-codeframe`](https://github.com/fregante/eslint-formatter-codeframe) or [`eslint-formatter-table`](https://github.com/fregante/eslint-formatter-table) packages, respectively, to be able to use them in ESLint v8.0.0. **Related issue(s):** [#14277](https://github.com/eslint/eslint/issues/14277), [#14316](https://github.com/eslint/eslint/pull/14316) @@ -135,7 +135,7 @@ module.exports = { }; ``` -The [eslint-plugin/require-meta-has-suggestions](https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/main/docs/rules/require-meta-has-suggestions.md) rule can automatically fix and enforce that your rules are properly specifying `meta.hasSuggestions`. +The [eslint-plugin/require-meta-has-suggestions](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-has-suggestions.md) rule can automatically fix and enforce that your rules are properly specifying `meta.hasSuggestions`. **Related issue(s):** [#14312](https://github.com/eslint/eslint/issues/14312) @@ -164,9 +164,9 @@ module.exports = { }; ``` -The [eslint-plugin/require-meta-fixable](https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/main/docs/rules/require-meta-fixable.md) rule can automatically fix and enforce that your rules are properly specifying `meta.fixable`. +The [eslint-plugin/require-meta-fixable](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-fixable.md) rule can automatically fix and enforce that your rules are properly specifying `meta.fixable`. -The [eslint-plugin/prefer-object-rule](https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/main/docs/rules/prefer-object-rule.md) rule can automatically fix and enforce that your rules are written with the object format instead of the deprecated function format. +The [eslint-plugin/prefer-object-rule](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-object-rule.md) rule can automatically fix and enforce that your rules are written with the object format instead of the deprecated function format. See the [rule documentation](../extend/custom-rules) for more information on writing rules. @@ -178,7 +178,7 @@ Back in ESLint v4.0.0, we deprecated `SourceCode#getComments()`, but we neglecte The `SourceCode#getComments()` method will be removed in v9.0.0. -**To address:** If your rule uses `SourceCode#getComments()`, please use [`SourceCode#getCommentsBefore()`, `SourceCode#getCommentsAfter()`, or `SourceCode#getCommentsInside()`](../extend/custom-rules#accessing-comments). +**To address:** If your rule uses `SourceCode#getComments()`, please use [`SourceCode#getCommentsBefore()`, `SourceCode#getCommentsAfter()`, or `SourceCode#getCommentsInside()`](../extend/custom-rules#sourcecodegetcommentsbefore-sourcecodegetcommentsafter-and-sourcecodegetcommentsinside). **Related issue(s):** [#14744](https://github.com/eslint/eslint/issues/14744) diff --git a/docs/src/use/migrating-to-5.0.0.md b/docs/src/use/migrating-to-5.0.0.md index 96c54ec5dee8..5eb4ec7ad1a1 100644 --- a/docs/src/use/migrating-to-5.0.0.md +++ b/docs/src/use/migrating-to-5.0.0.md @@ -221,7 +221,7 @@ Previously, the `context.getScope()` method changed its behavior based on the `p Additionally, `context.getScope()` incorrectly returned the parent scope of the proper scope on `CatchClause` (in ES5), `ForStatement` (in โ‰งES2015), `ForInStatement` (in โ‰งES2015), `ForOfStatement`, and `WithStatement` nodes. -In ESLint v5, the `context.getScope()` method has the same behavior regardless of `parserOptions.ecmaVersion` and returns the proper scope. See [the documentation](../extend/scope-manager-interface) for more details on which scopes are returned. +In ESLint v5, the `context.getScope()` method has the same behavior regardless of `parserOptions.ecmaVersion` and returns the proper scope. See [the documentation](../extend/custom-rules#contextgetscope) for more details on which scopes are returned. **To address:** If you have written a custom rule that uses the `context.getScope()` method in node handlers, you may need to update it to account for the modified scope information. diff --git a/docs/src/use/migrating-to-7.0.0.md b/docs/src/use/migrating-to-7.0.0.md index 86b81cf832a6..898bcad11a3f 100644 --- a/docs/src/use/migrating-to-7.0.0.md +++ b/docs/src/use/migrating-to-7.0.0.md @@ -145,7 +145,7 @@ To allow for the colocation of comments that provide context with the directive, ## Node.js/CommonJS rules have been deprecated -The ten Node.js/CommonJS rules in core have been deprecated and moved to the [eslint-plugin-node](https://github.com/mysticatea/eslint-plugin-node) plugin (for ESLint v8.0.0 and later, use the maintained [eslint-plugin-n](https://github.com/eslint-community/eslint-plugin-n) fork instead) . +The ten Node.js/CommonJS rules in core have been deprecated and moved to the [eslint-plugin-node](https://github.com/mysticatea/eslint-plugin-node) plugin. **To address:** As per [our deprecation policy](../use/rule-deprecation), the deprecated rules will remain in core for the foreseeable future and are still available for use. However, we will no longer be updating or fixing any bugs in those rules. To use a supported version of the rules, we recommend using the corresponding rules in the plugin instead. From 030a82737f51563f9a7b4985fc91b6d8eab54fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Tue, 6 Jun 2023 04:50:14 +0800 Subject: [PATCH 016/248] Revert "feat: docs license (#17010)" (#17231) This reverts commit b3634f695ddab6a82c0a9b1d8695e62b60d23366. --- docs/README.md | 4 ---- docs/src/_data/sites/en.yml | 2 +- docs/src/_data/sites/zh-hans.yml | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/README.md b/docs/README.md index dbf3c02e8f46..140608aa2d22 100644 --- a/docs/README.md +++ b/docs/README.md @@ -39,7 +39,3 @@ To autofix JS files, run this from the root folder (not the `docs` folder): ```shell npm run fix:docsjs ``` - -## License - -ยฉ OpenJS Foundation and ESLint contributors, [www.openjsf.org](https://www.openjsf.org/). Content licensed under [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/). diff --git a/docs/src/_data/sites/en.yml b/docs/src/_data/sites/en.yml index ccd87ad37bdf..1afff54b98b6 100644 --- a/docs/src/_data/sites/en.yml +++ b/docs/src/_data/sites/en.yml @@ -87,7 +87,7 @@ footer: language: Language latest: Latest copyright: > - © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + © OpenJS Foundation and ESLint contributors, www.openjsf.org links: open_jsf: The OpenJS Foundation terms: Terms of Use diff --git a/docs/src/_data/sites/zh-hans.yml b/docs/src/_data/sites/zh-hans.yml index efa9474b89d4..51c91288767a 100644 --- a/docs/src/_data/sites/zh-hans.yml +++ b/docs/src/_data/sites/zh-hans.yml @@ -85,7 +85,7 @@ footer: language: ่ฏญ่จ€ latest: ๆœ€ๆ–ฐ copyright: > - © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + © OpenJS Foundation and ESLint contributors, www.openjsf.org links: open_jsf: OpenJS ๅŸบ้‡‘ไผš terms: ไฝฟ็”จๆกๆฌพ From 6a0196c51310630a0ff96a1e8d7f257c2c7adda9 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 7 Jun 2023 09:25:04 +0200 Subject: [PATCH 017/248] chore: use eslint-plugin-eslint-plugin flat configs (#17204) * chore: use eslint-plugin-eslint-plugin flat configs * fix bin tests * fix cli tests * fix flat-eslint tests * fix eslint tests * fix cli-engine tests --- .eslintrc.js | 4 - eslint.config.js | 41 ++-- lib/rules/logical-assignment-operators.js | 1 - lib/rules/newline-after-var.js | 2 - package.json | 2 +- tests/bin/eslint.js | 8 +- .../simple-valid-project/.eslintrc.js | 5 + .../simple-valid-project/eslint.config.js | 5 + tests/fixtures/simple-valid-project/foo.js | 1 + .../simple-valid-project/src/foobar.js | 1 + tests/lib/cli-engine/cli-engine.js | 202 +++++++++++++----- tests/lib/cli.js | 55 ++++- tests/lib/eslint/eslint.js | 165 ++++++++++---- tests/lib/eslint/flat-eslint.js | 21 +- 14 files changed, 361 insertions(+), 152 deletions(-) create mode 100644 tests/fixtures/simple-valid-project/.eslintrc.js create mode 100644 tests/fixtures/simple-valid-project/eslint.config.js create mode 100644 tests/fixtures/simple-valid-project/foo.js create mode 100644 tests/fixtures/simple-valid-project/src/foobar.js diff --git a/.eslintrc.js b/.eslintrc.js index 4ceed4d3693f..ae258dc64415 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -96,9 +96,6 @@ module.exports = { "plugin:eslint-plugin/rules-recommended" ], rules: { - "eslint-plugin/no-missing-message-ids": "error", - "eslint-plugin/no-unused-message-ids": "error", - "eslint-plugin/prefer-message-ids": "error", "eslint-plugin/prefer-placeholders": "error", "eslint-plugin/prefer-replace-text": "error", "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], @@ -119,7 +116,6 @@ module.exports = { "plugin:eslint-plugin/tests-recommended" ], rules: { - "eslint-plugin/prefer-output-null": "error", "eslint-plugin/test-case-property-ordering": "error", "eslint-plugin/test-case-shorthand-strings": "error" } diff --git a/eslint.config.js b/eslint.config.js index 5c488a57a151..492e6793ffa2 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -27,10 +27,12 @@ const path = require("path"); const internalPlugin = require("eslint-plugin-internal-rules"); -const eslintPlugin = require("eslint-plugin-eslint-plugin"); +const eslintPluginRulesRecommendedConfig = require("eslint-plugin-eslint-plugin/configs/rules-recommended"); +const eslintPluginTestsRecommendedConfig = require("eslint-plugin-eslint-plugin/configs/tests-recommended"); const { FlatCompat } = require("@eslint/eslintrc"); const js = require("./packages/js"); const globals = require("globals"); +const merge = require("lodash.merge"); //----------------------------------------------------------------------------- // Helpers @@ -99,8 +101,7 @@ module.exports = [ }, { plugins: { - "internal-rules": internalPlugin, - "eslint-plugin": eslintPlugin + "internal-rules": internalPlugin }, languageOptions: { ecmaVersion: "latest" @@ -129,33 +130,31 @@ module.exports = [ { files: ["lib/rules/*", "tools/internal-rules/*"], ignores: ["**/index.js"], - rules: { - ...eslintPlugin.configs["rules-recommended"].rules, - "eslint-plugin/no-missing-message-ids": "error", - "eslint-plugin/no-unused-message-ids": "error", - "eslint-plugin/prefer-message-ids": "error", - "eslint-plugin/prefer-placeholders": "error", - "eslint-plugin/prefer-replace-text": "error", - "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], - "eslint-plugin/require-meta-docs-description": ["error", { pattern: "^(Enforce|Require|Disallow) .+[^. ]$" }], - "internal-rules/no-invalid-meta": "error" - } + ...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" + } + }) }, { files: ["lib/rules/*"], - ignores: ["index.js"], + ignores: ["**/index.js"], rules: { "eslint-plugin/require-meta-docs-url": ["error", { pattern: "https://eslint.org/docs/latest/rules/{{name}}" }] } }, { files: ["tests/lib/rules/*", "tests/tools/internal-rules/*"], - rules: { - ...eslintPlugin.configs["tests-recommended"].rules, - "eslint-plugin/prefer-output-null": "error", - "eslint-plugin/test-case-property-ordering": "error", - "eslint-plugin/test-case-shorthand-strings": "error" - } + ...merge({}, eslintPluginTestsRecommendedConfig, { + rules: { + "eslint-plugin/test-case-property-ordering": "error", + "eslint-plugin/test-case-shorthand-strings": "error" + } + }) }, { files: ["tests/**/*.js"], diff --git a/lib/rules/logical-assignment-operators.js b/lib/rules/logical-assignment-operators.js index d373bec6d093..cb1cbe5a16b9 100644 --- a/lib/rules/logical-assignment-operators.js +++ b/lib/rules/logical-assignment-operators.js @@ -188,7 +188,6 @@ module.exports = { }] }, fixable: "code", - // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- Does not detect conditional suggestions hasSuggestions: true, messages: { assignment: "Assignment (=) can be replaced with operator assignment ({{operator}}).", diff --git a/lib/rules/newline-after-var.js b/lib/rules/newline-after-var.js index 5c9b5fbd10f3..dc8b24d47382 100644 --- a/lib/rules/newline-after-var.js +++ b/lib/rules/newline-after-var.js @@ -212,7 +212,6 @@ module.exports = { context.report({ node, messageId: "unexpected", - data: { identifier: node.name }, fix(fixer) { const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(astUtils.LINEBREAK_MATCHER); @@ -231,7 +230,6 @@ module.exports = { context.report({ node, messageId: "expected", - data: { identifier: node.name }, fix(fixer) { if ((noNextLineToken ? getLastCommentLineOfBlock(nextLineNum) : lastToken.loc.end.line) === nextToken.loc.start.line) { return fixer.insertTextBefore(nextToken, "\n\n"); diff --git a/package.json b/package.json index 7a34beb8fa7a..9798881120eb 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "eslint": "file:.", "eslint-config-eslint": "file:packages/eslint-config-eslint", "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-eslint-plugin": "^4.4.0", + "eslint-plugin-eslint-plugin": "^5.1.0", "eslint-plugin-internal-rules": "file:tools/internal-rules", "eslint-plugin-jsdoc": "^38.1.6", "eslint-plugin-n": "^15.2.4", diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js index 848dba146713..b09496202b03 100644 --- a/tests/bin/eslint.js +++ b/tests/bin/eslint.js @@ -204,10 +204,10 @@ describe("bin/eslint.js", () => { }); describe("running on files", () => { - it("has exit code 0 if no linting errors occur", () => assertExitCode(runESLint(["bin/eslint.js"]), 0)); + it("has exit code 0 if no linting errors occur", () => assertExitCode(runESLint(["bin/eslint.js", "--no-config-lookup"]), 0)); it("has exit code 0 if a linting warning is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--no-config-lookup", "--rule", "semi: [1, never]"]), 0)); it("has exit code 1 if a linting error is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--no-config-lookup", "--rule", "semi: [2, never]"]), 1)); - it("has exit code 1 if a syntax error is thrown", () => assertExitCode(runESLint(["tests/fixtures/exit-on-fatal-error/fatal-error.js", "--no-ignore"]), 1)); + it("has exit code 1 if a syntax error is thrown", () => assertExitCode(runESLint(["tests/fixtures/exit-on-fatal-error/fatal-error.js", "--no-config-lookup"]), 1)); }); describe("automatically fixing files", () => { @@ -359,7 +359,7 @@ describe("bin/eslint.js", () => { describe("handling crashes", () => { it("prints the error message to stderr in the event of a crash", () => { - const child = runESLint(["--rule=no-restricted-syntax:[error, 'Invalid Selector [[[']", "Makefile.js"]); + const child = runESLint(["--rule=no-restricted-syntax:[error, 'Invalid Selector [[[']", "--no-config-lookup", "Makefile.js"]); const exitCodeAssertion = assertExitCode(child, 2); const outputAssertion = getOutput(child).then(output => { const expectedSubstring = "Syntax error in selector"; @@ -372,7 +372,7 @@ describe("bin/eslint.js", () => { }); it("prints the error message exactly once to stderr in the event of a crash", () => { - const child = runESLint(["--rule=no-restricted-syntax:[error, 'Invalid Selector [[[']", "Makefile.js"]); + const child = runESLint(["--rule=no-restricted-syntax:[error, 'Invalid Selector [[[']", "--no-config-lookup", "Makefile.js"]); const exitCodeAssertion = assertExitCode(child, 2); const outputAssertion = getOutput(child).then(output => { const expectedSubstring = "Syntax error in selector"; diff --git a/tests/fixtures/simple-valid-project/.eslintrc.js b/tests/fixtures/simple-valid-project/.eslintrc.js new file mode 100644 index 000000000000..cf2cd6e50a4c --- /dev/null +++ b/tests/fixtures/simple-valid-project/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + rules: { + quotes: ["error", "single"] + } +}; diff --git a/tests/fixtures/simple-valid-project/eslint.config.js b/tests/fixtures/simple-valid-project/eslint.config.js new file mode 100644 index 000000000000..6817217d965f --- /dev/null +++ b/tests/fixtures/simple-valid-project/eslint.config.js @@ -0,0 +1,5 @@ +module.exports = [{ + rules: { + quotes: ["error", "single"] + } +}]; diff --git a/tests/fixtures/simple-valid-project/foo.js b/tests/fixtures/simple-valid-project/foo.js new file mode 100644 index 000000000000..9e9f472d7eec --- /dev/null +++ b/tests/fixtures/simple-valid-project/foo.js @@ -0,0 +1 @@ +var a = 'b'; diff --git a/tests/fixtures/simple-valid-project/src/foobar.js b/tests/fixtures/simple-valid-project/src/foobar.js new file mode 100644 index 000000000000..9e9f472d7eec --- /dev/null +++ b/tests/fixtures/simple-valid-project/src/foobar.js @@ -0,0 +1 @@ +var a = 'b'; diff --git a/tests/lib/cli-engine/cli-engine.js b/tests/lib/cli-engine/cli-engine.js index 2528a398ea1e..971d6e609640 100644 --- a/tests/lib/cli-engine/cli-engine.js +++ b/tests/lib/cli-engine/cli-engine.js @@ -138,57 +138,83 @@ describe("CLIEngine", () => { let engine; - it("should report the total and per file errors when using local cwd .eslintrc", () => { + describe("when using local cwd .eslintrc", () => { - engine = new CLIEngine(); + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: path.join(os.tmpdir(), "eslint/multiple-rules-config"), + files: { + ".eslintrc.json": { + root: true, + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2 + }, + env: { + node: true + } + } + } + }); - const report = engine.executeOnText("var foo = 'bar';"); + beforeEach(prepare); + afterEach(cleanup); - assert.strictEqual(report.results.length, 1); - assert.strictEqual(report.errorCount, 5); - assert.strictEqual(report.warningCount, 0); - assert.strictEqual(report.fatalErrorCount, 0); - assert.strictEqual(report.fixableErrorCount, 3); - assert.strictEqual(report.fixableWarningCount, 0); - assert.strictEqual(report.results[0].messages.length, 5); - assert.strictEqual(report.results[0].messages[0].ruleId, "strict"); - assert.strictEqual(report.results[0].messages[1].ruleId, "no-var"); - assert.strictEqual(report.results[0].messages[2].ruleId, "no-unused-vars"); - assert.strictEqual(report.results[0].messages[3].ruleId, "quotes"); - assert.strictEqual(report.results[0].messages[4].ruleId, "eol-last"); - assert.strictEqual(report.results[0].fixableErrorCount, 3); - assert.strictEqual(report.results[0].fixableWarningCount, 0); - assert.strictEqual(report.results[0].suppressedMessages.length, 0); - }); + it("should report the total and per file errors", () => { - it("should report the total and per file warnings when using local cwd .eslintrc", () => { + engine = new CLIEngine({ cwd: getPath() }); - engine = new CLIEngine({ - rules: { - quotes: 1, - "no-var": 1, - "eol-last": 1, - strict: 1, - "no-unused-vars": 1 - } + const report = engine.executeOnText("var foo = 'bar';"); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.errorCount, 5); + assert.strictEqual(report.warningCount, 0); + assert.strictEqual(report.fatalErrorCount, 0); + assert.strictEqual(report.fixableErrorCount, 3); + assert.strictEqual(report.fixableWarningCount, 0); + assert.strictEqual(report.results[0].messages.length, 5); + assert.strictEqual(report.results[0].messages[0].ruleId, "strict"); + assert.strictEqual(report.results[0].messages[1].ruleId, "no-var"); + assert.strictEqual(report.results[0].messages[2].ruleId, "no-unused-vars"); + assert.strictEqual(report.results[0].messages[3].ruleId, "quotes"); + assert.strictEqual(report.results[0].messages[4].ruleId, "eol-last"); + assert.strictEqual(report.results[0].fixableErrorCount, 3); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); }); - const report = engine.executeOnText("var foo = 'bar';"); + it("should report the total and per file warnings", () => { - assert.strictEqual(report.results.length, 1); - assert.strictEqual(report.errorCount, 0); - assert.strictEqual(report.warningCount, 5); - assert.strictEqual(report.fixableErrorCount, 0); - assert.strictEqual(report.fixableWarningCount, 3); - assert.strictEqual(report.results[0].messages.length, 5); - assert.strictEqual(report.results[0].messages[0].ruleId, "strict"); - assert.strictEqual(report.results[0].messages[1].ruleId, "no-var"); - assert.strictEqual(report.results[0].messages[2].ruleId, "no-unused-vars"); - assert.strictEqual(report.results[0].messages[3].ruleId, "quotes"); - assert.strictEqual(report.results[0].messages[4].ruleId, "eol-last"); - assert.strictEqual(report.results[0].fixableErrorCount, 0); - assert.strictEqual(report.results[0].fixableWarningCount, 3); - assert.strictEqual(report.results[0].suppressedMessages.length, 0); + engine = new CLIEngine({ + cwd: getPath(), + rules: { + quotes: 1, + "no-var": 1, + "eol-last": 1, + strict: 1, + "no-unused-vars": 1 + } + }); + + const report = engine.executeOnText("var foo = 'bar';"); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.errorCount, 0); + assert.strictEqual(report.warningCount, 5); + assert.strictEqual(report.fixableErrorCount, 0); + assert.strictEqual(report.fixableWarningCount, 3); + assert.strictEqual(report.results[0].messages.length, 5); + assert.strictEqual(report.results[0].messages[0].ruleId, "strict"); + assert.strictEqual(report.results[0].messages[1].ruleId, "no-var"); + assert.strictEqual(report.results[0].messages[2].ruleId, "no-unused-vars"); + assert.strictEqual(report.results[0].messages[3].ruleId, "quotes"); + assert.strictEqual(report.results[0].messages[4].ruleId, "eol-last"); + assert.strictEqual(report.results[0].fixableErrorCount, 0); + assert.strictEqual(report.results[0].fixableWarningCount, 3); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); }); it("should report one message when using specific config file", () => { @@ -836,10 +862,12 @@ describe("CLIEngine", () => { engine = new CLIEngine({ cwd: originalDir, - configFile: ".eslintrc.js" + useEslintrc: false, + ignore: false, + overrideConfigFile: "tests/fixtures/simple-valid-project/.eslintrc.js" }); - const report = engine.executeOnFiles(["lib/**/cli*.js"]); + const report = engine.executeOnFiles(["tests/fixtures/simple-valid-project/**/foo*.js"]); assert.strictEqual(report.results.length, 2); assert.strictEqual(report.results[0].messages.length, 0); @@ -851,10 +879,16 @@ describe("CLIEngine", () => { engine = new CLIEngine({ cwd: originalDir, - configFile: ".eslintrc.js" + useEslintrc: false, + ignore: false, + overrideConfigFile: "tests/fixtures/simple-valid-project/.eslintrc.js" }); - const report = engine.executeOnFiles(["lib/**/cli*.js", "lib/cli.?s", "lib/{cli,cli-engine/cli-engine}.js"]); + const report = engine.executeOnFiles([ + "tests/fixtures/simple-valid-project/**/foo*.js", + "tests/fixtures/simple-valid-project/foo.?s", + "tests/fixtures/simple-valid-project/{foo,src/foobar}.js" + ]); assert.strictEqual(report.results.length, 2); assert.strictEqual(report.results[0].messages.length, 0); @@ -1394,6 +1428,7 @@ describe("CLIEngine", () => { it("should throw an error when all given files are ignored", () => { engine = new CLIEngine({ + useEslintrc: false, ignorePath: getFixturePath(".eslintignore") }); @@ -1404,6 +1439,7 @@ describe("CLIEngine", () => { it("should throw an error when all given files are ignored even with a `./` prefix", () => { engine = new CLIEngine({ + useEslintrc: false, ignorePath: getFixturePath(".eslintignore") }); @@ -1450,6 +1486,7 @@ describe("CLIEngine", () => { it("should throw an error when all given files are ignored via ignore-pattern", () => { engine = new CLIEngine({ + useEslintrc: false, ignorePattern: "tests/fixtures/single-quoted.js" }); @@ -1712,7 +1749,7 @@ describe("CLIEngine", () => { it("should warn when deprecated rules are configured", () => { engine = new CLIEngine({ cwd: originalDir, - configFile: ".eslintrc.js", + useEslintrc: false, rules: { "indent-legacy": 1, "require-jsdoc": 1, @@ -1736,7 +1773,7 @@ describe("CLIEngine", () => { it("should not warn when deprecated rules are not configured", () => { engine = new CLIEngine({ cwd: originalDir, - configFile: ".eslintrc.js", + useEslintrc: false, rules: { indent: 1, "valid-jsdoc": 0, "require-jsdoc": 0 } }); @@ -4881,7 +4918,21 @@ describe("CLIEngine", () => { it("should report 5 error messages when looking for errors only", () => { process.chdir(originalDir); - const engine = new CLIEngine(); + const engine = new CLIEngine({ + useEslintrc: false, + baseConfig: { + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2 + }, + env: { + node: true + } + } + }); const report = engine.executeOnText("var foo = 'bar';"); const errorResults = CLIEngine.getErrorResults(report.results); @@ -4905,7 +4956,21 @@ describe("CLIEngine", () => { it("should report no error messages when looking for errors only", () => { process.chdir(originalDir); - const engine = new CLIEngine(); + const engine = new CLIEngine({ + useEslintrc: false, + baseConfig: { + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2 + }, + env: { + node: true + } + } + }); const report = engine.executeOnText("var foo = 'bar'; // eslint-disable-line strict, no-var, no-unused-vars, quotes, eol-last -- justification"); const errorResults = CLIEngine.getErrorResults(report.results); @@ -4916,12 +4981,18 @@ describe("CLIEngine", () => { it("should not mutate passed report.results parameter", () => { process.chdir(originalDir); const engine = new CLIEngine({ - rules: { quotes: [1, "double"] } + useEslintrc: false, + rules: { + quotes: [1, "double"], + "no-var": 2 + } }); const report = engine.executeOnText("var foo = 'bar';"); const reportResultsLength = report.results[0].messages.length; + assert.strictEqual(report.results[0].messages.length, 2); + CLIEngine.getErrorResults(report.results); assert.lengthOf(report.results[0].messages, reportResultsLength); @@ -4930,9 +5001,16 @@ describe("CLIEngine", () => { it("should report no suppressed error messages when looking for errors only", () => { process.chdir(originalDir); const engine = new CLIEngine({ + useEslintrc: false, rules: { - quotes: [1, "double"], - "no-var": 2 + quotes: 1, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2 + }, + env: { + node: true } }); @@ -4948,7 +5026,21 @@ describe("CLIEngine", () => { it("should report a warningCount of 0 when looking for errors only", () => { process.chdir(originalDir); - const engine = new CLIEngine(); + const engine = new CLIEngine({ + useEslintrc: false, + baseConfig: { + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2 + }, + env: { + node: true + } + } + }); const report = engine.executeOnText("var foo = 'bar';"); const errorResults = CLIEngine.getErrorResults(report.results); diff --git a/tests/lib/cli.js b/tests/lib/cli.js index a487c9553def..15556e5cfd1e 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -114,15 +114,16 @@ describe("cli", () => { 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(`-c ${configFile} --stdin --stdin-filename foo.js`, "var foo = 'bar';", useFlatConfig); + 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(["--stdin", flag, "--stdin-filename", "foo.js"], "", useFlatConfig); + const result = await cli.execute(["argv0", "argv1", "--stdin", flag, "--stdin-filename", "foo.js"], "", useFlatConfig); assert.strictEqual(result, 0); assert.isTrue(log.info.notCalled); @@ -206,20 +207,30 @@ describe("cli", () => { describe("when there is a local config file", () => { + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + it(`should load the local config file with configType:${configType}`, async () => { - await cli.execute("lib/cli.js", null, useFlatConfig); + await cli.execute("cli/passing.js --no-ignore", null, useFlatConfig); }); if (useFlatConfig) { it(`should load the local config file with glob pattern and configType:${configType}`, async () => { - await cli.execute("lib/cli*.js", null, useFlatConfig); + await cli.execute("cli/pass*.js --no-ignore", null, 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("lib\\cli*.js", null, useFlatConfig); + await cli.execute("cli\\pass*.js --no-ignore", null, useFlatConfig); }); } }); @@ -336,10 +347,21 @@ describe("cli", () => { }); describe("when given an invalid formatter path", () => { + + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + 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(`-f ${formatterPath} ${filePath}`, null, useFlatConfig); + const exit = await cli.execute(`--no-ignore -f ${formatterPath} ${filePath}`, null, useFlatConfig); assert.strictEqual(exit, 2); }); @@ -869,13 +891,15 @@ describe("cli", () => { }); describe("when supplied with report output file path", () => { + const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; + 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 = `--no-ignore --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; + const code = `${flag} --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; await cli.execute(code, null, useFlatConfig); @@ -885,7 +909,7 @@ describe("cli", () => { it(`should return an error if the path is a directory with configType:${configType}`, async () => { const filePath = getFixturePath("single-quoted.js"); - const code = `--no-ignore --rule 'quotes: [1, double]' --o tests/output ${filePath}`; + const code = `${flag} --rule 'quotes: [1, double]' --o tests/output ${filePath}`; fs.mkdirSync("tests/output"); @@ -898,7 +922,7 @@ describe("cli", () => { 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 = `--no-ignore --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; + const code = `${flag} --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; fs.writeFileSync("tests/output", "foo"); @@ -1277,8 +1301,19 @@ describe("cli", () => { }); describe("when passing --print-config", () => { + + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + it(`should print out the configuration with configType:${configType}`, async () => { - const filePath = getFixturePath("xxxx"); + const filePath = getFixturePath("xxx.js"); const exitCode = await cli.execute(`--print-config ${filePath}`, null, useFlatConfig); diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index c05a869641d1..a422f4b867f5 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -117,7 +117,7 @@ describe("ESLint", () => { it("the default value of 'options.cwd' should be the current working directory.", async () => { process.chdir(__dirname); try { - const engine = new ESLint(); + const engine = new ESLint({ useEslintrc: false }); const results = await engine.lintFiles("eslint.js"); assert.strictEqual(path.dirname(results[0].filePath), __dirname); @@ -239,46 +239,71 @@ describe("ESLint", () => { describe("lintText()", () => { let eslint; - it("should report the total and per file errors when using local cwd .eslintrc", async () => { - eslint = new ESLint(); - const results = await eslint.lintText("var foo = 'bar';"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 5); - assert.strictEqual(results[0].messages[0].ruleId, "strict"); - assert.strictEqual(results[0].messages[1].ruleId, "no-var"); - assert.strictEqual(results[0].messages[2].ruleId, "no-unused-vars"); - assert.strictEqual(results[0].messages[3].ruleId, "quotes"); - assert.strictEqual(results[0].messages[4].ruleId, "eol-last"); - assert.strictEqual(results[0].fixableErrorCount, 3); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 0); - }); - - it("should report the total and per file warnings when using local cwd .eslintrc", async () => { - eslint = new ESLint({ - overrideConfig: { - rules: { - quotes: 1, - "no-var": 1, - "eol-last": 1, - strict: 1, - "no-unused-vars": 1 + describe("when using local cwd .eslintrc", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: path.join(os.tmpdir(), "eslint/multiple-rules-config"), + files: { + ".eslintrc.json": { + root: true, + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2 + }, + env: { + node: true + } } } }); - const results = await eslint.lintText("var foo = 'bar';"); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 5); - assert.strictEqual(results[0].messages[0].ruleId, "strict"); - assert.strictEqual(results[0].messages[1].ruleId, "no-var"); - assert.strictEqual(results[0].messages[2].ruleId, "no-unused-vars"); - assert.strictEqual(results[0].messages[3].ruleId, "quotes"); - assert.strictEqual(results[0].messages[4].ruleId, "eol-last"); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 3); - assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + beforeEach(prepare); + afterEach(cleanup); + + it("should report the total and per file errors", async () => { + eslint = new ESLint({ cwd: getPath() }); + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 5); + assert.strictEqual(results[0].messages[0].ruleId, "strict"); + assert.strictEqual(results[0].messages[1].ruleId, "no-var"); + assert.strictEqual(results[0].messages[2].ruleId, "no-unused-vars"); + assert.strictEqual(results[0].messages[3].ruleId, "quotes"); + assert.strictEqual(results[0].messages[4].ruleId, "eol-last"); + assert.strictEqual(results[0].fixableErrorCount, 3); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + }); + + it("should report the total and per file warnings", async () => { + eslint = new ESLint({ + cwd: getPath(), + overrideConfig: { + rules: { + quotes: 1, + "no-var": 1, + "eol-last": 1, + strict: 1, + "no-unused-vars": 1 + } + } + }); + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 5); + assert.strictEqual(results[0].messages[0].ruleId, "strict"); + assert.strictEqual(results[0].messages[1].ruleId, "no-var"); + assert.strictEqual(results[0].messages[2].ruleId, "no-unused-vars"); + assert.strictEqual(results[0].messages[3].ruleId, "quotes"); + assert.strictEqual(results[0].messages[4].ruleId, "eol-last"); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 3); + assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + }); }); it("should report one message when using specific config file", async () => { @@ -950,9 +975,11 @@ describe("ESLint", () => { it("should report zero messages when given a config file and a valid file", async () => { eslint = new ESLint({ cwd: originalDir, - overrideConfigFile: ".eslintrc.js" + useEslintrc: false, + ignore: false, + overrideConfigFile: "tests/fixtures/simple-valid-project/.eslintrc.js" }); - const results = await eslint.lintFiles(["lib/**/cli*.js"]); + const results = await eslint.lintFiles(["tests/fixtures/simple-valid-project/**/foo*.js"]); assert.strictEqual(results.length, 2); assert.strictEqual(results[0].messages.length, 0); @@ -962,9 +989,15 @@ describe("ESLint", () => { it("should handle multiple patterns with overlapping files", async () => { eslint = new ESLint({ cwd: originalDir, - overrideConfigFile: ".eslintrc.js" + useEslintrc: false, + ignore: false, + overrideConfigFile: "tests/fixtures/simple-valid-project/.eslintrc.js" }); - const results = await eslint.lintFiles(["lib/**/cli*.js", "lib/cli.?s", "lib/{cli,cli-engine/cli-engine}.js"]); + const results = await eslint.lintFiles([ + "tests/fixtures/simple-valid-project/**/foo*.js", + "tests/fixtures/simple-valid-project/foo.?s", + "tests/fixtures/simple-valid-project/{foo,src/foobar}.js" + ]); assert.strictEqual(results.length, 2); assert.strictEqual(results[0].messages.length, 0); @@ -1396,6 +1429,10 @@ describe("ESLint", () => { }); it("should throw an error when all given files are ignored", async () => { + eslint = new ESLint({ + useEslintrc: false, + ignorePath: getFixturePath(".eslintignore") + }); await assert.rejects(async () => { await eslint.lintFiles(["tests/fixtures/cli-engine/"]); }, /All files matched by 'tests\/fixtures\/cli-engine\/' are ignored\./u); @@ -1403,6 +1440,7 @@ describe("ESLint", () => { it("should throw an error when all given files are ignored even with a `./` prefix", async () => { eslint = new ESLint({ + useEslintrc: false, ignorePath: getFixturePath(".eslintignore") }); @@ -1466,6 +1504,7 @@ describe("ESLint", () => { it("should throw an error when all given files are ignored via ignore-pattern", async () => { eslint = new ESLint({ + useEslintrc: false, overrideConfig: { ignorePatterns: "tests/fixtures/single-quoted.js" } @@ -1675,7 +1714,7 @@ describe("ESLint", () => { it("should warn when deprecated rules are configured", async () => { eslint = new ESLint({ cwd: originalDir, - overrideConfigFile: ".eslintrc.js", + useEslintrc: false, overrideConfig: { rules: { "indent-legacy": 1, @@ -1699,7 +1738,7 @@ describe("ESLint", () => { it("should not warn when deprecated rules are not configured", async () => { eslint = new ESLint({ cwd: originalDir, - overrideConfigFile: ".eslintrc.js", + useEslintrc: false, overrideConfig: { rules: { indent: 1, "valid-jsdoc": 0, "require-jsdoc": 0 } } @@ -4865,7 +4904,21 @@ describe("ESLint", () => { describe("getErrorResults()", () => { it("should report 5 error messages when looking for errors only", async () => { process.chdir(originalDir); - const engine = new ESLint(); + const engine = new ESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2 + }, + env: { + node: true + } + } + }); const results = await engine.lintText("var foo = 'bar';"); const errorResults = ESLint.getErrorResults(results); @@ -4888,13 +4941,19 @@ describe("ESLint", () => { it("should not mutate passed report parameter", async () => { process.chdir(originalDir); const engine = new ESLint({ + useEslintrc: false, overrideConfig: { - rules: { quotes: [1, "double"] } + rules: { + quotes: [1, "double"], + "no-var": 2 + } } }); const results = await engine.lintText("var foo = 'bar';"); const reportResultsLength = results[0].messages.length; + assert.strictEqual(results[0].messages.length, 2); + ESLint.getErrorResults(results); assert.strictEqual(results[0].messages.length, reportResultsLength); @@ -4902,7 +4961,21 @@ describe("ESLint", () => { it("should report a warningCount of 0 when looking for errors only", async () => { process.chdir(originalDir); - const engine = new ESLint(); + const engine = new ESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2 + }, + env: { + node: true + } + } + }); const results = await engine.lintText("var foo = 'bar';"); const errorResults = ESLint.getErrorResults(results); diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index b68dabc2837c..ffad0a793ea4 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -769,9 +769,9 @@ describe("FlatESLint", () => { it("should report zero messages when given a config file and a valid file", async () => { eslint = new FlatESLint({ cwd: originalDir, - overrideConfigFile: "eslint.config.js" + overrideConfigFile: "tests/fixtures/simple-valid-project/eslint.config.js" }); - const results = await eslint.lintFiles(["lib/**/cli*.js"]); + const results = await eslint.lintFiles(["tests/fixtures/simple-valid-project/**/foo*.js"]); assert.strictEqual(results.length, 2); assert.strictEqual(results[0].messages.length, 0); @@ -782,9 +782,13 @@ describe("FlatESLint", () => { it("should handle multiple patterns with overlapping files", async () => { eslint = new FlatESLint({ cwd: originalDir, - overrideConfigFile: "eslint.config.js" + overrideConfigFile: "tests/fixtures/simple-valid-project/eslint.config.js" }); - const results = await eslint.lintFiles(["lib/**/cli*.js", "lib/cli.?s", "lib/{cli,cli-engine/cli-engine}.js"]); + const results = await eslint.lintFiles([ + "tests/fixtures/simple-valid-project/**/foo*.js", + "tests/fixtures/simple-valid-project/foo.?s", + "tests/fixtures/simple-valid-project/{foo,src/foobar}.js" + ]); assert.strictEqual(results.length, 2); assert.strictEqual(results[0].messages.length, 0); @@ -1408,11 +1412,10 @@ describe("FlatESLint", () => { }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); }); - it("should throw an error when all given files are ignored via ignore-pattern", async () => { + it("should throw an error when all given files are ignored via ignorePatterns", async () => { eslint = new FlatESLint({ - overrideConfig: { - ignorePatterns: "tests/fixtures/single-quoted.js" - } + overrideConfigFile: true, + ignorePatterns: ["tests/fixtures/single-quoted.js"] }); await assert.rejects(async () => { @@ -1817,6 +1820,7 @@ describe("FlatESLint", () => { it("should warn when deprecated rules are configured", async () => { eslint = new FlatESLint({ cwd: originalDir, + overrideConfigFile: true, overrideConfig: { rules: { "indent-legacy": 1, @@ -1840,6 +1844,7 @@ describe("FlatESLint", () => { it("should not warn when deprecated rules are not configured", async () => { eslint = new FlatESLint({ cwd: originalDir, + overrideConfigFile: true, overrideConfig: { rules: { indent: 1, "valid-jsdoc": 0, "require-jsdoc": 0 } } From 58aab6b6c09996875418aefeeb0fd76c50caef7a Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Wed, 7 Jun 2023 08:06:24 +0000 Subject: [PATCH 018/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ace716e4924d..6722238a53ea 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

BairesDev King Billy Slots ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

+

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

## Technology Sponsors From 788d83629a3790a7db6f52dcf0b4bddf51c6d063 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 7 Jun 2023 17:19:10 +0200 Subject: [PATCH 019/248] docs: add references to MIT License (#17248) Refs #17225 --- docs/README.md | 4 ++++ docs/src/_data/sites/en.yml | 2 +- docs/src/_data/sites/zh-hans.yml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index 140608aa2d22..33f91bd3f731 100644 --- a/docs/README.md +++ b/docs/README.md @@ -39,3 +39,7 @@ To autofix JS files, run this from the root folder (not the `docs` folder): ```shell npm run fix:docsjs ``` + +## License + +ยฉ OpenJS Foundation and ESLint contributors, [www.openjsf.org](https://www.openjsf.org/). Content licensed under [MIT License](https://github.com/eslint/eslint/blob/main/LICENSE). diff --git a/docs/src/_data/sites/en.yml b/docs/src/_data/sites/en.yml index 1afff54b98b6..532630be810a 100644 --- a/docs/src/_data/sites/en.yml +++ b/docs/src/_data/sites/en.yml @@ -87,7 +87,7 @@ footer: language: Language latest: Latest copyright: > - © OpenJS Foundation and ESLint contributors, www.openjsf.org + © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under MIT License. links: open_jsf: The OpenJS Foundation terms: Terms of Use diff --git a/docs/src/_data/sites/zh-hans.yml b/docs/src/_data/sites/zh-hans.yml index 51c91288767a..421401535d05 100644 --- a/docs/src/_data/sites/zh-hans.yml +++ b/docs/src/_data/sites/zh-hans.yml @@ -47,7 +47,7 @@ shared: #------------------------------------------------------------------------------ # Navigation #------------------------------------------------------------------------------ - + navigation: - text: ๅ›ข้˜Ÿ link: team @@ -85,7 +85,7 @@ footer: language: ่ฏญ่จ€ latest: ๆœ€ๆ–ฐ copyright: > - © OpenJS Foundation and ESLint contributors, www.openjsf.org + © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under MIT License. links: open_jsf: OpenJS ๅŸบ้‡‘ไผš terms: ไฝฟ็”จๆกๆฌพ From 1b7faf0702b1af86b6a0ddafc37cf45d60f5d4d8 Mon Sep 17 00:00:00 2001 From: Azat S Date: Thu, 8 Jun 2023 15:54:14 +0300 Subject: [PATCH 020/248] feat: add `skipJSXText` option to `no-irregular-whitespace` rule (#17182) * feat: add `skipJSXText` option to `no-irregular-whitespace` rule * fix: remove unnecessary jsx text value check * docs: fix no-irregular-whitespace docs * test: add invalid cases for jsx in no-irregular-whitespace rule * docs: fix check jsx node func jsdoc * style: fix trailing commas --- docs/src/rules/no-irregular-whitespace.md | 18 ++ lib/rules/no-irregular-whitespace.js | 18 ++ tests/lib/rules/no-irregular-whitespace.js | 358 +++++++++++++++++++++ 3 files changed, 394 insertions(+) diff --git a/docs/src/rules/no-irregular-whitespace.md b/docs/src/rules/no-irregular-whitespace.md index 258733b0827a..cb2c62bda58c 100644 --- a/docs/src/rules/no-irregular-whitespace.md +++ b/docs/src/rules/no-irregular-whitespace.md @@ -63,6 +63,7 @@ This rule has an object option for exceptions: * `"skipComments": true` allows any whitespace characters in comments * `"skipRegExps": true` allows any whitespace characters in regular expression literals * `"skipTemplates": true` allows any whitespace characters in template literals +* `"skipJSXText": true` allows any whitespace characters in JSX text ### skipStrings @@ -192,6 +193,23 @@ function thing() { ::: +### skipJSXText + +Examples of additional **correct** code for this rule with the `{ "skipJSXText": true }` option: + +::: correct + +```js +/*eslint no-irregular-whitespace: ["error", { "skipJSXText": true }]*/ +/*eslint-env es6*/ + +function Thing() { + return
text inโ€‚JSX
; +} +``` + +::: + ## When Not To Use It If you decide that you wish to use whitespace other than tabs and spaces outside of strings in your application. diff --git a/lib/rules/no-irregular-whitespace.js b/lib/rules/no-irregular-whitespace.js index 0badc58b87a8..ab7ccac54e4c 100644 --- a/lib/rules/no-irregular-whitespace.js +++ b/lib/rules/no-irregular-whitespace.js @@ -55,6 +55,10 @@ module.exports = { skipRegExps: { type: "boolean", default: false + }, + skipJSXText: { + type: "boolean", + default: false } }, additionalProperties: false @@ -77,6 +81,7 @@ module.exports = { const skipStrings = options.skipStrings !== false; const skipRegExps = !!options.skipRegExps; const skipTemplates = !!options.skipTemplates; + const skipJSXText = !!options.skipJSXText; const sourceCode = context.sourceCode; const commentNodes = sourceCode.getAllComments(); @@ -144,6 +149,18 @@ module.exports = { } } + /** + * Checks JSX nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeInvalidNodeErrorsInJSXText(node) { + if (ALL_IRREGULARS.test(node.raw)) { + removeWhitespaceError(node); + } + } + /** * Checks the program source for irregular whitespace * @param {ASTNode} node The program node @@ -239,6 +256,7 @@ module.exports = { nodes.Literal = removeInvalidNodeErrorsInLiteral; nodes.TemplateElement = skipTemplates ? removeInvalidNodeErrorsInTemplateLiteral : noop; + nodes.JSXText = skipJSXText ? removeInvalidNodeErrorsInJSXText : noop; nodes["Program:exit"] = function() { if (skipComments) { diff --git a/tests/lib/rules/no-irregular-whitespace.js b/tests/lib/rules/no-irregular-whitespace.js index 9becb5a9ecf6..e6ca5d111b6c 100644 --- a/tests/lib/rules/no-irregular-whitespace.js +++ b/tests/lib/rules/no-irregular-whitespace.js @@ -171,6 +171,28 @@ ruleTester.run("no-irregular-whitespace", rule, { { code: "const error = `\n\u3000\n`;", options: [{ skipTemplates: true }], parserOptions: { ecmaVersion: 6 } }, { code: "const error = `foo\u3000bar\nfoo\u3000bar`;", options: [{ skipTemplates: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "
\u000B
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u000C
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u0085
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u00A0
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u180E
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\ufeff
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u2000
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u2001
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u2002
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u2003
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u2004
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u2005
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u2006
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u2007
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u2008
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u2009
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u200A
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u200B
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u202F
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u205f
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + { code: "
\u3000
;", options: [{ skipJSXText: true }], parserOptions: { ecmaFeatures: { jsx: true } } }, + // Unicode BOM. "\uFEFFconsole.log('hello BOM');" ], @@ -993,6 +1015,342 @@ ruleTester.run("no-irregular-whitespace", rule, { endColumn: 2 } ] + }, + { + code: "
\u000B
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u000C
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u0085
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u00A0
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u180E
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\ufeff
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u2000
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u2001
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u2002
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u2003
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u2004
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u2005
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u2006
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u2007
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u2008
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u2009
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u200A
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u200B
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u202F
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u205f
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] + }, + { + code: "
\u3000
;", + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + errors: [ + { + messageId: "noIrregularWhitespace", + type: "Program", + line: 1, + column: 6 + } + ] } ] }); From 0bc257c290b12fcda85cb61b40d55fc2be0f938c Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Thu, 8 Jun 2023 17:20:38 +0200 Subject: [PATCH 021/248] docs: Clarify `no-div-regex` rule docs (#17051) (#17255) * fix: Clarify `no-div-regex` rule docs * Update opening section --------- Co-authored-by: Milos Djermanovic --- docs/src/rules/no-div-regex.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/rules/no-div-regex.md b/docs/src/rules/no-div-regex.md index ccc7287dcbe8..cb30420e7000 100644 --- a/docs/src/rules/no-div-regex.md +++ b/docs/src/rules/no-div-regex.md @@ -8,7 +8,7 @@ related_rules: -Require regex literals to escape division operators. +Characters `/=` at the beginning of a regular expression literal can be confused with a division assignment operator. ```js function bar() { return /=foo/; } @@ -16,7 +16,7 @@ function bar() { return /=foo/; } ## Rule Details -This is used to disambiguate the division operator to not confuse users. +This rule forbids equal signs (`=`) after the slash (`/`) at the beginning of a regular expression literal, because the characters `/=` can be confused with a division assignment operator. Examples of **incorrect** code for this rule: From e0a2448e0c0ef354e69998858846630a3fce8ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Fri, 9 Jun 2023 00:25:49 +0800 Subject: [PATCH 022/248] chore: docs package.license ISC => MIT (#17254) --- docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index cefdc39ca13b..c728f96465a4 100644 --- a/docs/package.json +++ b/docs/package.json @@ -6,7 +6,7 @@ "main": "index.js", "keywords": [], "author": "", - "license": "ISC", + "license": "MIT", "files": [], "scripts": { "images": "imagemin '_site/assets/images' --out-dir='_site/assets/images'", From 35e11d3248e00b711fd652836edc900f22af0ebd Mon Sep 17 00:00:00 2001 From: Ed Lucas <1424108+edlucas@users.noreply.github.com> Date: Thu, 8 Jun 2023 16:36:22 +0000 Subject: [PATCH 023/248] docs: fix typos and missing info (#17257) --- docs/src/rules/id-match.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/rules/id-match.md b/docs/src/rules/id-match.md index 468078d2d54f..08d5ff3ba9c4 100644 --- a/docs/src/rules/id-match.md +++ b/docs/src/rules/id-match.md @@ -91,10 +91,10 @@ This rule has an object option: * `"properties": false` (default) does not check object properties * `"properties": true` requires object literal properties and member expression assignment properties to match the specified regular expression -* `"classFields": false` (default) does not class field names +* `"classFields": false` (default) does not check class field names * `"classFields": true` requires class field names to match the specified regular expression * `"onlyDeclarations": false` (default) requires all variable names to match the specified regular expression -* `"onlyDeclarations": true` requires only `var`, `function`, and `class` declarations to match the specified regular expression +* `"onlyDeclarations": true` requires only `var`, `const`, `let`, `function`, and `class` declarations to match the specified regular expression * `"ignoreDestructuring": false` (default) enforces `id-match` for destructured identifiers * `"ignoreDestructuring": true` does not check destructured identifiers From 3912f3a225c12bfb5ce9b7ba26c2b5301e6275bd Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Thu, 8 Jun 2023 19:24:02 +0200 Subject: [PATCH 024/248] docs: Improve `ignores` documentation (#17239) * docs: Improve `ignores` documentation * Another note about `dir/` vs. `dir/**` in non-global patterns * Update docs/src/use/configure/configuration-files-new.md Clarifiy a sentence Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- docs/src/use/configure/configuration-files-new.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/src/use/configure/configuration-files-new.md b/docs/src/use/configure/configuration-files-new.md index a57bbf0f235f..083be3d93c91 100644 --- a/docs/src/use/configure/configuration-files-new.md +++ b/docs/src/use/configure/configuration-files-new.md @@ -116,7 +116,9 @@ export default [ Here, the configuration object excludes files ending with `.config.js` except for `eslint.config.js`. That file still has `semi` applied. -If `ignores` is used without `files` and any other setting, then the configuration object applies to all files except the ones specified in `ignores`, for example: +Non-global `ignores` patterns can only match file names. A pattern like `"dir-to-exclude/"` will not ignore anything. To ignore everything in a particular directory, a pattern like `"dir-to-exclude/**"` should be used instead. + +If `ignores` is used without `files` and there are other keys (such as `rules`), then the configuration object applies to all files except the ones specified in `ignores`, for example: ```js export default [ @@ -145,6 +147,9 @@ export default [ This configuration specifies that all of the files in the `.config` directory should be ignored. This pattern is added after the default patterns, which are `["**/node_modules/**", ".git/**"]`. +Note that only global `ignores` patterns can match directories. +`ignores` patterns that are specific to a configuration will only match file names. + #### 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 de4d3c14c30a88795b9075d59827d3fe63a42c5e Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Thu, 8 Jun 2023 20:40:03 +0200 Subject: [PATCH 025/248] docs: update flat config default ignore patterns (#17258) --- .../src/use/configure/configuration-files-new.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/src/use/configure/configuration-files-new.md b/docs/src/use/configure/configuration-files-new.md index 083be3d93c91..857089775e22 100644 --- a/docs/src/use/configure/configuration-files-new.md +++ b/docs/src/use/configure/configuration-files-new.md @@ -145,7 +145,21 @@ export default [ ]; ``` -This configuration specifies that all of the files in the `.config` directory should be ignored. This pattern is added after the default patterns, which are `["**/node_modules/**", ".git/**"]`. +This configuration specifies that all of the files in the `.config` directory should be ignored. This pattern is added after the default patterns, which are `["**/node_modules/", ".git/"]`. + +You can also unignore files and directories that are ignored by the default patterns. For example, this config unignores `node_modules/mylibrary`: + +```js +export default [ + { + ignores: [ + "!node_modules/", // unignore `node_modules/` directory + "node_modules/*", // ignore its content + "!node_modules/mylibrary/" // unignore `node_modules/mylibrary` directory + ] + } +]; +``` Note that only global `ignores` patterns can match directories. `ignores` patterns that are specific to a configuration will only match file names. From 85d2b30bc318c1355e52ebb21c56cca32f0ab198 Mon Sep 17 00:00:00 2001 From: Marcus Wyatt Date: Fri, 9 Jun 2023 12:46:09 -0400 Subject: [PATCH 026/248] docs: explain how to include predefined globals (#17261) --- .../use/configure/configuration-files-new.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/src/use/configure/configuration-files-new.md b/docs/src/use/configure/configuration-files-new.md index 857089775e22..de69801ff5e4 100644 --- a/docs/src/use/configure/configuration-files-new.md +++ b/docs/src/use/configure/configuration-files-new.md @@ -347,6 +347,23 @@ export default [ For historical reasons, the boolean value `false` and the string value `"readable"` are equivalent to `"readonly"`. Similarly, the boolean value `true` and the string value `"writeable"` are equivalent to `"writable"`. However, the use of older values is deprecated. +##### Predefined global variables + +Apart from the ECMAScript standard built-in globals, which are automatically enabled based on the configured `languageOptions.ecmaVersion`, ESLint doesn't provide predefined sets of global variables. You can use the [`globals`](https://www.npmjs.com/package/globals) package to additionally enable all globals for a specific environment. For example, here is how you can add `console`, amongst other browser globals, into your configuration. + +```js +import globals from "globals"; +export default [ + { + languageOptions: { + globals: { + ...globals.browser + } + } + } +]; +``` + ### Using plugins in your configuration Plugins are used to share rules, processors, configurations, parsers, and more across ESLint projects. From cb2560f7a393e74b761faa9adad938fb1deb947d Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 9 Jun 2023 14:25:29 -0400 Subject: [PATCH 027/248] docs: Resubmit getScope/getDeclaredVariables docs (#17262) * docs: Resubmit getScope/getDeclaredVariables docs Refs #17225 * Update docs/src/extend/custom-rules.md Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- docs/src/extend/code-path-analysis.md | 4 +- docs/src/extend/custom-rules.md | 106 +++++++++++++------------- 2 files changed, 57 insertions(+), 53 deletions(-) diff --git a/docs/src/extend/code-path-analysis.md b/docs/src/extend/code-path-analysis.md index 6cb7d2ac571e..a2caeff8ea92 100644 --- a/docs/src/extend/code-path-analysis.md +++ b/docs/src/extend/code-path-analysis.md @@ -259,7 +259,9 @@ Please use a map of information instead. ```js function hasCb(node, context) { if (node.type.indexOf("Function") !== -1) { - return context.getDeclaredVariables(node).some(function(v) { + const sourceCode = context.getSourceCode(); + + return sourceCode.getDeclaredVariables(node).some(function(v) { return v.type === "Parameter" && v.name === "cb"; }); } diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index 0118757c6a4d..01fed35977a0 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -117,9 +117,9 @@ The `context` object contains additional functionality that is helpful for rules Additionally, the `context` object has the following methods: -* `getAncestors()` - returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. +* `getAncestors()` - (**Deprecated:** Use `SourceCode#getAncestors(node)` instead.) returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. * `getCwd()` - returns the `cwd` passed to [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered as the current working directory. -* `getDeclaredVariables(node)` - returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. +* `getDeclaredVariables(node)` - (**Deprecated:** Use `SourceCode#getDeclaredVariables(node)` instead.) returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. * If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. * If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. * If the node is a `FunctionDeclaration` or `FunctionExpression`, the variable for the function name is returned, in addition to variables for the function parameters. @@ -131,62 +131,13 @@ Additionally, the `context` object has the following methods: * Otherwise, if the node does not declare any variables, an empty array is returned. * `getFilename()` - returns the filename associated with the source. * `getPhysicalFilename()` - when linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `โ€”stdin-filename` or `` if not specified. -* `getScope()` - returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. +* `getScope()` - (**Deprecated:** Use `SourceCode#getScope(node)` instead.) returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. * `getSourceCode()` - returns a [`SourceCode`](#contextgetsourcecode) object that you can use to work with the source that was passed to ESLint. * `markVariableAsUsed(name)` - marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. * `report(descriptor)` - reports a problem in the code (see the [dedicated section](#contextreport)). **Note:** Earlier versions of ESLint supported additional methods on the `context` object. Those methods were removed in the new format and should not be relied upon. -### context.getScope() - -This method returns the scope of the current node. It is a useful method for finding information about the variables in a given scope, and how they are used in other scopes. - -#### Scope types - -The following table contains a list of AST node types and the scope type that they correspond to. For more information about the scope types, refer to the [`Scope` object documentation](./scope-manager-interface#scope-interface). - -| AST Node Type | Scope Type | -|:--------------------------|:-----------| -| `Program` | `global` | -| `FunctionDeclaration` | `function` | -| `FunctionExpression` | `function` | -| `ArrowFunctionExpression` | `function` | -| `ClassDeclaration` | `class` | -| `ClassExpression` | `class` | -| `BlockStatement` โ€ป1 | `block` | -| `SwitchStatement` โ€ป1 | `switch` | -| `ForStatement` โ€ป2 | `for` | -| `ForInStatement` โ€ป2 | `for` | -| `ForOfStatement` โ€ป2 | `for` | -| `WithStatement` | `with` | -| `CatchClause` | `catch` | -| others | โ€ป3 | - -**โ€ป1** Only if the configured parser provided the block-scope feature. The default parser provides the block-scope feature if `parserOptions.ecmaVersion` is not less than `6`.
-**โ€ป2** Only if the `for` statement defines the iteration variable as a block-scoped variable (E.g., `for (let i = 0;;) {}`).
-**โ€ป3** The scope of the closest ancestor node which has own scope. If the closest ancestor node has multiple scopes then it chooses the innermost scope (E.g., the `Program` node has a `global` scope and a `module` scope if `Program#sourceType` is `"module"`. The innermost scope is the `module` scope.). - -#### Scope Variables - -The `Scope#variables` property contains an array of [`Variable` objects](./scope-manager-interface#variable-interface). These are the variables declared in current scope. You can use these `Variable` objects to track references to a variable throughout the entire module. - -Inside of each `Variable`, the `Variable#references` property contains an array of [`Reference` objects](./scope-manager-interface#reference-interface). The `Reference` array contains all the locations where the variable is referenced in the module's source code. - -Also inside of each `Variable`, the `Variable#defs` property contains an array of [`Definition` objects](./scope-manager-interface#definition-interface). You can use the `Definitions` to find where the variable was defined. - -Global variables have the following additional properties: - -* `Variable#writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only. -* `Variable#eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file. -* `Variable#eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments. -* `Variable#eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments. - -For examples of using `context.getScope()` to track variables, refer to the source code for the following built-in rules: - -* [no-shadow](https://github.com/eslint/eslint/blob/main/lib/rules/no-shadow.js): Calls `context.getScopes()` at the global scope and parses all child scopes to make sure a variable name is not reused at a lower scope. ([no-shadow](../rules/no-shadow) documentation) -* [no-redeclare](https://github.com/eslint/eslint/blob/main/lib/rules/no-redeclare.js): Calls `context.getScope()` at each scope to make sure that a variable is not declared twice at that scope. ([no-redeclare](../rules/no-redeclare) documentation) - ### context.report() The main method you'll use is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties: @@ -684,6 +635,57 @@ Finally, comments can be accessed through many of `sourceCode`'s methods using t Shebangs are represented by tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined above. +### Accessing Variable Scopes + +The `SourceCode#getScope(node)` method returns the scope of the given node. It is a useful method for finding information about the variables in a given scope and how they are used in other scopes. + +**Deprecated:** The `context.getScope()` is deprecated; make sure to use `SourceCode#getScope(node)` instead. + +#### Scope types + +The following table contains a list of AST node types and the scope type that they correspond to. For more information about the scope types, refer to the [`Scope` object documentation](./scope-manager-interface#scope-interface). + +| AST Node Type | Scope Type | +|:--------------------------|:-----------| +| `Program` | `global` | +| `FunctionDeclaration` | `function` | +| `FunctionExpression` | `function` | +| `ArrowFunctionExpression` | `function` | +| `ClassDeclaration` | `class` | +| `ClassExpression` | `class` | +| `BlockStatement` โ€ป1 | `block` | +| `SwitchStatement` โ€ป1 | `switch` | +| `ForStatement` โ€ป2 | `for` | +| `ForInStatement` โ€ป2 | `for` | +| `ForOfStatement` โ€ป2 | `for` | +| `WithStatement` | `with` | +| `CatchClause` | `catch` | +| others | โ€ป3 | + +**โ€ป1** Only if the configured parser provided the block-scope feature. The default parser provides the block-scope feature if `parserOptions.ecmaVersion` is not less than `6`.
+**โ€ป2** Only if the `for` statement defines the iteration variable as a block-scoped variable (E.g., `for (let i = 0;;) {}`).
+**โ€ป3** The scope of the closest ancestor node which has own scope. If the closest ancestor node has multiple scopes then it chooses the innermost scope (E.g., the `Program` node has a `global` scope and a `module` scope if `Program#sourceType` is `"module"`. The innermost scope is the `module` scope.). + +#### Scope Variables + +The `Scope#variables` property contains an array of [`Variable` objects](./scope-manager-interface#variable-interface). These are the variables declared in current scope. You can use these `Variable` objects to track references to a variable throughout the entire module. + +Inside of each `Variable`, the `Variable#references` property contains an array of [`Reference` objects](./scope-manager-interface#reference-interface). The `Reference` array contains all the locations where the variable is referenced in the module's source code. + +Also inside of each `Variable`, the `Variable#defs` property contains an array of [`Definition` objects](./scope-manager-interface#definition-interface). You can use the `Definitions` to find where the variable was defined. + +Global variables have the following additional properties: + +* `Variable#writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only. +* `Variable#eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file. +* `Variable#eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments. +* `Variable#eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments. + +For examples of using `SourceCode#getScope()` to track variables, refer to the source code for the following built-in rules: + +* [no-shadow](https://github.com/eslint/eslint/blob/main/lib/rules/no-shadow.js): Calls `sourceCode.getScope()` at the `Program` node and inspects all child scopes to make sure a variable name is not reused at a lower scope. ([no-shadow](../rules/no-shadow) documentation) +* [no-redeclare](https://github.com/eslint/eslint/blob/main/lib/rules/no-redeclare.js): Calls `sourceCode.getScope()` at each scope to make sure that a variable is not declared twice in the same scope. ([no-redeclare](../rules/no-redeclare) documentation) + ### Accessing Code Paths ESLint analyzes code paths while traversing AST. From 0e9980c3a8a1e554fdb377305c0ebe9e94a354c9 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sun, 11 Jun 2023 20:40:24 +0530 Subject: [PATCH 028/248] docs: add new `omitLastInOneLineClassBody` option to the `semi` rule (#17263) --- docs/src/rules/semi.md | 73 +++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/docs/src/rules/semi.md b/docs/src/rules/semi.md index 86c4e5d84fe1..eca93a747002 100644 --- a/docs/src/rules/semi.md +++ b/docs/src/rules/semi.md @@ -80,7 +80,8 @@ String option: Object option (when `"always"`): -* `"omitLastInOneLineBlock": true` ignores the last semicolon in a block in which its braces (and therefore the content of the block) are in the same line +* `"omitLastInOneLineBlock": true` disallows the last semicolon in a block in which its braces (and therefore the content of the block) are in the same line +* `"omitLastInOneLineClassBody": true` disallows the last semicolon in a class body in which its braces (and therefore the content of the class body) are in the same line Object option (when `"never"`): @@ -132,6 +133,52 @@ class Foo { ::: +#### omitLastInOneLineBlock + +Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineBlock": true }` options: + +::: correct + +```js +/*eslint semi: ["error", "always", { "omitLastInOneLineBlock": true}] */ + +if (foo) { bar() } + +if (foo) { bar(); baz() } + +function f() { bar(); baz() } + +class C { + foo() { bar(); baz() } + + static { bar(); baz() } +} +``` + +::: + +#### omitLastInOneLineClassBody + +Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineClassBody": true }` options: + +::: correct + +```js +/*eslint semi: ["error", "always", { "omitLastInOneLineClassBody": true}] */ + +export class SomeClass{ + logType(){ + console.log(this.type); + console.log(this.anotherType); + } +} + +export class Variant1 extends SomeClass{type=1} +export class Variant2 extends SomeClass{type=2; anotherType=3} +``` + +::: + ### never Examples of **incorrect** code for this rule with the `"never"` option: @@ -190,30 +237,6 @@ class Foo { ::: -#### omitLastInOneLineBlock - -Examples of additional **correct** code for this rule with the `"always", { "omitLastInOneLineBlock": true }` options: - -::: correct - -```js -/*eslint semi: ["error", "always", { "omitLastInOneLineBlock": true}] */ - -if (foo) { bar() } - -if (foo) { bar(); baz() } - -function f() { bar(); baz() } - -class C { - foo() { bar(); baz() } - - static { bar(); baz() } -} -``` - -::: - #### beforeStatementContinuationChars Examples of additional **incorrect** code for this rule with the `"never", { "beforeStatementContinuationChars": "always" }` options: From e50fac3f8f998f729e3080e256066db3a7827c67 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sun, 11 Jun 2023 17:28:43 +0200 Subject: [PATCH 029/248] feat: add declaration loc to message in block-scoped-var (#17252) * feat: add declaration loc to message in block-scoped-var Fixes #17244 * add test with multiple declarations --- lib/rules/block-scoped-var.js | 20 +- tests/lib/rules/block-scoped-var.js | 316 ++++++++++++++++++++++++++-- 2 files changed, 314 insertions(+), 22 deletions(-) diff --git a/lib/rules/block-scoped-var.js b/lib/rules/block-scoped-var.js index 5a951276c593..ec597d5661e6 100644 --- a/lib/rules/block-scoped-var.js +++ b/lib/rules/block-scoped-var.js @@ -22,7 +22,7 @@ module.exports = { schema: [], messages: { - outOfScope: "'{{name}}' used outside of binding context." + outOfScope: "'{{name}}' declared on line {{definitionLine}} column {{definitionColumn}} is used outside of binding context." } }, @@ -50,12 +50,22 @@ module.exports = { /** * Reports a given reference. * @param {eslint-scope.Reference} reference A reference to report. + * @param {eslint-scope.Definition} definition A definition for which to report reference. * @returns {void} */ - function report(reference) { + function report(reference, definition) { const identifier = reference.identifier; - - context.report({ node: identifier, messageId: "outOfScope", data: { name: identifier.name } }); + const definitionPosition = definition.name.loc.start; + + context.report({ + node: identifier, + messageId: "outOfScope", + data: { + name: identifier.name, + definitionLine: definitionPosition.line, + definitionColumn: definitionPosition.column + 1 + } + }); } /** @@ -92,7 +102,7 @@ module.exports = { variables[i] .references .filter(isOutsideOfScope) - .forEach(report); + .forEach(ref => report(ref, variables[i].defs.find(def => def.parent === node))); } } diff --git a/tests/lib/rules/block-scoped-var.js b/tests/lib/rules/block-scoped-var.js index f401332b116d..175676b3a584 100644 --- a/tests/lib/rules/block-scoped-var.js +++ b/tests/lib/rules/block-scoped-var.js @@ -121,63 +121,345 @@ ruleTester.run("block-scoped-var", rule, { { code: "foo; class C { static {} } var foo; ", parserOptions: { ecmaVersion: 2022 } } ], invalid: [ - { code: "function f(){ x; { var x; } }", errors: [{ messageId: "outOfScope", data: { name: "x" }, type: "Identifier" }] }, - { code: "function f(){ { var x; } x; }", errors: [{ messageId: "outOfScope", data: { name: "x" }, type: "Identifier" }] }, - { code: "function f() { var a; { var b = 0; } a = b; }", errors: [{ messageId: "outOfScope", data: { name: "b" }, type: "Identifier" }] }, - { code: "function f() { try { var a = 0; } catch (e) { var b = a; } }", errors: [{ messageId: "outOfScope", data: { name: "a" }, type: "Identifier" }] }, + { + code: "function f(){ x; { var x; } }", + errors: [{ + messageId: "outOfScope", + data: { + name: "x", + definitionLine: 1, + definitionColumn: 24 + }, + line: 1, + column: 15, + type: "Identifier" + }] + }, + { + code: "function f(){ { var x; } x; }", + errors: [{ + messageId: "outOfScope", + data: { + name: "x", + definitionLine: 1, + definitionColumn: 21 + }, + line: 1, + column: 26, + type: "Identifier" + }] + }, + { + code: "function f() { var a; { var b = 0; } a = b; }", + errors: [{ + messageId: "outOfScope", + data: { + name: "b", + definitionLine: 1, + definitionColumn: 29 + }, + line: 1, + column: 42, + type: "Identifier" + }] + }, + { + code: "function f() { try { var a = 0; } catch (e) { var b = a; } }", + errors: [{ + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 26 + }, + line: 1, + column: 55, + type: "Identifier" + }] + }, { code: "function a() { for(var b in {}) { var c = b; } c; }", - errors: [{ messageId: "outOfScope", data: { name: "c" }, type: "Identifier" }] + errors: [{ + messageId: "outOfScope", + data: { + name: "c", + definitionLine: 1, + definitionColumn: 39 + }, + line: 1, + column: 48, + type: "Identifier" + }] }, { code: "function a() { for(var b of {}) { var c = b; } c; }", parserOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "outOfScope", data: { name: "c" }, type: "Identifier" }] + errors: [{ + messageId: "outOfScope", + data: { + name: "c", + definitionLine: 1, + definitionColumn: 39 + }, + line: 1, + column: 48, + type: "Identifier" + }] }, { code: "function f(){ switch(2) { case 1: var b = 2; b; break; default: b; break;} b; }", - errors: [{ messageId: "outOfScope", data: { name: "b" }, type: "Identifier" }] + errors: [{ + messageId: "outOfScope", + data: { + name: "b", + definitionLine: 1, + definitionColumn: 39 + }, + line: 1, + column: 76, + type: "Identifier" + }] }, { code: "for (var a = 0;;) {} a;", - errors: [{ messageId: "outOfScope", data: { name: "a" }, type: "Identifier" }] + errors: [{ + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 10 + }, + line: 1, + column: 22, + type: "Identifier" + }] }, { code: "for (var a in []) {} a;", - errors: [{ messageId: "outOfScope", data: { name: "a" }, type: "Identifier" }] + errors: [{ + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 10 + }, + line: 1, + column: 22, + type: "Identifier" + }] }, { code: "for (var a of []) {} a;", parserOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "outOfScope", data: { name: "a" }, type: "Identifier" }] + errors: [{ + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 10 + }, + line: 1, + column: 22, + type: "Identifier" + }] }, { code: "{ var a = 0; } a;", parserOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ messageId: "outOfScope", data: { name: "a" }, type: "Identifier" }] + errors: [{ + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 7 + }, + line: 1, + column: 16, + type: "Identifier" + }] }, { code: "if (true) { var a; } a;", - errors: [{ messageId: "outOfScope", data: { name: "a" }, type: "Identifier" }] + errors: [{ + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 17 + }, + line: 1, + column: 22, + type: "Identifier" + }] }, { code: "if (true) { var a = 1; } else { var a = 2; }", errors: [ - { messageId: "outOfScope", data: { name: "a" }, type: "Identifier" }, - { messageId: "outOfScope", data: { name: "a" }, type: "Identifier" } + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 37 + }, + line: 1, + column: 17, + type: "Identifier" + }, + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 17 + }, + line: 1, + column: 37, + type: "Identifier" + } ] }, { code: "for (var i = 0;;) {} for(var i = 0;;) {}", errors: [ - { messageId: "outOfScope", data: { name: "i" }, type: "Identifier" }, - { messageId: "outOfScope", data: { name: "i" }, type: "Identifier" } + { + messageId: "outOfScope", + data: { + name: "i", + definitionLine: 1, + definitionColumn: 30 + }, + line: 1, + column: 10, + type: "Identifier" + }, + { + messageId: "outOfScope", + data: { + name: "i", + definitionLine: 1, + definitionColumn: 10 + }, + line: 1, + column: 30, + type: "Identifier" + } ] }, { code: "class C { static { if (bar) { var foo; } foo; } }", parserOptions: { ecmaVersion: 2022 }, - errors: [{ messageId: "outOfScope", data: { name: "foo" }, type: "Identifier" }] + errors: [{ + messageId: "outOfScope", + data: { + name: "foo", + definitionLine: 1, + definitionColumn: 35 + }, + line: 1, + column: 42, + type: "Identifier" + }] + }, + { + code: "{ var foo,\n bar; } bar;", + errors: [{ + messageId: "outOfScope", + data: { + name: "bar", + definitionLine: 2, + definitionColumn: 3 + }, + line: 2, + column: 10, + type: "Identifier" + }] + }, + { + code: "{ var { foo,\n bar } = baz; } bar;", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "outOfScope", + data: { + name: "bar", + definitionLine: 2, + definitionColumn: 3 + }, + line: 2, + column: 18, + type: "Identifier" + }] + }, + { + code: "if (foo) { var a = 1; } else if (bar) { var a = 2; } else { var a = 3; }", + errors: [ + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 45 + }, + line: 1, + column: 16, + type: "Identifier" + }, + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 65 + }, + line: 1, + column: 16, + type: "Identifier" + }, + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 16 + }, + line: 1, + column: 45, + type: "Identifier" + }, + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 65 + }, + line: 1, + column: 45, + type: "Identifier" + }, + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 16 + }, + line: 1, + column: 65, + type: "Identifier" + }, + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 45 + }, + line: 1, + column: 65, + type: "Identifier" + } + ] } ] }); From b1992954591a3f4d8417013f52739b5fef4e0cd7 Mon Sep 17 00:00:00 2001 From: Ben Perlmutter <57849986+bpmutter@users.noreply.github.com> Date: Sun, 11 Jun 2023 13:08:31 -0400 Subject: [PATCH 030/248] docs: Resubmit custom rules update docs (#17273) * docs: Resubmit custom rules update docs Done b/c need to have OSS license apply to them. See #17225 * revert other changes to migrate to 8 pagE --- docs/src/extend/custom-rules.md | 397 +++++++++++++++-------------- docs/src/use/migrate-to-8.0.0.md | 2 +- docs/src/use/migrating-to-5.0.0.md | 2 +- 3 files changed, 209 insertions(+), 192 deletions(-) diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index 01fed35977a0..a20d65d3a653 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -34,44 +34,43 @@ module.exports = { }; ``` -## Rule Basics +## Rule Structure -The source file for a rule exports an object with the following properties. +The source file for a rule exports an object with the following properties. Both custom rules and core rules follow this format. -`meta` (object) contains metadata for the rule: +`meta`: (`object`) Contains metadata for the rule: -* `type` (string) indicates the type of rule, which is one of `"problem"`, `"suggestion"`, or `"layout"`: - * `"problem"` means the rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. - * `"suggestion"` means the rule is identifying something that could be done in a better way but no errors will occur if the code isn't changed. - * `"layout"` means the rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren't specified in the AST. +* `type`: (`string`) Indicates the type of rule, which is one of `"problem"`, `"suggestion"`, or `"layout"`: -* `docs` (object) is required for core rules of ESLint: + * `"problem"`: The rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. + * `"suggestion"`: The rule is identifying something that could be done in a better way but no errors will occur if the code isn't changed. + * `"layout"`: The rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren't specified in the AST. - * `description` (string) provides the short description of the rule in the [rules index](../rules/) - * `recommended` (boolean) is whether the `"extends": "eslint:recommended"` property in a [configuration file](../use/configure/configuration-files#extending-configuration-files) enables the rule - * `url` (string) specifies the URL at which the full documentation can be accessed (enabling code editors to provide a helpful link on highlighted rule violations) +* `docs`: (`object`) Required for core rules and optional for custom rules. Core rules have specific entries inside of `docs` while custom rules can include any properties that you need. The following properties are only relevant when working on core rules. - In a custom rule or plugin, you can omit `docs` or include any properties that you need in it. + * `description`: (`string`) Provides the short description of the rule in the [rules index](../rules/). + * `recommended`: (`boolean`) Specifies whether the `"extends": "eslint:recommended"` property in a [configuration file](../use/configure/configuration-files#extending-configuration-files) enables the rule. + * `url`: (`string`) Specifies the URL at which the full documentation can be accessed (enabling code editors to provide a helpful link on highlighted rule violations). -* `fixable` (string) is either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../use/command-line-interface#--fix) automatically fixes problems reported by the rule +* `fixable`: (`string`) Either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../use/command-line-interface#--fix) automatically fixes problems reported by the rule. - **Important:** the `fixable` property is mandatory for fixable rules. If this property isn't specified, ESLint will throw an error whenever the rule attempts to produce a fix. Omit the `fixable` property if the rule is not fixable. + **Important:** the `fixable` property is mandatory for fixable rules. If this property isn't specified, ESLint will throw an error whenever the rule attempts to produce a fix. Omit the `fixable` property if the rule is not fixable. -* `hasSuggestions` (boolean) specifies whether rules can return suggestions (defaults to `false` if omitted) +* `hasSuggestions`: (`boolean`) Specifies whether rules can return suggestions (defaults to `false` if omitted). - **Important:** the `hasSuggestions` property is mandatory for rules that provide suggestions. If this property isn't set to `true`, ESLint will throw an error whenever the rule attempts to produce a suggestion. Omit the `hasSuggestions` property if the rule does not provide suggestions. + **Important:** the `hasSuggestions` property is mandatory for rules that provide suggestions. If this property isn't set to `true`, ESLint will throw an error whenever the rule attempts to produce a suggestion. Omit the `hasSuggestions` property if the rule does not provide suggestions. -* `schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../use/configure/rules) +* `schema`: (`object | array`) Specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../use/configure/rules). -* `deprecated` (boolean) indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated. +* `deprecated`: (`boolean`) Indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated. -* `replacedBy` (array) in the case of a deprecated rule, specifies replacement rule(s) +* `replacedBy`: (`array`) In the case of a deprecated rule, specify replacement rule(s). -`create` (function) returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code: +`create()`: Returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code: -* if a key is a node type or a [selector](./selectors), ESLint calls that **visitor** function while going **down** the tree -* if a key is a node type or a [selector](./selectors) plus `:exit`, ESLint calls that **visitor** function while going **up** the tree -* if a key is an event name, ESLint calls that **handler** function for [code path analysis](code-path-analysis) +* If a key is a node type or a [selector](./selectors), ESLint calls that **visitor** function while going **down** the tree. +* If a key is a node type or a [selector](./selectors) plus `:exit`, ESLint calls that **visitor** function while going **up** the tree. +* If a key is an event name, ESLint calls that **handler** function for [code path analysis](code-path-analysis). A rule can use the current node and its surrounding tree to report or fix problems. @@ -106,20 +105,36 @@ module.exports = { ## The Context Object -The `context` object contains additional functionality that is helpful for rules to do their jobs. As the name implies, the `context` object contains information that is relevant to the context of the rule. The `context` object has the following properties: +The `context` object is the only argument of the `create` method in a rule. For example: -* `parserOptions` - the parser options configured for this run (more details [here](../use/configure/language-options#specifying-parser-options)). -* `id` - the rule ID. -* `options` - an array of the [configured options](../use/configure/rules) for this rule. This array does not include the rule severity. For more information, see [here](#contextoptions). -* `settings` - the [shared settings](../use/configure/configuration-files#adding-shared-settings) from configuration. -* `parserPath` - the name of the `parser` from configuration. -* `parserServices` - an object containing parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) +```js +// customRule.js + +module.exports = { + meta: { ... }, + // `context` object is the argument + create(context) { + // ... + } +}; +``` + +As the name implies, the `context` object contains information that is relevant to the context of the rule. + +The `context` object has the following properties: + +* `id`: (`string`) The rule ID. +* `options`: (`array`) An array of the [configured options](../use/configure/rules) for this rule. This array does not include the rule severity (see the [dedicated section](#accessing-options-passed-to-a-rule)). +* `settings`: (`object`) The [shared settings](../use/configure/configuration-files#adding-shared-settings) from the configuration. +* `parserPath`: (`string`) The name of the `parser` from the configuration. +* `parserServices`: (`object`) Contains parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) +* `parserOptions`: The parser options configured for this run (more details [here](../use/configure/language-options#specifying-parser-options)). Additionally, the `context` object has the following methods: -* `getAncestors()` - (**Deprecated:** Use `SourceCode#getAncestors(node)` instead.) returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. -* `getCwd()` - returns the `cwd` passed to [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered as the current working directory. -* `getDeclaredVariables(node)` - (**Deprecated:** Use `SourceCode#getDeclaredVariables(node)` instead.) returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. +* `getAncestors()` - (**Deprecated:** Use `SourceCode#getAncestors(node)` instead.) Returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. +* `getCwd()` - Returns the `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. +* `getDeclaredVariables(node)` - (**Deprecated:** Use `SourceCode#getDeclaredVariables(node)` instead.) Returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. * If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. * If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. * If the node is a `FunctionDeclaration` or `FunctionExpression`, the variable for the function name is returned, in addition to variables for the function parameters. @@ -129,30 +144,30 @@ Additionally, the `context` object has the following methods: * If the node is an `ImportDeclaration`, variables for all of its specifiers are returned. * If the node is an `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`, the declared variable is returned. * Otherwise, if the node does not declare any variables, an empty array is returned. -* `getFilename()` - returns the filename associated with the source. -* `getPhysicalFilename()` - when linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `โ€”stdin-filename` or `` if not specified. -* `getScope()` - (**Deprecated:** Use `SourceCode#getScope(node)` instead.) returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. -* `getSourceCode()` - returns a [`SourceCode`](#contextgetsourcecode) object that you can use to work with the source that was passed to ESLint. -* `markVariableAsUsed(name)` - marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. -* `report(descriptor)` - reports a problem in the code (see the [dedicated section](#contextreport)). +* `getFilename()`: Returns the filename associated with the source. +* `getPhysicalFilename()`: When linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `โ€”stdin-filename` or `` if not specified. +* `getScope()`: (**Deprecated:** Use `SourceCode#getScope(node)` instead.) Returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. +* `getSourceCode()`: Returns a `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). +* `markVariableAsUsed(name)`: Marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. +* `report(descriptor)`. Reports a problem in the code (see the [dedicated section](#reporting-problems)). **Note:** Earlier versions of ESLint supported additional methods on the `context` object. Those methods were removed in the new format and should not be relied upon. -### context.report() +### Reporting Problems -The main method you'll use is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties: +The main method you'll use when writing custom rules is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties: -* `message` - the problem message. -* `node` - (optional) the AST node related to the problem. If present and `loc` is not specified, then the starting location of the node is used as the location of the problem. -* `loc` - (optional) an object specifying the location of the problem. If both `loc` and `node` are specified, then the location is used from `loc` instead of `node`. - * `start` - An object of the start location. - * `line` - the 1-based line number at which the problem occurred. - * `column` - the 0-based column number at which the problem occurred. - * `end` - An object of the end location. - * `line` - the 1-based line number at which the problem occurred. - * `column` - the 0-based column number at which the problem occurred. -* `data` - (optional) [placeholder](#using-message-placeholders) data for `message`. -* `fix` - (optional) a function that applies a [fix](#applying-fixes) to resolve the problem. +* `message`: (`string`) The problem message. +* `node`: (optional `object`) The AST node related to the problem. If present and `loc` is not specified, then the starting location of the node is used as the location of the problem. +* `loc`: (optional `object`) Specifies the location of the problem. If both `loc` and `node` are specified, then the location is used from `loc` instead of `node`. + * `start`: An object of the start location. + * `line`: (`number`) The 1-based line number at which the problem occurred. + * `column`: (`number`) The 0-based column number at which the problem occurred. + * `end`: An object of the end location. + * `line`: (`number`) The 1-based line number at which the problem occurred. + * `column`: (`number`) The 0-based column number at which the problem occurred. +* `data`: (optional `object`) [Placeholder](#using-message-placeholders) data for `message`. +* `fix(fixer)`: (optional `function`) Applies a [fix](#applying-fixes) to resolve the problem. Note that at least one of `node` or `loc` is required. @@ -165,9 +180,9 @@ context.report({ }); ``` -The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node. +The node contains all the information necessary to figure out the line and column number of the offending text as well as the source text representing the node. -### Using message placeholders +#### Using Message Placeholders You can also use placeholders in the message and provide `data`: @@ -185,17 +200,20 @@ context.report({ Note that leading and trailing whitespace is optional in message parameters. -The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node. +The node contains all the information necessary to figure out the line and column number of the offending text as well as the source text representing the node. -### `messageId`s +#### `messageId`s Instead of typing out messages in both the `context.report()` call and your tests, you can use `messageId`s instead. This allows you to avoid retyping error messages. It also prevents errors reported in different sections of your rule from having out-of-date messages. +Rule file: + ```js {% raw %} -// in your rule +// avoid-name.js + module.exports = { meta: { messages: { @@ -218,14 +236,24 @@ module.exports = { }; } }; +{% endraw %} +``` + +In the file to lint: -// in the file to lint: +```javascript +// someFile.js var foo = 2; // ^ error: Avoid using variables named 'foo' +``` + +In your tests: -// In your tests: -var rule = require("../../../lib/rules/my-rule"); +```javascript +// avoid-name.test.js + +var rule = require("../../../lib/rules/avoid-name"); var RuleTester = require("eslint").RuleTester; var ruleTester = new RuleTester(); @@ -242,10 +270,9 @@ ruleTester.run("my-rule", rule, { } ] }); -{% endraw %} ``` -### Applying Fixes +#### Applying Fixes If you'd like ESLint to attempt to fix the problem you're reporting, you can do so by specifying the `fix` function when using `context.report()`. The `fix` function receives a single argument, a `fixer` object, that you can use to apply a fix. For example: @@ -253,28 +280,28 @@ If you'd like ESLint to attempt to fix the problem you're reporting, you can do context.report({ node: node, message: "Missing semicolon", - fix: function(fixer) { + fix(fixer) { return fixer.insertTextAfter(node, ";"); } }); ``` -Here, the `fix()` function is used to insert a semicolon after the node. Note that a fix is not immediately applied, and may not be applied at all if there are conflicts with other fixes. After applying fixes, ESLint will run all of the enabled rules again on the fixed code, potentially applying more fixes. This process will repeat up to 10 times, or until no more fixable problems are found. Afterwards, any remaining problems will be reported as usual. +Here, the `fix()` function is used to insert a semicolon after the node. Note that a fix is not immediately applied, and may not be applied at all if there are conflicts with other fixes. After applying fixes, ESLint will run all the enabled rules again on the fixed code, potentially applying more fixes. This process will repeat up to 10 times, or until no more fixable problems are found. Afterward, any remaining problems will be reported as usual. -**Important:** The `meta.fixable` property is mandatory for fixable rules. ESLint will throw an error if a rule that implements `fix` functions does not [export](#rule-basics) the `meta.fixable` property. +**Important:** The `meta.fixable` property is mandatory for fixable rules. ESLint will throw an error if a rule that implements `fix` functions does not [export](#rule-structure) the `meta.fixable` property. The `fixer` object has the following methods: -* `insertTextAfter(nodeOrToken, text)` - inserts text after the given node or token -* `insertTextAfterRange(range, text)` - inserts text after the given range -* `insertTextBefore(nodeOrToken, text)` - inserts text before the given node or token -* `insertTextBeforeRange(range, text)` - inserts text before the given range -* `remove(nodeOrToken)` - removes the given node or token -* `removeRange(range)` - removes text in the given range -* `replaceText(nodeOrToken, text)` - replaces the text in the given node or token -* `replaceTextRange(range, text)` - replaces the text in the given range +* `insertTextAfter(nodeOrToken, text)`: Insert text after the given node or token. +* `insertTextAfterRange(range, text)`: Insert text after the given range. +* `insertTextBefore(nodeOrToken, text)`: Insert text before the given node or token. +* `insertTextBeforeRange(range, text)`: Insert text before the given range. +* `remove(nodeOrToken)`: Remove the given node or token. +* `removeRange(range)`: Remove text in the given range. +* `replaceText(nodeOrToken, text)`: Replace the text in the given node or token. +* `replaceTextRange(range, text)`: Replace the text in the given range. -A range is a two-item array containing character indices inside of the source code. The first item is the start of the range (inclusive) and the second item is the end of the range (exclusive). Every node and token has a `range` property to identify the source code range they represent. +A `range` is a two-item array containing character indices inside the source code. The first item is the start of the range (inclusive) and the second item is the end of the range (exclusive). Every node and token has a `range` property to identify the source code range they represent. The above methods return a `fixing` object. The `fix()` function can return the following values: @@ -291,21 +318,22 @@ Best practices for fixes: 1. Make fixes as small as possible. Fixes that are unnecessarily large could conflict with other fixes, and prevent them from being applied. 1. Only make one fix per message. This is enforced because you must return the result of the fixer operation from `fix()`. 1. Since all rules are run again after the initial round of fixes is applied, it's not necessary for a rule to check whether the code style of a fix will cause errors to be reported by another rule. - * For example, suppose a fixer would like to surround an object key with quotes, but it's not sure whether the user would prefer single or double quotes. - ```js - ({ foo : 1 }) +* For example, suppose a fixer would like to surround an object key with quotes, but it's not sure whether the user would prefer single or double quotes. + + ```js + ({ foo : 1 }) - // should get fixed to either + // should get fixed to either - ({ 'foo': 1 }) + ({ 'foo': 1 }) - // or + // or - ({ "foo": 1 }) - ``` + ({ "foo": 1 }) + ``` - * This fixer can just select a quote type arbitrarily. If it guesses wrong, the resulting code will be automatically reported and fixed by the [`quotes`](../rules/quotes) rule. +* This fixer can just select a quote type arbitrarily. If it guesses wrong, the resulting code will be automatically reported and fixed by the [`quotes`](../rules/quotes) rule. Note: Making fixes as small as possible is a best practice, but in some cases it may be correct to extend the range of the fix in order to intentionally prevent other rules from making fixes in a surrounding range in the same pass. For instance, if replacement text declares a new variable, it can be useful to prevent other changes in the scope of the variable as they might cause name collisions. @@ -332,11 +360,11 @@ There is no way to specify which of the conflicting fixes is applied. For example, if two fixes want to modify characters 0 through 5, only one is applied. -### Providing Suggestions +#### Providing Suggestions In some cases fixes aren't appropriate to be automatically applied, for example, if a fix potentially changes functionality or if there are multiple valid ways to fix a rule depending on the implementation intent (see the best practices for [applying fixes](#applying-fixes) listed above). In these cases, there is an alternative `suggest` option on `context.report()` that allows other tools, such as editors, to expose helpers for users to manually apply a suggestion. -In order to provide suggestions, use the `suggest` key in the report argument with an array of suggestion objects. The suggestion objects represent individual suggestions that could be applied and require either a `desc` key string that describes what applying the suggestion would do or a `messageId` key (see [below](#suggestion-messageids)), and a `fix` key that is a function defining the suggestion result. This `fix` function follows the same API as regular fixes (described above in [applying fixes](#applying-fixes)). +To provide suggestions, use the `suggest` key in the report argument with an array of suggestion objects. The suggestion objects represent individual suggestions that could be applied and require either a `desc` key string that describes what applying the suggestion would do or a `messageId` key (see [below](#suggestion-messageids)), and a `fix` key that is a function defining the suggestion result. This `fix` function follows the same API as regular fixes (described above in [applying fixes](#applying-fixes)). ```js {% raw %} @@ -362,9 +390,9 @@ context.report({ {% endraw %} ``` -**Important:** The `meta.hasSuggestions` property is mandatory for rules that provide suggestions. ESLint will throw an error if a rule attempts to produce a suggestion but does not [export](#rule-basics) this property. +**Important:** The `meta.hasSuggestions` property is mandatory for rules that provide suggestions. ESLint will throw an error if a rule attempts to produce a suggestion but does not [export](#rule-structure) this property. -Note: Suggestions will be applied as a stand-alone change, without triggering multipass fixes. Each suggestion should focus on a singular change in the code and should not try to conform to user defined styles. For example, if a suggestion is adding a new statement into the codebase, it should not try to match correct indentation, or conform to user preferences on presence/absence of semicolons. All of those things can be corrected by multipass autofix when the user triggers it. +**Note:** Suggestions are applied as stand-alone changes, without triggering multipass fixes. Each suggestion should focus on a singular change in the code and should not try to conform to user-defined styles. For example, if a suggestion is adding a new statement into the codebase, it should not try to match correct indentation or conform to user preferences on the presence/absence of semicolons. All of those things can be corrected by multipass autofix when the user triggers it. Best practices for suggestions: @@ -375,7 +403,7 @@ Suggestions are intended to provide fixes. ESLint will automatically remove the #### Suggestion `messageId`s -Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageids)). Here is an example of how to use it in a rule: +Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageids)). Here is an example of how to use a suggestion `messageId` in a rule: ```js {% raw %} @@ -396,13 +424,13 @@ module.exports = { data: { character }, suggest: [ { - messageId: "removeEscape", + messageId: "removeEscape", // suggestion messageId fix: function(fixer) { return fixer.removeRange(range); } }, { - messageId: "escapeBackslash", + messageId: "escapeBackslash", // suggestion messageId fix: function(fixer) { return fixer.insertTextBeforeRange(range, "\\"); } @@ -414,7 +442,7 @@ module.exports = { {% endraw %} ``` -#### Placeholders in suggestion messages +#### Placeholders in Suggestion Messages You can also use placeholders in the suggestion message. This works the same way as placeholders for the overall error (see [using message placeholders](#using-message-placeholders)). @@ -451,9 +479,9 @@ module.exports = { {% endraw %} ``` -### context.options +### Accessing Options Passed to a Rule -Some rules require options in order to function correctly. These options appear in configuration (`.eslintrc`, command line, or in comments). For example: +Some rules require options in order to function correctly. These options appear in configuration (`.eslintrc`, command line interface, or comments). For example: ```json { @@ -477,9 +505,9 @@ Since `context.options` is just an array, you can use it to determine how many o When using options, make sure that your rule has some logical defaults in case the options are not provided. -### context.getSourceCode() +### Accessing the Source Code -The `SourceCode` object is the main object for getting more information about the source code being linted. You can retrieve the `SourceCode` object at any time by using the `getSourceCode()` method: +The `SourceCode` object is the main object for getting more information about the source code being linted. You can retrieve the `SourceCode` object at any time by using the `context.getSourceCode()` method: ```js module.exports = { @@ -493,76 +521,97 @@ module.exports = { Once you have an instance of `SourceCode`, you can use the following methods on it to work with the code: -* `getText(node)` - returns the source code for the given node. Omit `node` to get the whole source. -* `getAllComments()` - returns an array of all comments in the source. -* `getCommentsBefore(nodeOrToken)` - returns an array of comment tokens that occur directly before the given node or token. -* `getCommentsAfter(nodeOrToken)` - returns an array of comment tokens that occur directly after the given node or token. -* `getCommentsInside(node)` - returns an array of all comment tokens inside a given node. -* `isSpaceBetween(nodeOrToken, nodeOrToken)` - returns true if there is a whitespace character between the two tokens or, if given a node, the last token of the first node and the first token of the second node. -* `getFirstToken(node, skipOptions)` - returns the first token representing the given node. -* `getFirstTokens(node, countOptions)` - returns the first `count` tokens representing the given node. -* `getLastToken(node, skipOptions)` - returns the last token representing the given node. -* `getLastTokens(node, countOptions)` - returns the last `count` tokens representing the given node. -* `getTokenAfter(nodeOrToken, skipOptions)` - returns the first token after the given node or token. -* `getTokensAfter(nodeOrToken, countOptions)` - returns `count` tokens after the given node or token. -* `getTokenBefore(nodeOrToken, skipOptions)` - returns the first token before the given node or token. -* `getTokensBefore(nodeOrToken, countOptions)` - returns `count` tokens before the given node or token. -* `getFirstTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)` - returns the first token between two nodes or tokens. -* `getFirstTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)` - returns the first `count` tokens between two nodes or tokens. -* `getLastTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)` - returns the last token between two nodes or tokens. -* `getLastTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)` - returns the last `count` tokens between two nodes or tokens. -* `getTokens(node)` - returns all tokens for the given node. -* `getTokensBetween(nodeOrToken1, nodeOrToken2)` - returns all tokens between two nodes. -* `getTokenByRangeStart(index, rangeOptions)` - returns the token whose range starts at the given index in the source. -* `getNodeByRangeIndex(index)` - returns the deepest node in the AST containing the given source index. -* `getLocFromIndex(index)` - returns an object with `line` and `column` properties, corresponding to the location of the given source index. `line` is 1-based and `column` is 0-based. -* `getIndexFromLoc(loc)` - returns the index of a given location in the source code, where `loc` is an object with a 1-based `line` key and a 0-based `column` key. -* `commentsExistBetween(nodeOrToken1, nodeOrToken2)` - returns `true` if comments exist between two nodes. +* `getText(node)`: Returns the source code for the given node. Omit `node` to get the whole source (see the [dedicated section](#accessing-the-source-text)). +* `getAllComments()`: Returns an array of all comments in the source (see the [dedicated section](#accessing-comments)). +* `getCommentsBefore(nodeOrToken)`: Returns an array of comment tokens that occur directly before the given node or token (see the [dedicated section](#accessing-comments)). +* `getCommentsAfter(nodeOrToken)`: Returns an array of comment tokens that occur directly after the given node or token (see the [dedicated section](#accessing-comments)). +* `getCommentsInside(node)`: Returns an array of all comment tokens inside a given node (see the [dedicated section](#accessing-comments)). +* `isSpaceBetween(nodeOrToken, nodeOrToken)`: Returns true if there is a whitespace character between the two tokens or, if given a node, the last token of the first node and the first token of the second node. +* `getFirstToken(node, skipOptions)`: Returns the first token representing the given node. +* `getFirstTokens(node, countOptions)`: Returns the first `count` tokens representing the given node. +* `getLastToken(node, skipOptions)`: Returns the last token representing the given node. +* `getLastTokens(node, countOptions)`: Returns the last `count` tokens representing the given node. +* `getTokenAfter(nodeOrToken, skipOptions)`: Returns the first token after the given node or token. +* `getTokensAfter(nodeOrToken, countOptions)`: Returns `count` tokens after the given node or token. +* `getTokenBefore(nodeOrToken, skipOptions)`: Returns the first token before the given node or token. +* `getTokensBefore(nodeOrToken, countOptions)`: Returns `count` tokens before the given node or token. +* `getFirstTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)`: Returns the first token between two nodes or tokens. +* `getFirstTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)`: Returns the first `count` tokens between two nodes or tokens. +* `getLastTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)`: Returns the last token between two nodes or tokens. +* `getLastTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)`: Returns the last `count` tokens between two nodes or tokens. +* `getTokens(node)`: Returns all tokens for the given node. +* `getTokensBetween(nodeOrToken1, nodeOrToken2)`: Returns all tokens between two nodes. +* `getTokenByRangeStart(index, rangeOptions)`: Returns the token whose range starts at the given index in the source. +* `getNodeByRangeIndex(index)`: Returns the deepest node in the AST containing the given source index. +* `getLocFromIndex(index)`: Returns an object with `line` and `column` properties, corresponding to the location of the given source index. `line` is 1-based and `column` is 0-based. +* `getIndexFromLoc(loc)`: Returns the index of a given location in the source code, where `loc` is an object with a 1-based `line` key and a 0-based `column` key. +* `commentsExistBetween(nodeOrToken1, nodeOrToken2)`: Returns `true` if comments exist between two nodes. `skipOptions` is an object which has 3 properties; `skip`, `includeComments`, and `filter`. Default is `{skip: 0, includeComments: false, filter: null}`. -* `skip` is a positive integer, the number of skipping tokens. If `filter` option is given at the same time, it doesn't count filtered tokens as skipped. -* `includeComments` is a boolean value, the flag to include comment tokens into the result. -* `filter` is a function which gets a token as the first argument, if the function returns `false` then the result excludes the token. +* `skip`: (`number`) Positive integer, the number of skipping tokens. If `filter` option is given at the same time, it doesn't count filtered tokens as skipped. +* `includeComments`: (`boolean`) The flag to include comment tokens into the result. +* `filter(token)`: Function which gets a token as the first argument. If the function returns `false` then the result excludes the token. `countOptions` is an object which has 3 properties; `count`, `includeComments`, and `filter`. Default is `{count: 0, includeComments: false, filter: null}`. -* `count` is a positive integer, the maximum number of returning tokens. -* `includeComments` is a boolean value, the flag to include comment tokens into the result. -* `filter` is a function which gets a token as the first argument, if the function returns `false` then the result excludes the token. +* `count`: (`number`) Positive integer, the maximum number of returning tokens. +* `includeComments`: (`boolean`) The flag to include comment tokens into the result. +* `filter(token)`: Function which gets a token as the first argument, if the function returns `false` then the result excludes the token. -`rangeOptions` is an object which has 1 property: `includeComments`. +`rangeOptions` is an object that has 1 property, `includeComments`. Default is `{includeComments: false}`. -* `includeComments` is a boolean value, the flag to include comment tokens into the result. +* `includeComments`: (`boolean`) The flag to include comment tokens into the result. There are also some properties you can access: -* `hasBOM` - the flag to indicate whether or not the source code has Unicode BOM. -* `text` - the full text of the code being linted. Unicode BOM has been stripped from this text. -* `ast` - the `Program` node of the AST for the code being linted. -* `scopeManager` - the [ScopeManager](./scope-manager-interface#scopemanager-interface) object of the code. -* `visitorKeys` - the visitor keys to traverse this AST. -* `lines` - an array of lines, split according to the specification's definition of line breaks. +* `hasBOM`: (`boolean`) The flag to indicate whether the source code has Unicode BOM. +* `text`: (`string`) The full text of the code being linted. Unicode BOM has been stripped from this text. +* `ast`: (`object`) `Program` node of the AST for the code being linted. +* `scopeManager`: [ScopeManager](./scope-manager-interface#scopemanager-interface) object of the code. +* `visitorKeys`: (`object`) Visitor keys to traverse this AST. +* `lines`: (`array`) Array of lines, split according to the specification's definition of line breaks. You should use a `SourceCode` object whenever you need to get more information about the code being linted. -#### Deprecated +#### Accessing the Source Text + +If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows: -Please note that the following methods have been deprecated and will be removed in a future version of ESLint: +```js -* `getComments()` - replaced by `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` -* `getTokenOrCommentBefore()` - replaced by `getTokenBefore()` with the `{ includeComments: true }` option -* `getTokenOrCommentAfter()` - replaced by `getTokenAfter()` with the `{ includeComments: true }` option -* `isSpaceBetweenTokens()` - replaced by `isSpaceBetween()` -* `getJSDocComment()` +// get all source +var source = sourceCode.getText(); + +// get source for just this AST node +var nodeSource = sourceCode.getText(node); + +// get source for AST node plus previous two characters +var nodeSourceWithPrev = sourceCode.getText(node, 2); + +// get source for AST node plus following two characters +var nodeSourceWithFollowing = sourceCode.getText(node, 0, 2); +``` + +In this way, you can look for patterns in the JavaScript text itself when the AST isn't providing the appropriate data (such as the location of commas, semicolons, parentheses, etc.). + +#### Accessing Comments + +While comments are not technically part of the AST, ESLint provides the `sourceCode.getAllComments()`, `sourceCode.getCommentsBefore()`, `sourceCode.getCommentsAfter()`, and `sourceCode.getCommentsInside()` to access them. + +`sourceCode.getCommentsBefore()`, `sourceCode.getCommentsAfter()`, and `sourceCode.getCommentsInside()` are useful for rules that need to check comments in relation to a given node or token. + +Keep in mind that the results of these methods are calculated on demand. + +You can also access comments through many of `sourceCode`'s methods using the `includeComments` option. ### Options Schemas -Rules may export a `schema` property, which is a [JSON schema](https://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. +Rules may export a `schema` property, which is a [JSON Schema](https://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. There are two formats for a rule's exported `schema`. The first is a full JSON Schema object describing all possible options the rule accepts, including the rule's error level as the first argument and any optional arguments thereafter. -However, to simplify schema creation, rules may also export an array of schemas for each optional positional argument, and ESLint will automatically validate the required error level first. For example, the `yoda` rule accepts a primary mode argument, as well as an extra options object with named properties. +However, to simplify schema creation, rules may also export an array of schemas for each optional positional argument. ESLint automatically validates the required error level first. For example, the `yoda` rule accepts a primary mode argument, as well as an extra options object with named properties. ```js // "yoda": [2, "never", { "exceptRange": true }] @@ -586,54 +635,15 @@ module.exports = { }; ``` -In the preceding example, the error level is assumed to be the first argument. It is followed by the first optional argument, a string which may be either `"always"` or `"never"`. The final optional argument is an object, which may have a Boolean property named `exceptRange`. +In the preceding example, the error level is assumed to be the first argument. It is followed by the first optional argument, a string that may be either `"always"` or `"never"`. The final optional argument is an object, which may have a boolean property named `exceptRange`. To learn more about JSON Schema, we recommend looking at some examples in [website](https://json-schema.org/learn/) to start, and also reading [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) (a free ebook). -**Note:** Currently you need to use full JSON Schema object rather than array in case your schema has references ($ref), because in case of array format ESLint transforms this array into a single schema without updating references that makes them incorrect (they are ignored). - -### Getting the Source - -If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows: - -```js - -// get all source -var source = sourceCode.getText(); - -// get source for just this AST node -var nodeSource = sourceCode.getText(node); - -// get source for AST node plus previous two characters -var nodeSourceWithPrev = sourceCode.getText(node, 2); - -// get source for AST node plus following two characters -var nodeSourceWithFollowing = sourceCode.getText(node, 0, 2); -``` - -In this way, you can look for patterns in the JavaScript text itself when the AST isn't providing the appropriate data (such as location of commas, semicolons, parentheses, etc.). - -### Accessing Comments - -While comments are not technically part of the AST, ESLint provides a few ways for rules to access them: - -#### sourceCode.getAllComments() - -This method returns an array of all the comments found in the program. This is useful for rules that need to check all comments regardless of location. - -#### sourceCode.getCommentsBefore(), sourceCode.getCommentsAfter(), and sourceCode.getCommentsInside() - -These methods return an array of comments that appear directly before, directly after, and inside nodes, respectively. They are useful for rules that need to check comments in relation to a given node or token. - -Keep in mind that the results of this method are calculated on demand. - -#### Token traversal methods - -Finally, comments can be accessed through many of `sourceCode`'s methods using the `includeComments` option. +**Note:** If your rule schema uses JSON schema [`$ref`](https://json-schema.org/understanding-json-schema/structuring.html#ref) properties, you must use the full JSON Schema object rather than the array of positional property schemas. This is because ESLint transforms the array shorthand into a single schema without updating references that makes them incorrect (they are ignored). ### Accessing Shebangs -Shebangs are represented by tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined above. +[Shebangs (#!)](https://en.wikipedia.org/wiki/Shebang_(Unix)) are represented by the unique tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined in the [Accessing Comments](#accessing-comments) section, such as `sourceCode.getAllComments()`. ### Accessing Variable Scopes @@ -688,10 +698,17 @@ For examples of using `SourceCode#getScope()` to track variables, refer to the s ### Accessing Code Paths -ESLint analyzes code paths while traversing AST. -You can access that code path objects with five events related to code paths. +ESLint analyzes code paths while traversing AST. You can access code path objects with five events related to code paths. For more information, refer to [Code Path Analysis](code-path-analysis). + +### Deprecated `SourceCode` Methods -[details here](code-path-analysis) +Please note that the following `SourceCode` methods have been deprecated and will be removed in a future version of ESLint: + +* `getComments()`: Replaced by `SourceCode#getCommentsBefore()`, `SourceCode#getCommentsAfter()`, and `SourceCode#getCommentsInside()`. +* `getTokenOrCommentBefore()`: Replaced by `SourceCode#getTokenBefore()` with the `{ includeComments: true }` option. +* `getTokenOrCommentAfter()`: Replaced by `SourceCode#getTokenAfter()` with the `{ includeComments: true }` option. +* `isSpaceBetweenTokens()`: Replaced by `SourceCode#isSpaceBetween()` +* `getJSDocComment()` ## Rule Unit Tests @@ -699,11 +716,11 @@ ESLint provides the [`RuleTester`](../integrate/nodejs-api#ruletester) utility t ## Rule Naming Conventions -While you can give a custom rule any name you'd like, the core rules have naming conventions that it could be clearer to apply to your custom rule. To learn more, refer to the [Core Rule Naming Conventions](../contribute/core-rules#rule-naming-conventions) documentation. +While you can give a custom rule any name you'd like, the core rules have naming conventions. It could be clearer to apply these same naming conventions to your custom rule. To learn more, refer to the [Core Rule Naming Conventions](../contribute/core-rules#rule-naming-conventions) documentation. ## Runtime Rules -The thing that makes ESLint different from other linters is the ability to define custom rules at runtime. This is perfect for rules that are specific to your project or company and wouldn't make sense for ESLint to ship with. With runtime rules, you don't have to wait for the next version of ESLint or be disappointed that your rule isn't general enough to apply to the larger JavaScript community, just write your rules and include them at runtime. +The thing that makes ESLint different from other linters is the ability to define custom rules at runtime. This is perfect for rules that are specific to your project or company and wouldn't make sense for ESLint to ship with or be included in a plugin. Just write your rules and include them at runtime. Runtime rules are written in the same format as all other rules. Create your rule as you would any other and then follow these steps: diff --git a/docs/src/use/migrate-to-8.0.0.md b/docs/src/use/migrate-to-8.0.0.md index 71d92c52087a..2eaa942b91b2 100644 --- a/docs/src/use/migrate-to-8.0.0.md +++ b/docs/src/use/migrate-to-8.0.0.md @@ -178,7 +178,7 @@ Back in ESLint v4.0.0, we deprecated `SourceCode#getComments()`, but we neglecte The `SourceCode#getComments()` method will be removed in v9.0.0. -**To address:** If your rule uses `SourceCode#getComments()`, please use [`SourceCode#getCommentsBefore()`, `SourceCode#getCommentsAfter()`, or `SourceCode#getCommentsInside()`](../extend/custom-rules#sourcecodegetcommentsbefore-sourcecodegetcommentsafter-and-sourcecodegetcommentsinside). +**To address:** If your rule uses `SourceCode#getComments()`, please use [`SourceCode#getCommentsBefore()`, `SourceCode#getCommentsAfter()`, or `SourceCode#getCommentsInside()`](../extend/custom-rules#accessing-comments). **Related issue(s):** [#14744](https://github.com/eslint/eslint/issues/14744) diff --git a/docs/src/use/migrating-to-5.0.0.md b/docs/src/use/migrating-to-5.0.0.md index 5eb4ec7ad1a1..96c54ec5dee8 100644 --- a/docs/src/use/migrating-to-5.0.0.md +++ b/docs/src/use/migrating-to-5.0.0.md @@ -221,7 +221,7 @@ Previously, the `context.getScope()` method changed its behavior based on the `p Additionally, `context.getScope()` incorrectly returned the parent scope of the proper scope on `CatchClause` (in ES5), `ForStatement` (in โ‰งES2015), `ForInStatement` (in โ‰งES2015), `ForOfStatement`, and `WithStatement` nodes. -In ESLint v5, the `context.getScope()` method has the same behavior regardless of `parserOptions.ecmaVersion` and returns the proper scope. See [the documentation](../extend/custom-rules#contextgetscope) for more details on which scopes are returned. +In ESLint v5, the `context.getScope()` method has the same behavior regardless of `parserOptions.ecmaVersion` and returns the proper scope. See [the documentation](../extend/scope-manager-interface) for more details on which scopes are returned. **To address:** If you have written a custom rule that uses the `context.getScope()` method in node handlers, you may need to update it to account for the modified scope information. From f5c01f281ad288b1a0ebddbf579230ae11587c6c Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Mon, 12 Jun 2023 23:28:33 +0530 Subject: [PATCH 031/248] docs: resubmit `Manage Issues` documentation (#17266) * docs: resubmit `Manage Issues` documentation * docs: resubmit `Manage Issues` documentation * docs: Update triage board location --- docs/src/maintain/manage-issues.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/src/maintain/manage-issues.md b/docs/src/maintain/manage-issues.md index 6cfdbc93b191..778ad15ddbfa 100644 --- a/docs/src/maintain/manage-issues.md +++ b/docs/src/maintain/manage-issues.md @@ -30,7 +30,7 @@ The first goal when evaluating an issue is to determine which category the issue ## Triaging Process -All of ESLint's issues, across all GitHub repositories, are managed on our [Triage Project](https://github.com/orgs/eslint/projects/2). Please use the Triage project instead of the issues list when reviewing issues to determine what to work on. The Triage project has several columns: +All of ESLint's issues, across all GitHub repositories, are managed on our [Triage Project](https://github.com/orgs/eslint/projects/3). Please use the Triage project instead of the issues list when reviewing issues to determine what to work on. The Triage project has several columns: * **Needs Triage**: Issues that have not yet been reviewed by anyone * **Triaging**: Issues that someone has reviewed but has not been able to fully triage yet @@ -41,7 +41,7 @@ All of ESLint's issues, across all GitHub repositories, are managed on our [Tria * **RFC Opened**: An RFC is opened to address these issues * **Blocked**: The issue can't move forward due to some dependency * **Ready to Implement**: These issues have all the details necessary to start implementation -* **PR Opened**: There is an open pull request for each of these issues +* **Implementing**: There is an open pull request for each of these issues * **Completed**: The issue has been closed (either via pull request merge or by the team manually closing the issue) We make every attempt to automate movement between as many columns as we can, but sometimes moving issues needs to be done manually. @@ -64,7 +64,7 @@ The steps for triaging an issue are: * If the issue is actually a question (rather than something the dev team needs to change), please [convert it to a discussion](https://docs.github.com/en/free-pro-team@latest/discussions/managing-discussions-for-your-community/moderating-discussions#converting-an-issue-to-a-discussion). You can continue the conversation as a discussion. * If the issue is reporting a bug, try to reproduce the issue following the instructions in the issue. If you can reproduce the bug, please add the "repro:yes" label. (The bot will automatically remove the "repro:needed" label.) If you can't reproduce the bug, ask the author for more information about their environment or to clarify reproduction steps. * If the issue is reporting something that works as intended, please add the "works as intended" label and close the issue. - * For all issues, please add labels describing the part of ESLint affected: + * Please add labels describing the part of ESLint affected: * **3rd party plugin**: Related to third-party functionality (plugins, parsers, rules, etc.) * **build**: Related to commands run during a build (testing, linting, release scripts, etc.) * **cli**: Related to command line input or output, or to `CLIEngine` @@ -72,6 +72,16 @@ The steps for triaging an issue are: * **documentation**: Related to content on eslint.org * **infrastructure**: Related to resources needed for builds or deployment (VMs, CI tools, bots, etc.) * **rule**: Related to core rules + * Please assign an initial priority based on the importance of the issue. If you're not sure, use your best judgment. We can always change the priority later. + * **P1**: Urgent and important, we need to address this immediately. + * **P2**: Important but not urgent. Should be handled by a TSC member or reviewer. + * **P3**: Nice to have but not important. Can be handled by any team member. + * **P4**: A good idea that we'd like to have but may take a while for the team to get to it. + * **P5**: A good idea that the core team can't commit to. Will likely need to be done by an outside contributor. + * Please assign an initial impact assessment (make your best guess): + * **Low**: Doesn't affect many users. + * **Medium**: Affects most users or has a noticeable effect on user experience. + * **High**: Affects a lot of users, is a breaking change, or otherwise will be very noticeable to users. * If you can't properly triage the issue, move the issue back to the "Needs Triage" column in the Triage project so someone else can triage it. * If you have triaged the issue, move the issue to the "Ready for Dev Team" column in the Triage project. @@ -120,8 +130,8 @@ Consensus is reached on issues when there are at least three team members who be If consensus cannot be reached on an issue, or an issue's progress has been stalled and it's not clear if the issue should be closed, then you can refer the issue to the TSC for resolution. To do so, add the "tsc agenda" label to the issue and add a comment including the following information: -1. A one-paragraph summary of the discussion to this point. -2. The question you would like the TSC to answer. +1. A one-paragraph summary of the discussion to this point. This should begin with "TSC Summary:". +2. The question you would like the TSC to answer. This should begin with "TSC Question:". The issue will be discussed at the next TSC meeting and the resolution will be posted back to the issue. From bdca88cf4f8b7888cb72197bfe9c1d90b490a0dd Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Mon, 12 Jun 2023 23:29:15 +0530 Subject: [PATCH 032/248] docs: resubmit `Configuration Files` documentation (#17267) --- docs/src/use/configure/configuration-files-new.md | 2 +- docs/src/use/configure/configuration-files.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/src/use/configure/configuration-files-new.md b/docs/src/use/configure/configuration-files-new.md index de69801ff5e4..62394c0b2e73 100644 --- a/docs/src/use/configure/configuration-files-new.md +++ b/docs/src/use/configure/configuration-files-new.md @@ -9,7 +9,7 @@ eleventyNavigation: --- ::: warning -This is an experimental feature. To opt-in, place an `eslint.config.js` file in the root of your project or set the `ESLINT_USE_FLAT_CONFIG` environment variable to `true`. To opt-out, even in the presence of an `eslint.config.js` file, set the environment variable to `false`. If you are using the API, you can use the configuration system described on this page by using the `FlatESLint` class, the `FlatRuleTester` class, or by setting `configType: "flat"` in the `Linter` class. +This config system is feature complete but not enabled by default. To opt-in, place an `eslint.config.js` file in the root of your project or set the `ESLINT_USE_FLAT_CONFIG` environment variable to `true`. To opt-out, even in the presence of an `eslint.config.js` file, set the environment variable to `false`. If you are using the API, you can use the configuration system described on this page by using the `FlatESLint` class, the `FlatRuleTester` class, or by setting `configType: "flat"` in the `Linter` class. ::: You can put your ESLint project configuration in a configuration file. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. diff --git a/docs/src/use/configure/configuration-files.md b/docs/src/use/configure/configuration-files.md index f9c7aa341f3a..b2d51e09a3be 100644 --- a/docs/src/use/configure/configuration-files.md +++ b/docs/src/use/configure/configuration-files.md @@ -8,6 +8,10 @@ eleventyNavigation: --- +::: warning +We are transitioning to a new config system in ESLint v9.0.0. The config system shared on this page is currently the default but will be deprecated in v9.0.0. You can opt-in to the new config system by following the instructions in the [documentation](configuration-files-new). +::: + You can put your ESLint project configuration in a configuration file. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. ## Configuration File Formats From 428fc76806dea1ac82484d628261a5385f928e6a Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Mon, 12 Jun 2023 23:29:57 +0530 Subject: [PATCH 033/248] docs: resubmit `Create Plugins` documentation (#17268) --- docs/src/extend/plugins.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/src/extend/plugins.md b/docs/src/extend/plugins.md index f180c034faa9..9d7983e67f27 100644 --- a/docs/src/extend/plugins.md +++ b/docs/src/extend/plugins.md @@ -18,6 +18,34 @@ Each plugin is an npm module with a name in the format of `eslint-plugin- Date: Mon, 12 Jun 2023 23:32:13 +0530 Subject: [PATCH 034/248] docs: resubmit `Custom Processors` documentation (#17265) --- docs/src/extend/custom-processors.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/extend/custom-processors.md b/docs/src/extend/custom-processors.md index 8f330883b03b..d112d7246885 100644 --- a/docs/src/extend/custom-processors.md +++ b/docs/src/extend/custom-processors.md @@ -59,19 +59,19 @@ Reported problems have the following location information in each lint message: type LintMessage = { /// The 1-based line number where the message occurs. - line: number; + line?: number; /// The 1-based column number where the message occurs. - column: number; + column?: number; /// The 1-based line number of the end location. - endLine: number; + endLine?: number; /// The 1-based column number of the end location. - endColumn: number; + endColumn?: number; /// If `true`, this is a fatal error. - fatal: boolean; + fatal?: boolean; /// Information for an autofix. fix: Fix; From 8e51ea943c2fcd05bd8917cfa89e36b91209c7cd Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Mon, 12 Jun 2023 23:32:46 +0530 Subject: [PATCH 035/248] docs: resubmit `no-new` rule documentation (#17264) --- docs/src/rules/no-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/rules/no-new.md b/docs/src/rules/no-new.md index 9eeda70095a9..c8cea29cb0ff 100644 --- a/docs/src/rules/no-new.md +++ b/docs/src/rules/no-new.md @@ -43,7 +43,7 @@ Examples of **correct** code for this rule: var thing = new Thing(); -Thing(); +Foo(); ``` ::: From e0cf0d86d985ed2b2f901dd9aab5ccd2fff062ad Mon Sep 17 00:00:00 2001 From: Ben Perlmutter <57849986+bpmutter@users.noreply.github.com> Date: Mon, 12 Jun 2023 14:03:07 -0400 Subject: [PATCH 036/248] docs: Custom rule & plugin tutorial (#17024) * Start copy edits * checkpoint * docs update * docs update * Add intro * Copy edits * remove old draft of tutorial * Fix take ii * update tutorial * Apply suggestions from code review Co-authored-by: Nicholas C. Zakas * update src code names * rename tutorial * update code examples to match updated code * additional fixes * implement additional NZ feedback * Apply suggestions from code review Co-authored-by: Nitin Kumar Co-authored-by: Samuel Roldan Co-authored-by: moniuch * draft tutorial changes * copy edits * fix lint err * Apply suggestions from code review Co-authored-by: Nitin Kumar * add file overviews * copy edits on step 8 intro * Apply suggestions from code review Co-authored-by: Nicholas C. Zakas * implement NZ feedback * Apply suggestions from code review Co-authored-by: Milos Djermanovic * Apply suggestions from code review Co-authored-by: Milos Djermanovic * Apply suggestions from code review * Apply suggestions from code review * Apply suggestions from code review * update rule name to "enforce-foo-bar" * Apply suggestions from code review * implement MD feedback * Apply suggestions from code review Co-authored-by: Milos Djermanovic * implement MD feedback * copy edit * Apply suggestions from code review Co-authored-by: Milos Djermanovic * move tutorial to _examples dir * undo big change * Fix broken example * Apply suggestions from code review Co-authored-by: Nitin Kumar --------- Co-authored-by: Nicholas C. Zakas Co-authored-by: Nitin Kumar Co-authored-by: Samuel Roldan Co-authored-by: moniuch Co-authored-by: Milos Djermanovic --- .../custom-rule-tutorial-code/.gitignore | 1 + .../enforce-foo-bar.js | 57 +++ .../enforce-foo-bar.test.js | 34 ++ .../eslint-plugin-example.js | 9 + .../eslint.config.js | 23 + .../custom-rule-tutorial-code/example.js | 22 + .../custom-rule-tutorial-code/package.json | 22 + docs/src/extend/custom-processors.md | 2 +- docs/src/extend/custom-rule-tutorial.md | 478 ++++++++++++++++++ docs/src/extend/custom-rules.md | 2 +- docs/src/extend/index.md | 4 + 11 files changed, 652 insertions(+), 2 deletions(-) create mode 100644 docs/_examples/custom-rule-tutorial-code/.gitignore create mode 100644 docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.js create mode 100644 docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.test.js create mode 100644 docs/_examples/custom-rule-tutorial-code/eslint-plugin-example.js create mode 100644 docs/_examples/custom-rule-tutorial-code/eslint.config.js create mode 100644 docs/_examples/custom-rule-tutorial-code/example.js create mode 100644 docs/_examples/custom-rule-tutorial-code/package.json create mode 100644 docs/src/extend/custom-rule-tutorial.md diff --git a/docs/_examples/custom-rule-tutorial-code/.gitignore b/docs/_examples/custom-rule-tutorial-code/.gitignore new file mode 100644 index 000000000000..b512c09d4766 --- /dev/null +++ b/docs/_examples/custom-rule-tutorial-code/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.js b/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.js new file mode 100644 index 000000000000..5f3e677f638f --- /dev/null +++ b/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.js @@ -0,0 +1,57 @@ +/** + * @fileoverview Rule to enforce that `const foo` is assigned "bar". + * @author Ben Perlmutter + */ + +"use strict"; + +// The enforce-foo-bar rule definition +module.exports = { + meta: { + type: "problem", + docs: { + description: "Enforce that a variable named `foo` can only be assigned a value of 'bar'." + }, + fixable: "code", + schema: [] + }, + create(context) { + return { + + // Performs action in the function on every variable declarator + VariableDeclarator(node) { + + // Check if a `const` variable declaration + if (node.parent.kind === "const") { + + // Check if variable name is `foo` + if (node.id.type === "Identifier" && node.id.name === "foo") { + + // Check if value of variable is "bar" + if (node.init && node.init.type === "Literal" && node.init.value !== "bar") { + + /* + * Report error to ESLint. Error message uses + * a message placeholder to include the incorrect value + * in the error message. + * Also includes a `fix(fixer)` function that replaces + * any values assigned to `const foo` with "bar". + */ + context.report({ + node, + message: 'Value other than "bar" assigned to `const foo`. Unexpected value: {{ notBar }}.', + data: { + notBar: node.init.value + }, + fix(fixer) { + return fixer.replaceText(node.init, '"bar"'); + } + }); + } + } + } + } + }; + } +}; + 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 new file mode 100644 index 000000000000..d5f9c40334db --- /dev/null +++ b/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.test.js @@ -0,0 +1,34 @@ +/** + * @fileoverview Tests for enforce-foo-bar.js rule. + * @author Ben Perlmutter +*/ +"use strict"; + +const {RuleTester} = require("eslint"); +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 } +}); + +// Throws error if the tests in ruleTester.run() do not pass +ruleTester.run( + "enforce-foo-bar", // rule name + fooBarRule, // rule code + { // checks + // 'valid' checks cases that should pass + valid: [{ + code: "const foo = 'bar';", + }], + // 'invalid' checks cases that should not pass + invalid: [{ + code: "const foo = 'baz';", + output: 'const foo = "bar";', + errors: 1, + }], + } +); + +console.log("All tests passed!"); \ No newline at end of file diff --git a/docs/_examples/custom-rule-tutorial-code/eslint-plugin-example.js b/docs/_examples/custom-rule-tutorial-code/eslint-plugin-example.js new file mode 100644 index 000000000000..1a32ca4db0a2 --- /dev/null +++ b/docs/_examples/custom-rule-tutorial-code/eslint-plugin-example.js @@ -0,0 +1,9 @@ +/** + * @fileoverview Example an ESLint plugin with a custom rule. + * @author Ben Perlmutter +*/ +"use strict"; + +const fooBarRule = require("./enforce-foo-bar"); +const plugin = { rules: { "enforce-foo-bar": fooBarRule } }; +module.exports = plugin; diff --git a/docs/_examples/custom-rule-tutorial-code/eslint.config.js b/docs/_examples/custom-rule-tutorial-code/eslint.config.js new file mode 100644 index 000000000000..cf08f1ee57cd --- /dev/null +++ b/docs/_examples/custom-rule-tutorial-code/eslint.config.js @@ -0,0 +1,23 @@ +/** + * @fileoverview Example ESLint config file that uses the custom rule from this tutorial. + * @author Ben Perlmutter +*/ +"use strict"; + +// Import the ESLint plugin +const eslintPluginExample = require("./eslint-plugin-example"); + +module.exports = [ + { + files: ["**/*.js"], + languageOptions: { + sourceType: "commonjs", + ecmaVersion: "latest", + }, + // Using the eslint-plugin-example plugin defined locally + plugins: {"example": eslintPluginExample}, + rules: { + "example/enforce-foo-bar": "error", + }, + } +] diff --git a/docs/_examples/custom-rule-tutorial-code/example.js b/docs/_examples/custom-rule-tutorial-code/example.js new file mode 100644 index 000000000000..0d6da91d49ea --- /dev/null +++ b/docs/_examples/custom-rule-tutorial-code/example.js @@ -0,0 +1,22 @@ +/** + * @fileoverview Example of a file that will fail the custom rule in this tutorial. + * @author Ben Perlmutter +*/ +"use strict"; + +/* eslint-disable no-unused-vars -- Disable other rule causing problem for this file */ + +// To see the error in the terminal, run the following command: +// npx eslint example.js + +// To fix the error, run the following command: +// npx eslint example.js --fix + +function correctFooBar() { + const foo = "bar"; +} + +function incorrectFoo(){ + const foo = "baz"; // Problem! +} + diff --git a/docs/_examples/custom-rule-tutorial-code/package.json b/docs/_examples/custom-rule-tutorial-code/package.json new file mode 100644 index 000000000000..0578c79496c9 --- /dev/null +++ b/docs/_examples/custom-rule-tutorial-code/package.json @@ -0,0 +1,22 @@ +{ + "name": "eslint-plugin-example", + "version": "1.0.0", + "description": "ESLint plugin for enforce-foo-bar rule.", + "main": "eslint-plugin-example.js", + "keywords": [ + "eslint", + "eslintplugin", + "eslint-plugin" + ], + "peerDependencies": { + "eslint": ">=8.0.0" + }, + "scripts": { + "test": "node enforce-foo-bar.test.js" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "eslint": "^8.36.0" + } +} \ No newline at end of file diff --git a/docs/src/extend/custom-processors.md b/docs/src/extend/custom-processors.md index d112d7246885..697d052e6556 100644 --- a/docs/src/extend/custom-processors.md +++ b/docs/src/extend/custom-processors.md @@ -4,7 +4,7 @@ eleventyNavigation: key: custom processors parent: create plugins title: Custom Processors - order: 2 + order: 3 --- diff --git a/docs/src/extend/custom-rule-tutorial.md b/docs/src/extend/custom-rule-tutorial.md new file mode 100644 index 000000000000..8426961acae5 --- /dev/null +++ b/docs/src/extend/custom-rule-tutorial.md @@ -0,0 +1,478 @@ +--- +title: Custom Rule Tutorial +eleventyNavigation: + key: custom rule tutorial + parent: create plugins + title: Custom Rule Tutorial + order: 1 +--- +This tutorial covers how to create a custom rule for ESLint and distribute it with a plugin. + +You can create custom rules to validate if your code meets a certain expectation, and determine what to do if it does not meet that expectation. Plugins package custom rules and other configuration, allowing you to easily share and reuse them in different projects. + +To learn more about custom rules and plugins refer to the following documentation: + +* [Custom Rules](custom-rules) +* [Plugins](plugins) + +## Why Create a Custom Rule? + +Create a custom rule if the ESLint [built-in rules](../rules/) and community-published custom rules do not meet your needs. You might create a custom rule to enforce a best practice for your company or project, prevent a particular bug from recurring, or ensure compliance with a style guide. + +Before creating a custom rule that isn't specific to your company or project, it's worth searching the web to see if someone has published a plugin with a custom rule that solves your use case. It's quite possible the rule may already exist. + +## Prerequisites + +Before you begin, make sure you have the following installed in your development environment: + +* [Node.js](https://nodejs.org/en/download/) +* [npm](https://www.npmjs.com/) + +This tutorial also assumes that you have a basic understanding of ESLint and ESLint rules. + +## The Custom Rule + +The custom rule in this tutorial requires that all `const` variables named `foo` are assigned the string literal `"bar"`. The rule is defined in the file `enforce-foo-bar.js`. The rule also suggests replacing any other value assigned to `const foo` with `"bar"`. + +For example, say you had the following `foo.js` file: + +```javascript +// foo.js + +const foo = "baz123"; +``` + +Running ESLint with the rule would flag `"baz123"` as an incorrect value for variable `foo`. If ESLint is running in autofix mode, then ESLint would fix the file to contain the following: + +```javascript +// foo.js + +const foo = "bar"; +``` + +## Step 1: Set up Your Project + +First, create a new project for your custom rule. Create a new directory, initiate a new npm project in it, and create a new file for the custom rule: + +```shell +mkdir eslint-custom-rule-example # create directory +cd eslint-custom-rule-example # enter the directory +npm init -y # init new npm project +touch enforce-foo-bar.js # create file enforce-foo-bar.js +``` + +## Step 2: Stub Out the Rule File + +In the `enforce-foo-bar.js` file, add some scaffolding for the `enforce-foo-bar` custom rule. Also, add a `meta` object with some basic information about the rule. + +```javascript +// enforce-foo-bar.js + +module.exports = { + meta: { + // TODO: add metadata + }, + create(context) { + return { + // TODO: add callback function(s) + }; + } +}; +``` + +## Step 3: Add Rule Metadata + +Before writing the rule, add some metadata to the rule object. ESLint uses this information when running the rule. + +Start by exporting an object with a `meta` property containing the rule's metadata, such as the rule type, documentation, and fixability. In this case, the rule type is "problem," the description is "Enforce that a variable named `foo` can only be assigned a value of 'bar'.", and the rule is fixable by modifying the code. + +```javascript +// enforce-foo-bar.js + +module.exports = { + meta: { + type: "problem", + docs: { + description: "Enforce that a variable named `foo` can only be assigned a value of 'bar'.", + }, + fixable: "code", + schema: [] + }, + create(context) { + return { + // TODO: add callback function(s) + }; + } +}; +``` + +To learn more about rule metadata, refer to [Rule Structure](custom-rules#rule-structure). + +## Step 4: Add Rule Visitor Methods + +Define the rule's `create` function, which accepts a `context` object and returns an object with a property for each syntax node type you want to handle. In this case, you want to handle `VariableDeclarator` nodes. +You can choose any [ESTree node type](https://github.com/estree/estree) or [selector](selectors). + +Inside the `VariableDeclarator` visitor method, check if the node represents a `const` variable declaration, if its name is `foo`, and if it's not assigned to the string `"bar"`. You do this by evaluating the `node` passed to the `VariableDeclaration` method. + +If the `const foo` declaration is assigned a value of `"bar"`, then the rule does nothing. If `const foo` **is not** assigned a value of `"bar"`, then `context.report()` reports an error to ESLint. The error report includes information about the error and how to fix it. + +```javascript +// enforce-foo-bar.js +{% raw %} +module.exports = { + meta: { + type: "problem", + docs: { + description: "Enforce that a variable named `foo` can only be assigned a value of 'bar'." + }, + fixable: "code", + schema: [] + }, + create(context) { + return { + + // Performs action in the function on every variable declarator + VariableDeclarator(node) { + + // Check if a `const` variable declaration + if (node.parent.kind === "const") { + + // Check if variable name is `foo` + if (node.id.type === "Identifier" && node.id.name === "foo") { + + // Check if value of variable is "bar" + if (node.init && node.init.type === "Literal" && node.init.value !== "bar") { + + /* + * Report error to ESLint. Error message uses + * a message placeholder to include the incorrect value + * in the error message. + * Also includes a `fix(fixer)` function that replaces + * any values assigned to `const foo` with "bar". + */ + context.report({ + node, + message: 'Value other than "bar" assigned to `const foo`. Unexpected value: {{ notBar }}.', + data: { + notBar: node.init.value + }, + fix(fixer) { + return fixer.replaceText(node.init, '"bar"'); + } + }); + } + } + } + } + }; + } +}; +{% endraw %} +``` + +## Step 5: Set up Testing + +With the rule written, you can test it to make sure it's working as expected. + +ESLint provides the built-in [`RuleTester`](../integrate/nodejs-api#ruletester) class to test rules. You do not need to use third-party testing libraries to test ESLint rules, but `RuleTester` works seamlessly with tools like Mocha and Jest. + +Next, create the file for the tests, `enforce-foo-bar.test.js`: + +```shell +touch enforce-foo-bar.test.js +``` + +You will use the `eslint` package in the test file. Install it as a development dependency: + +```shell +npm install eslint --save-dev +``` + +And add a test script to your `package.json` file to run the tests: + +```javascript +// package.json +{ + // ...other configuration + "scripts": { + "test": "node enforce-foo-bar.test.js" + }, + // ...other configuration +} +``` + +## Step 6: Write the Test + +To write the test using `RuleTester`, import the class and your custom rule into the `enforce-foo-bar.test.js` file. + +The `RuleTester#run()` method tests the rule against valid and invalid test cases. If the rule fails to pass any of the test scenarios, this method throws an error. +`RuleTester` requires that at least one valid and one invalid test scenario be present. + +```javascript +// enforce-foo-bar.test.js +const {RuleTester} = require("eslint"); +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 } +}); + +// Throws error if the tests in ruleTester.run() do not pass +ruleTester.run( + "enforce-foo-bar", // rule name + fooBarRule, // rule code + { // checks + // 'valid' checks cases that should pass + valid: [{ + code: "const foo = 'bar';", + }], + // 'invalid' checks cases that should not pass + invalid: [{ + code: "const foo = 'baz';", + output: 'const foo = "bar";', + errors: 1, + }], + } +); + +console.log("All tests passed!"); +``` + +Run the test with the following command: + +```shell +npm test +``` + +If the test passes, you should see the following in your console: + +```shell +All tests passed! +``` + +## Step 7: Bundle the Custom Rule in a Plugin + +Now that you've written the custom rule and validated that it works, you can include it in a plugin. Using a plugin, you can share the rule in an npm package to use in other projects. + +Create the file for the plugin: + +```shell +touch eslint-plugin-example.js +``` + +And now write the plugin code. Plugins are just exported JavaScript objects. To include a rule in a plugin, include it in the plugin's `rules` object, which contains key-value pairs of rule names and their source code. + +To learn more about creating plugins, refer to [Create Plugins](plugins). + +```javascript +// eslint-plugin-example.js + +const fooBarRule = require("./enforce-foo-bar"); +const plugin = { rules: { "enforce-foo-bar": fooBarRule } }; +module.exports = plugin; +``` + +## Step 8: Use the Plugin Locally + +You can use a locally defined plugin to execute the custom rule in your project. To use a local plugin, specify the path to the plugin in the `plugins` property of your ESLint configuration file. + +You might want to use a locally defined plugin in one of the following scenarios: + +* You want to test the plugin before publishing it to npm. +* You want to use a plugin, but do not want to publish it to npm. + +Before you can add the plugin to the project, create an ESLint configuration for your project using a [flat configuration file](../use/configure/configuration-files-new), `eslint.config.js`: + +```shell +touch eslint.config.js +``` + +Then, add the following code to `eslint.config.js`: + +```javascript +// eslint.config.js +"use strict"; + +// Import the ESLint plugin locally +const eslintPluginExample = require("./eslint-plugin-example"); + +module.exports = [ + { + files: ["**/*.js"], + languageOptions: { + sourceType: "commonjs", + ecmaVersion: "latest", + }, + // Using the eslint-plugin-example plugin defined locally + plugins: {"example": eslintPluginExample}, + rules: { + "example/enforce-foo-bar": "error", + }, + } +] +``` + +Before you can test the rule, you must create a file to test the rule on. + +Create a file `example.js`: + +```shell +touch example.js +``` + +Add the following code to `example.js`: + +```javascript +// example.js + +function correctFooBar() { + const foo = "bar"; +} + +function incorrectFoo(){ + const foo = "baz"; // Problem! +} +``` + +Now you're ready to test the custom rule with the locally defined plugin. + +Run ESLint on `example.js`: + +```shell +npx eslint example.js +``` + +This produces the following output in the terminal: + +```text +//eslint-custom-rule-example/example.js + 8:11 error Value other than "bar" assigned to `const foo`. Unexpected value: baz example/enforce-foo-bar + +โœ– 1 problem (1 error, 0 warnings) + 1 error and 0 warnings potentially fixable with the `--fix` option. +``` + +## Step 9: Publish the Plugin + +To publish a plugin containing a rule to npm, you need to configure the `package.json`. Add the following in the corresponding fields: + +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. `"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: + +```javascript +// package.json +{ + // Name npm package. + // Add your own package name. eslint-plugin-example is taken! + "name": "eslint-plugin-example", + "version": "1.0.0", + "description": "ESLint plugin for enforce-foo-bar rule.", + "main": "eslint-plugin-example.js", // plugin entry point + "scripts": { + "test": "node enforce-foo-bar.test.js" + }, + // Add eslint>=8.0.0 as a peer dependency. + "peerDependencies": { + "eslint": ">=8.0.0" + }, + // Add these standard keywords to make plugin easy to find! + "keywords": [ + "eslint", + "eslintplugin", + "eslint-plugin" + ], + "author": "", + "license": "ISC", + "devDependencies": { + "eslint": "^8.36.0" + } +} +``` + +To publish the package, run `npm publish` and follow the CLI prompts. + +You should see the package live on npm! + +## Step 10: Use the Published Custom Rule + +Next, you can use the published plugin. + +Run the following command in your project to download the package: + +```shell +npm install --save-dev eslint-plugin-example # Add your package name here +``` + +Update the `eslint.config.js` to use the packaged version of the plugin: + +```javascript +// eslint.config.js +"use strict"; + +// Import the plugin downloaded from npm +const eslintPluginExample = require("eslint-plugin-example"); + +// ... rest of configuration +``` + +Now you're ready to test the custom rule. + +Run ESLint on the `example.js` file you created in step 8, now with the downloaded plugin: + +```shell +npx eslint example.js +``` + +This produces the following output in the terminal: + +```text +//eslint-custom-rule-example/example.js + 8:11 error Value other than "bar" assigned to `const foo`. Unexpected value: baz example/enforce-foo-bar + +โœ– 1 problem (1 error, 0 warnings) + 1 error and 0 warnings potentially fixable with the `--fix` option. +``` + +As you can see in the above message, you can actually fix the issue with the `--fix` flag, correcting the variable assignment to be `"bar"`. + +Run ESLint again with the `--fix` flag: + +```shell +npx eslint example.js --fix +``` + +There is no error output in the terminal when you run this, but you can see the fix applied in `example.js`. You should see the following: + +```javascript +// example.js + +// ... rest of file + +function incorrectFoo(){ + const foo = "bar"; // Fixed! +} +``` + +## Summary + +In this tutorial, you've made a custom rule that requires all `const` variables named `foo` to be assigned the string `"bar"` and suggests replacing any other value assigned to `const foo` with `"bar"`. You've also added the rule to a plugin, and published the plugin on npm. + +Through doing this, you've learned the following practices which you can apply to create other custom rules and plugins: + +1. Creating a custom ESLint rule +1. Testing the custom rule +1. Bundling the rule in a plugin +1. Publishing the plugin +1. Using the rule from the plugin + +## View the Tutorial Code + +You can view the annotated source code for the tutorial [here](https://github.com/eslint/eslint/tree/main/docs/_examples/custom-rule-tutorial-code). diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index a20d65d3a653..cd3c6663306b 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -4,7 +4,7 @@ eleventyNavigation: key: custom rules parent: create plugins title: Custom Rules - order: 1 + order: 2 --- diff --git a/docs/src/extend/index.md b/docs/src/extend/index.md index 9f623a6119d0..844d0789875a 100644 --- a/docs/src/extend/index.md +++ b/docs/src/extend/index.md @@ -25,6 +25,10 @@ This page summarizes the various ways that you can extend ESLint and how these e You've developed custom rules for ESLint and you want to share them with the community. You can publish an ESLint plugin on npm. +## [Custom Rule Tutorial](custom-rule-tutorial) + +A tutorial that walks you through creating a custom rule for ESLint. + ## [Custom Rules](custom-rules) This section explains how to create custom rules to use with ESLint. From 54383e69b092ef537d59a1f7799a85b1412f4e59 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Tue, 13 Jun 2023 17:33:07 +0200 Subject: [PATCH 037/248] fix: Remove `no-extra-parens` autofix for potential directives (#17022) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Remove `no-extra-parens` autofix for potential directives * Update lib/rules/no-extra-parens.js Co-authored-by: Nicholas C. Zakas * Elaborate JSDoc for `isFixable` * Update docs for `no-extra-parens` * Update lib/rules/no-extra-parens.js Co-authored-by: Nicholas C. Zakas * One empty line is enough ๐Ÿ˜Š * Review updates * Check for top-level ExpressionStatement * Review updates * Revert changes in `quotes` rule --------- Co-authored-by: Nicholas C. Zakas --- docs/src/rules/no-extra-parens.md | 19 +++++++ lib/rules/no-extra-parens.js | 42 ++++++++++++--- lib/rules/no-unused-expressions.js | 8 ++- lib/rules/padding-line-between-statements.js | 9 +--- lib/rules/utils/ast-utils.js | 23 +++++++- tests/lib/rules/no-extra-parens.js | 20 ++++++- tests/lib/rules/utils/ast-utils.js | 55 ++++++++++++++++++++ 7 files changed, 152 insertions(+), 24 deletions(-) diff --git a/docs/src/rules/no-extra-parens.md b/docs/src/rules/no-extra-parens.md index 9b1b8df07250..dd2b4642008d 100644 --- a/docs/src/rules/no-extra-parens.md +++ b/docs/src/rules/no-extra-parens.md @@ -21,6 +21,25 @@ This rule always ignores extra parentheses around the following: * immediately-invoked function expressions (also known as IIFEs) such as `var x = (function () {})();` and `var x = (function () {}());` to avoid conflicts with the [wrap-iife](wrap-iife) rule * arrow function arguments to avoid conflicts with the [arrow-parens](arrow-parens) rule +Problems reported by this rule can be fixed automatically, except when removing the parentheses would create a new directive, because that could change the semantics of the code. +For example, the following script prints `object` to the console, but if the parentheses around `"use strict"` were removed, it would print `undefined` instead. + +```js + + +("use strict"); + +function test() { + console.log(typeof this); +} + +test(); +``` + +In this case, the rule will not try to remove the parentheses around `"use strict"` but will still report them as a problem. + ## Options This rule has a string option: diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js index 0f59d7dd3825..e9080120fab0 100644 --- a/lib/rules/no-extra-parens.js +++ b/lib/rules/no-extra-parens.js @@ -386,6 +386,30 @@ module.exports = { return node && (node.type === "Identifier" || node.type === "MemberExpression"); } + /** + * Checks if a node is fixable. + * A node is fixable if removing a single pair of surrounding parentheses does not turn it + * into a directive after fixing other nodes. + * Almost all nodes are fixable, except if all of the following conditions are met: + * The node is a string Literal + * It has a single pair of parentheses + * It is the only child of an ExpressionStatement + * @param {ASTNode} node The node to evaluate. + * @returns {boolean} Whether or not the node is fixable. + * @private + */ + function isFixable(node) { + + // if it's not a string literal it can be autofixed + if (node.type !== "Literal" || typeof node.value !== "string") { + return true; + } + if (isParenthesisedTwice(node)) { + return true; + } + return !astUtils.isTopLevelExpressionStatement(node.parent); + } + /** * Report the node * @param {ASTNode} node node to evaluate @@ -429,14 +453,16 @@ module.exports = { node, loc: leftParenToken.loc, messageId: "unexpected", - fix(fixer) { - const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]); - - return fixer.replaceTextRange([ - leftParenToken.range[0], - rightParenToken.range[1] - ], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : "")); - } + fix: isFixable(node) + ? fixer => { + const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]); + + return fixer.replaceTextRange([ + leftParenToken.range[0], + rightParenToken.range[1] + ], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : "")); + } + : null }); } diff --git a/lib/rules/no-unused-expressions.js b/lib/rules/no-unused-expressions.js index 8337c19487ef..46bb7baac223 100644 --- a/lib/rules/no-unused-expressions.js +++ b/lib/rules/no-unused-expressions.js @@ -4,6 +4,8 @@ */ "use strict"; +const astUtils = require("./utils/ast-utils"); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -112,8 +114,6 @@ module.exports = { * @returns {boolean} whether the given node is considered a directive in its current position */ function isDirective(node) { - const parent = node.parent, - grandparent = parent.parent; /** * https://tc39.es/ecma262/#directive-prologue @@ -121,9 +121,7 @@ module.exports = { * Only `FunctionBody`, `ScriptBody` and `ModuleBody` can have directive prologue. * Class static blocks do not have directive prologue. */ - return (parent.type === "Program" || parent.type === "BlockStatement" && - (/Function/u.test(grandparent.type))) && - directives(parent).includes(node); + return astUtils.isTopLevelExpressionStatement(node) && directives(node.parent).includes(node); } /** diff --git a/lib/rules/padding-line-between-statements.js b/lib/rules/padding-line-between-statements.js index be7e2e404885..6b165c07f273 100644 --- a/lib/rules/padding-line-between-statements.js +++ b/lib/rules/padding-line-between-statements.js @@ -138,14 +138,7 @@ function isBlockLikeStatement(sourceCode, node) { */ function isDirective(node, sourceCode) { return ( - node.type === "ExpressionStatement" && - ( - node.parent.type === "Program" || - ( - node.parent.type === "BlockStatement" && - astUtils.isFunction(node.parent.parent) - ) - ) && + astUtils.isTopLevelExpressionStatement(node) && node.expression.type === "Literal" && typeof node.expression.value === "string" && !astUtils.isParenthesised(sourceCode, node.expression) diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index f4a18cff7832..aed2c42f9a3f 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -986,6 +986,25 @@ function isConstant(scope, node, inBooleanPosition) { return false; } +/** + * Checks whether a node is an ExpressionStatement at the top level of a file or function body. + * A top-level ExpressionStatement node is a directive if it contains a single unparenthesized + * string literal and if it occurs either as the first sibling or immediately after another + * directive. + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether or not the node is an ExpressionStatement at the top level of a + * file or function body. + */ +function isTopLevelExpressionStatement(node) { + if (node.type !== "ExpressionStatement") { + return false; + } + const parent = node.parent; + + return parent.type === "Program" || (parent.type === "BlockStatement" && isFunction(parent.parent)); + +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -1501,7 +1520,6 @@ module.exports = { return directives; }, - /** * Determines whether this node is a decimal integer literal. If a node is a decimal integer literal, a dot added * after the node will be parsed as a decimal point, rather than a property-access dot. @@ -2120,5 +2138,6 @@ module.exports = { isLogicalAssignmentOperator, getSwitchCaseColonToken, getModuleExportName, - isConstant + isConstant, + isTopLevelExpressionStatement }; diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js index c51a47cfd3fb..71e1e2e98457 100644 --- a/tests/lib/rules/no-extra-parens.js +++ b/tests/lib/rules/no-extra-parens.js @@ -3444,6 +3444,24 @@ ruleTester.run("no-extra-parens", rule, { `a ${operator} function () {};`, "Identifier" ) - ) + ), + + // Potential directives (no autofix) + invalid("('use strict');", null), + invalid("function f() { ('abc'); }", null), + invalid("(function () { ('abc'); })();", null), + invalid("_ = () => { ('abc'); };", null), + invalid("'use strict';(\"foobar\");", null), + invalid("foo(); ('bar');", null), + invalid("foo = { bar() { ; (\"baz\"); } };", null), + + // Directive lookalikes + invalid("(12345);", "12345;"), + invalid("(('foobar'));", "('foobar');"), + invalid("(`foobar`);", "`foobar`;"), + invalid("void ('foobar');", "void 'foobar';"), + invalid("_ = () => ('abc');", "_ = () => 'abc';"), + invalid("if (foo) ('bar');", "if (foo) 'bar';"), + invalid("const foo = () => ('bar');", "const foo = () => 'bar';") ] }); diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js index c2a9201ac73d..b0b9cb57cdf8 100644 --- a/tests/lib/rules/utils/ast-utils.js +++ b/tests/lib/rules/utils/ast-utils.js @@ -1802,4 +1802,59 @@ describe("ast-utils", () => { }); }); }); + + describe("isTopLevelExpressionStatement", () => { + it("should return false for a Program node", () => { + const node = { type: "Program", parent: null }; + + assert.strictEqual(astUtils.isTopLevelExpressionStatement(node), false); + }); + + it("should return false if the node is not an ExpressionStatement", () => { + linter.defineRule("checker", { + create: mustCall(() => ({ + ":expression": mustCall(node => { + assert.strictEqual(astUtils.isTopLevelExpressionStatement(node), false); + }) + })) + }); + + linter.verify("var foo = () => \"use strict\";", { rules: { checker: "error" }, parserOptions: { ecmaVersion: 2022 } }); + }); + + const expectedResults = [ + ["if (foo) { \"use strict\"; }", "\"use strict\";", false], + ["{ \"use strict\"; }", "\"use strict\";", false], + ["switch (foo) { case bar: \"use strict\"; }", "\"use strict\";", false], + ["foo; bar;", "foo;", true], + ["foo; bar;", "bar;", true], + ["function foo() { bar; }", "bar;", true], + ["var foo = function () { foo(); };", "foo();", true], + ["var foo = () => { 'bar'; }", "'bar';", true], + ["\"use strict\"", "\"use strict\"", true], + ["(`use strict`)", "(`use strict`)", true] + ]; + + expectedResults.forEach(([code, nodeText, expectedRetVal]) => { + it(`should return ${expectedRetVal} for \`${nodeText}\` in \`${code}\``, () => { + linter.defineRule("checker", { + create: mustCall(context => { + const assertForNode = mustCall( + node => assert.strictEqual(astUtils.isTopLevelExpressionStatement(node), expectedRetVal) + ); + + return ({ + ExpressionStatement(node) { + if (context.sourceCode.getText(node) === nodeText) { + assertForNode(node); + } + } + }); + }) + }); + + linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 2022 } }); + }); + }); + }); }); From 503647a0b94ca8c776d7e7e8c54c8b1d32904467 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 13 Jun 2023 12:21:59 -0400 Subject: [PATCH 038/248] docs: Resubmit markVariableAsUsed docs (#17280) --- docs/src/extend/custom-rules.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index cd3c6663306b..52cff2103d3d 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -148,7 +148,7 @@ Additionally, the `context` object has the following methods: * `getPhysicalFilename()`: When linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `โ€”stdin-filename` or `` if not specified. * `getScope()`: (**Deprecated:** Use `SourceCode#getScope(node)` instead.) Returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. * `getSourceCode()`: Returns a `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). -* `markVariableAsUsed(name)`: Marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. +* `markVariableAsUsed(name)` - (**Deprecated:** Use `SourceCode#markVariableAsUsed(name, node)` instead.) Marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. * `report(descriptor)`. Reports a problem in the code (see the [dedicated section](#reporting-problems)). **Note:** Earlier versions of ESLint supported additional methods on the `context` object. Those methods were removed in the new format and should not be relied upon. @@ -696,6 +696,36 @@ For examples of using `SourceCode#getScope()` to track variables, refer to the s * [no-shadow](https://github.com/eslint/eslint/blob/main/lib/rules/no-shadow.js): Calls `sourceCode.getScope()` at the `Program` node and inspects all child scopes to make sure a variable name is not reused at a lower scope. ([no-shadow](../rules/no-shadow) documentation) * [no-redeclare](https://github.com/eslint/eslint/blob/main/lib/rules/no-redeclare.js): Calls `sourceCode.getScope()` at each scope to make sure that a variable is not declared twice in the same scope. ([no-redeclare](../rules/no-redeclare) documentation) +### Marking Variables as Used + +**Deprecated:** The `context.markVariableAsUsed()` method is deprecated in favor of `sourceCode.markVariableAsUsed()`. + +Certain ESLint rules, such as [`no-unused-vars`](../rules/no-unused-vars), check to see if a variable has been used. ESLint itself only knows about the standard rules of variable access and so custom ways of accessing variables may not register as "used". + +To help with this, you can use the `sourceCode.markVariableAsUsed()` method. This method takes two arguments: the name of the variable to mark as used and an option reference node indicating the scope in which you are working. Here's an example: + +```js +module.exports = { + create: function(context) { + var sourceCode = context.getSourceCode(); + + return { + ReturnStatement(node) { + + // look in the scope of the function for myCustomVar and mark as used + sourceCode.markVariableAsUsed("myCustomVar", node); + + // or: look in the global scope for myCustomVar and mark as used + sourceCode.markVariableAsUsed("myCustomVar"); + } + } + // ... + } +}; +``` + +Here, the `myCustomVar` variable is marked as used relative to a `ReturnStatement` node, which means ESLint will start searching from the scope closest to that node. If you omit the second argument, then the top-level scope is used. (For ESM files, the top-level scope is the module scope; for CommonJS files, the top-level scope is the first function scope.) + ### Accessing Code Paths ESLint analyzes code paths while traversing AST. You can access code path objects with five events related to code paths. For more information, refer to [Code Path Analysis](code-path-analysis). From 9e3d77cba65d0e38e07996e57961fb04f30d9303 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 14 Jun 2023 04:02:31 +0200 Subject: [PATCH 039/248] docs: Resubmit Fix formatting in Custom Rules docs (#17281) --- docs/src/extend/custom-rules.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index 52cff2103d3d..1fe23d97ce14 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -132,9 +132,9 @@ The `context` object has the following properties: Additionally, the `context` object has the following methods: -* `getAncestors()` - (**Deprecated:** Use `SourceCode#getAncestors(node)` instead.) Returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. -* `getCwd()` - Returns the `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. -* `getDeclaredVariables(node)` - (**Deprecated:** Use `SourceCode#getDeclaredVariables(node)` instead.) Returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. +* `getAncestors()`: (**Deprecated:** Use `SourceCode#getAncestors(node)` instead.) Returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. +* `getCwd()`: Returns the `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. +* `getDeclaredVariables(node)`: (**Deprecated:** Use `SourceCode#getDeclaredVariables(node)` instead.) Returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. * If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. * If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. * If the node is a `FunctionDeclaration` or `FunctionExpression`, the variable for the function name is returned, in addition to variables for the function parameters. @@ -148,7 +148,7 @@ Additionally, the `context` object has the following methods: * `getPhysicalFilename()`: When linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `โ€”stdin-filename` or `` if not specified. * `getScope()`: (**Deprecated:** Use `SourceCode#getScope(node)` instead.) Returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. * `getSourceCode()`: Returns a `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). -* `markVariableAsUsed(name)` - (**Deprecated:** Use `SourceCode#markVariableAsUsed(name, node)` instead.) Marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. +* `markVariableAsUsed(name)`: (**Deprecated:** Use `SourceCode#markVariableAsUsed(name, node)` instead.) Marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. * `report(descriptor)`. Reports a problem in the code (see the [dedicated section](#reporting-problems)). **Note:** Earlier versions of ESLint supported additional methods on the `context` object. Those methods were removed in the new format and should not be relied upon. @@ -257,7 +257,7 @@ var rule = require("../../../lib/rules/avoid-name"); var RuleTester = require("eslint").RuleTester; var ruleTester = new RuleTester(); -ruleTester.run("my-rule", rule, { +ruleTester.run("avoid-name", rule, { valid: ["bar", "baz"], invalid: [ { @@ -318,22 +318,21 @@ Best practices for fixes: 1. Make fixes as small as possible. Fixes that are unnecessarily large could conflict with other fixes, and prevent them from being applied. 1. Only make one fix per message. This is enforced because you must return the result of the fixer operation from `fix()`. 1. Since all rules are run again after the initial round of fixes is applied, it's not necessary for a rule to check whether the code style of a fix will cause errors to be reported by another rule. + * For example, suppose a fixer would like to surround an object key with quotes, but it's not sure whether the user would prefer single or double quotes. -* For example, suppose a fixer would like to surround an object key with quotes, but it's not sure whether the user would prefer single or double quotes. + ```js + ({ foo : 1 }) - ```js - ({ foo : 1 }) + // should get fixed to either - // should get fixed to either + ({ 'foo': 1 }) - ({ 'foo': 1 }) + // or - // or + ({ "foo": 1 }) + ``` - ({ "foo": 1 }) - ``` - -* This fixer can just select a quote type arbitrarily. If it guesses wrong, the resulting code will be automatically reported and fixed by the [`quotes`](../rules/quotes) rule. + * This fixer can just select a quote type arbitrarily. If it guesses wrong, the resulting code will be automatically reported and fixed by the [`quotes`](../rules/quotes) rule. Note: Making fixes as small as possible is a best practice, but in some cases it may be correct to extend the range of the fix in order to intentionally prevent other rules from making fixes in a surrounding range in the same pass. For instance, if replacement text declares a new variable, it can be useful to prevent other changes in the scope of the variable as they might cause name collisions. From 5338b56fda7f47d16bdb23514f1e95b24de7b92f Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 14 Jun 2023 19:17:23 +0200 Subject: [PATCH 040/248] fix: normalize `cwd` passed to `ESLint`/`FlatESLint` constructor (#17277) Fixes #17042 --- lib/eslint/eslint-helpers.js | 2 +- lib/eslint/eslint.js | 2 +- tests/fixtures/example-app3/.eslintignore | 1 + tests/fixtures/example-app3/.eslintrc.js | 9 +++ tests/fixtures/example-app3/eslint.config.js | 10 +++ .../eslint-formatter-cwd/index.js | 1 + .../eslint-formatter-cwd/package.json | 1 + .../node_modules/eslint-plugin-test/index.js | 19 +++++ .../eslint-plugin-test/package.json | 1 + tests/fixtures/example-app3/src/1.js | 1 + tests/fixtures/example-app3/src/dist/2.js | 1 + tests/lib/eslint/eslint.js | 68 ++++++++++++++++++ tests/lib/eslint/flat-eslint.js | 72 +++++++++++++++++++ 13 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/example-app3/.eslintignore create mode 100644 tests/fixtures/example-app3/.eslintrc.js create mode 100644 tests/fixtures/example-app3/eslint.config.js create mode 100644 tests/fixtures/example-app3/node_modules/eslint-formatter-cwd/index.js create mode 100644 tests/fixtures/example-app3/node_modules/eslint-formatter-cwd/package.json create mode 100644 tests/fixtures/example-app3/node_modules/eslint-plugin-test/index.js create mode 100644 tests/fixtures/example-app3/node_modules/eslint-plugin-test/package.json create mode 100644 tests/fixtures/example-app3/src/1.js create mode 100644 tests/fixtures/example-app3/src/dist/2.js diff --git a/lib/eslint/eslint-helpers.js b/lib/eslint/eslint-helpers.js index 9bfd751cb2b6..e25b10e8bc4e 100644 --- a/lib/eslint/eslint-helpers.js +++ b/lib/eslint/eslint-helpers.js @@ -795,7 +795,7 @@ function processOptions({ // when overrideConfigFile is true that means don't do config file lookup configFile: overrideConfigFile === true ? false : overrideConfigFile, overrideConfig, - cwd, + cwd: path.normalize(cwd), errorOnUnmatchedPattern, fix, fixTypes, diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index e1d2116944e2..15e6b3dee413 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -289,7 +289,7 @@ function processOptions({ cacheLocation, cacheStrategy, configFile: overrideConfigFile, - cwd, + cwd: path.normalize(cwd), errorOnUnmatchedPattern, extensions, fix, diff --git a/tests/fixtures/example-app3/.eslintignore b/tests/fixtures/example-app3/.eslintignore new file mode 100644 index 000000000000..e59996451602 --- /dev/null +++ b/tests/fixtures/example-app3/.eslintignore @@ -0,0 +1 @@ +src/dist diff --git a/tests/fixtures/example-app3/.eslintrc.js b/tests/fixtures/example-app3/.eslintrc.js new file mode 100644 index 000000000000..86a595d362c9 --- /dev/null +++ b/tests/fixtures/example-app3/.eslintrc.js @@ -0,0 +1,9 @@ +module.exports = { + parserOptions: { + ecmaVersion: 2020, + }, + 'root': true, + 'rules': { + 'semi': 'error', + }, + }; diff --git a/tests/fixtures/example-app3/eslint.config.js b/tests/fixtures/example-app3/eslint.config.js new file mode 100644 index 000000000000..7f94a016cdc4 --- /dev/null +++ b/tests/fixtures/example-app3/eslint.config.js @@ -0,0 +1,10 @@ +module.exports = [ + { + ignores: ["src/dist"] + }, + { + rules: { + semi: "error" + } + } +]; diff --git a/tests/fixtures/example-app3/node_modules/eslint-formatter-cwd/index.js b/tests/fixtures/example-app3/node_modules/eslint-formatter-cwd/index.js new file mode 100644 index 000000000000..ca853b3a44d6 --- /dev/null +++ b/tests/fixtures/example-app3/node_modules/eslint-formatter-cwd/index.js @@ -0,0 +1 @@ +module.exports = (_, { cwd }) => cwd; diff --git a/tests/fixtures/example-app3/node_modules/eslint-formatter-cwd/package.json b/tests/fixtures/example-app3/node_modules/eslint-formatter-cwd/package.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/tests/fixtures/example-app3/node_modules/eslint-formatter-cwd/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/fixtures/example-app3/node_modules/eslint-plugin-test/index.js b/tests/fixtures/example-app3/node_modules/eslint-plugin-test/index.js new file mode 100644 index 000000000000..50d288c61017 --- /dev/null +++ b/tests/fixtures/example-app3/node_modules/eslint-plugin-test/index.js @@ -0,0 +1,19 @@ +module.exports = { + rules: { + "report-cwd": { + meta: { + schema: [] + }, + create(context) { + return { + Program(node) { + context.report({ + node, + message: context.cwd + }); + } + } + } + } + } +} diff --git a/tests/fixtures/example-app3/node_modules/eslint-plugin-test/package.json b/tests/fixtures/example-app3/node_modules/eslint-plugin-test/package.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/tests/fixtures/example-app3/node_modules/eslint-plugin-test/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/fixtures/example-app3/src/1.js b/tests/fixtures/example-app3/src/1.js new file mode 100644 index 000000000000..e14c4f2a9561 --- /dev/null +++ b/tests/fixtures/example-app3/src/1.js @@ -0,0 +1 @@ +console.log(1) diff --git a/tests/fixtures/example-app3/src/dist/2.js b/tests/fixtures/example-app3/src/dist/2.js new file mode 100644 index 000000000000..32926e25ddb3 --- /dev/null +++ b/tests/fixtures/example-app3/src/dist/2.js @@ -0,0 +1 @@ +console.log(2) diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index a422f4b867f5..cb43823fbc5f 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -126,6 +126,28 @@ describe("ESLint", () => { } }); + it("should normalize 'options.cwd'.", async () => { + const cwd = getFixturePath("example-app3"); + const engine = new ESLint({ + cwd: `${cwd}${path.sep}foo${path.sep}..`, // `/foo/..` should be normalized to `` + useEslintrc: false, + overrideConfig: { + plugins: ["test"], + rules: { + "test/report-cwd": "error" + } + } + }); + const results = await engine.lintText(""); + + assert.strictEqual(results[0].messages[0].ruleId, "test/report-cwd"); + assert.strictEqual(results[0].messages[0].message, cwd); + + const formatter = await engine.loadFormatter("cwd"); + + assert.strictEqual(formatter.format(results), cwd); + }); + it("should report one fatal message when given a path by --ignore-path that is not a file when ignore is true.", () => { assert.throws(() => { // eslint-disable-next-line no-new -- Check for throwing @@ -6945,4 +6967,50 @@ describe("ESLint", () => { }); }); }); + + // only works on a Windows machine + if (os.platform() === "win32") { + + // https://github.com/eslint/eslint/issues/17042 + describe("with cwd that is using forward slash on Windows", () => { + const cwd = getFixturePath("example-app3"); + const cwdForwardSlash = cwd.replace(/\\/gu, "/"); + + it("should correctly handle ignore patterns", async () => { + const engine = new ESLint({ cwd: cwdForwardSlash }); + const results = await engine.lintFiles(["./src"]); + + // src/dist/2.js should be ignored + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, path.join(cwd, "src\\1.js")); + }); + + it("should pass cwd with backslashes to rules", async () => { + const engine = new ESLint({ + cwd: cwdForwardSlash, + useEslintrc: false, + overrideConfig: { + plugins: ["test"], + rules: { + "test/report-cwd": "error" + } + } + }); + const results = await engine.lintText(""); + + assert.strictEqual(results[0].messages[0].ruleId, "test/report-cwd"); + assert.strictEqual(results[0].messages[0].message, cwd); + }); + + it("should pass cwd with backslashes to formatters", async () => { + const engine = new ESLint({ + cwd: cwdForwardSlash + }); + const results = await engine.lintText(""); + const formatter = await engine.loadFormatter("cwd"); + + assert.strictEqual(formatter.format(results), cwd); + }); + }); + } }); diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index ffad0a793ea4..0a692e9e7f96 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -140,6 +140,30 @@ describe("FlatESLint", () => { } }); + it("should normalize 'options.cwd'.", async () => { + const cwd = getFixturePath("example-app3"); + const engine = new FlatESLint({ + cwd: `${cwd}${path.sep}foo${path.sep}..`, // `/foo/..` should be normalized to `` + overrideConfigFile: true, + overrideConfig: { + plugins: { + test: require(path.join(cwd, "node_modules", "eslint-plugin-test")) + }, + rules: { + "test/report-cwd": "error" + } + } + }); + const results = await engine.lintText(""); + + assert.strictEqual(results[0].messages[0].ruleId, "test/report-cwd"); + assert.strictEqual(results[0].messages[0].message, cwd); + + const formatter = await engine.loadFormatter("cwd"); + + assert.strictEqual(formatter.format(results), cwd); + }); + // https://github.com/eslint/eslint/issues/2380 it("should not modify baseConfig when format is specified", () => { const customBaseConfig = { root: true }; @@ -5701,6 +5725,54 @@ describe("FlatESLint", () => { }); }); + // only works on a Windows machine + if (os.platform() === "win32") { + + // https://github.com/eslint/eslint/issues/17042 + describe("with cwd that is using forward slash on Windows", () => { + const cwd = getFixturePath("example-app3"); + const cwdForwardSlash = cwd.replace(/\\/gu, "/"); + + it("should correctly handle ignore patterns", async () => { + const engine = new FlatESLint({ cwd: cwdForwardSlash }); + const results = await engine.lintFiles(["./src"]); + + // src/dist/2.js should be ignored + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, path.join(cwd, "src\\1.js")); + }); + + it("should pass cwd with backslashes to rules", async () => { + const engine = new FlatESLint({ + cwd: cwdForwardSlash, + overrideConfigFile: true, + overrideConfig: { + plugins: { + test: require(path.join(cwd, "node_modules", "eslint-plugin-test")) + }, + rules: { + "test/report-cwd": "error" + } + } + }); + const results = await engine.lintText(""); + + assert.strictEqual(results[0].messages[0].ruleId, "test/report-cwd"); + assert.strictEqual(results[0].messages[0].message, cwd); + }); + + it("should pass cwd with backslashes to formatters", async () => { + const engine = new FlatESLint({ + cwd: cwdForwardSlash + }); + const results = await engine.lintText(""); + const formatter = await engine.loadFormatter("cwd"); + + assert.strictEqual(formatter.format(results), cwd); + }); + }); + } + }); describe("shouldUseFlatConfig", () => { From 67e7af3fdbdb4648b747dfd669be4decfe24086a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Fri, 16 Jun 2023 13:45:29 +0800 Subject: [PATCH 041/248] docs: resubmit custom-rules doc changes (#17294) credits: https://github.com/snitin315 Refs: * https://github.com/eslint/eslint/issues/17225 * https://github.com/eslint/eslint/pull/17108 * https://github.com/eslint/eslint/pull/17106 * https://github.com/eslint/eslint/pull/17111 * https://github.com/eslint/eslint/pull/17107 --- docs/src/extend/code-path-analysis.md | 3 +-- docs/src/extend/custom-rules.md | 20 +++++++++++++------- docs/src/integrate/nodejs-api.md | 4 ++-- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/docs/src/extend/code-path-analysis.md b/docs/src/extend/code-path-analysis.md index a2caeff8ea92..7344f8647adf 100644 --- a/docs/src/extend/code-path-analysis.md +++ b/docs/src/extend/code-path-analysis.md @@ -259,8 +259,7 @@ Please use a map of information instead. ```js function hasCb(node, context) { if (node.type.indexOf("Function") !== -1) { - const sourceCode = context.getSourceCode(); - + const sourceCode = context.sourceCode; return sourceCode.getDeclaredVariables(node).some(function(v) { return v.type === "Parameter" && v.name === "cb"; }); diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index 1fe23d97ce14..3ba8845b9542 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -124,7 +124,11 @@ As the name implies, the `context` object contains information that is relevant The `context` object has the following properties: * `id`: (`string`) The rule ID. +* `filename`: (`string`) The filename associated with the source. +* `physicalFilename`: (`string`) When linting a file, it provides the full path of the file on disk without any code block information. When linting text, it provides the value passed to `โ€”stdin-filename` or `` if not specified. +* `cwd`: (`string`) The `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. * `options`: (`array`) An array of the [configured options](../use/configure/rules) for this rule. This array does not include the rule severity (see the [dedicated section](#accessing-options-passed-to-a-rule)). +* `sourceCode`: (`object`) A `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). * `settings`: (`object`) The [shared settings](../use/configure/configuration-files#adding-shared-settings) from the configuration. * `parserPath`: (`string`) The name of the `parser` from the configuration. * `parserServices`: (`object`) Contains parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) @@ -133,7 +137,7 @@ The `context` object has the following properties: Additionally, the `context` object has the following methods: * `getAncestors()`: (**Deprecated:** Use `SourceCode#getAncestors(node)` instead.) Returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. -* `getCwd()`: Returns the `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. +* `getCwd()`: (**Deprecated:** Use `context.cwd` instead.) Returns the `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. * `getDeclaredVariables(node)`: (**Deprecated:** Use `SourceCode#getDeclaredVariables(node)` instead.) Returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. * If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. * If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. @@ -144,10 +148,10 @@ Additionally, the `context` object has the following methods: * If the node is an `ImportDeclaration`, variables for all of its specifiers are returned. * If the node is an `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`, the declared variable is returned. * Otherwise, if the node does not declare any variables, an empty array is returned. -* `getFilename()`: Returns the filename associated with the source. -* `getPhysicalFilename()`: When linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `โ€”stdin-filename` or `` if not specified. +* `getFilename()`: (**Deprecated:** Use `context.filename` instead.) Returns the filename associated with the source. +* `getPhysicalFilename()`: (**Deprecated:** Use `context.physicalFilename` instead.) When linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `โ€”stdin-filename` or `` if not specified. * `getScope()`: (**Deprecated:** Use `SourceCode#getScope(node)` instead.) Returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. -* `getSourceCode()`: Returns a `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). +* `getSourceCode()`: (**Deprecated:** Use `context.sourceCode` instead.) Returns a `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). * `markVariableAsUsed(name)`: (**Deprecated:** Use `SourceCode#markVariableAsUsed(name, node)` instead.) Marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. * `report(descriptor)`. Reports a problem in the code (see the [dedicated section](#reporting-problems)). @@ -506,18 +510,20 @@ When using options, make sure that your rule has some logical defaults in case t ### Accessing the Source Code -The `SourceCode` object is the main object for getting more information about the source code being linted. You can retrieve the `SourceCode` object at any time by using the `context.getSourceCode()` method: +The `SourceCode` object is the main object for getting more information about the source code being linted. You can retrieve the `SourceCode` object at any time by using the `context.sourceCode` property: ```js module.exports = { create: function(context) { - var sourceCode = context.getSourceCode(); + var sourceCode = context.sourceCode; // ... } }; ``` +**Deprecated:** The `context.getSourceCode()` method is deprecated; make sure to use `context.sourceCode` property instead. + Once you have an instance of `SourceCode`, you can use the following methods on it to work with the code: * `getText(node)`: Returns the source code for the given node. Omit `node` to get the whole source (see the [dedicated section](#accessing-the-source-text)). @@ -706,7 +712,7 @@ To help with this, you can use the `sourceCode.markVariableAsUsed()` method. Thi ```js module.exports = { create: function(context) { - var sourceCode = context.getSourceCode(); + var sourceCode = context.sourceCode; return { ReturnStatement(node) { diff --git a/docs/src/integrate/nodejs-api.md b/docs/src/integrate/nodejs-api.md index de035ca731c6..853abdc57b8d 100644 --- a/docs/src/integrate/nodejs-api.md +++ b/docs/src/integrate/nodejs-api.md @@ -510,7 +510,7 @@ The `Linter` object does the actual evaluation of the JavaScript code. It doesn' The `Linter` is a constructor, and you can create a new instance by passing in the options you want to use. The available options are: -* `cwd` - Path to a directory that should be considered as the current working directory. It is accessible to rules by calling `context.getCwd()` (see [The Context Object](../extend/custom-rules#the-context-object)). If `cwd` is `undefined`, it will be normalized to `process.cwd()` if the global `process` object is defined (for example, in the Node.js runtime) , or `undefined` otherwise. +* `cwd` - Path to a directory that should be considered as the current working directory. It is accessible to rules from `context.cwd` or by calling `context.getCwd()` (see [The Context Object](../extend/custom-rules#the-context-object)). If `cwd` is `undefined`, it will be normalized to `process.cwd()` if the global `process` object is defined (for example, in the Node.js runtime) , or `undefined` otherwise. For example: @@ -520,7 +520,7 @@ const linter1 = new Linter({ cwd: 'path/to/project' }); const linter2 = new Linter(); ``` -In this example, rules run on `linter1` will get `path/to/project` when calling `context.getCwd()`. +In this example, rules run on `linter1` will get `path/to/project` from `context.cwd` or when calling `context.getCwd()`. Those run on `linter2` will get `process.cwd()` if the global `process` object is defined or `undefined` otherwise (e.g. on the browser ). ### Linter#verify From 372722eac32ca9e3f31cf0d0bc10317c6f153369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Sat, 17 Jun 2023 00:27:01 +0800 Subject: [PATCH 042/248] docs: resubmit pr17012 doc changes (#17293) credits: https://github.com/alundiak Refs: * https://github.com/eslint/eslint/issues/17225 * https://github.com/eslint/eslint/pull/17012 --- docs/src/rules/semi.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/rules/semi.md b/docs/src/rules/semi.md index eca93a747002..6fd05501cd1f 100644 --- a/docs/src/rules/semi.md +++ b/docs/src/rules/semi.md @@ -76,7 +76,7 @@ This rule has two options, a string option and an object option. String option: * `"always"` (default) requires semicolons at the end of statements -* `"never"` disallows semicolons as the end of statements (except to disambiguate statements beginning with `[`, `(`, `/`, `+`, or `-`) +* `"never"` disallows semicolons at the end of statements (except to disambiguate statements beginning with `[`, `(`, `/`, `+`, or `-`) Object option (when `"always"`): @@ -87,7 +87,7 @@ Object option (when `"never"`): * `"beforeStatementContinuationChars": "any"` (default) ignores semicolons (or lacking semicolon) at the end of statements if the next line starts with `[`, `(`, `/`, `+`, or `-`. * `"beforeStatementContinuationChars": "always"` requires semicolons at the end of statements if the next line starts with `[`, `(`, `/`, `+`, or `-`. -* `"beforeStatementContinuationChars": "never"` disallows semicolons as the end of statements if it doesn't make ASI hazard even if the next line starts with `[`, `(`, `/`, `+`, or `-`. +* `"beforeStatementContinuationChars": "never"` disallows semicolons at the end of statements if it doesn't make ASI hazard even if the next line starts with `[`, `(`, `/`, `+`, or `-`. **Note:** `beforeStatementContinuationChars` does not apply to class fields because class fields are not statements. From 14581ff15aaee5a55c46bbf4983818ddc8dd7cb1 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 16 Jun 2023 21:19:08 +0200 Subject: [PATCH 043/248] feat: directive prologue detection and autofix condition in `quotes` (#17284) * feat: directive prologue detection and autofix condition in `quotes` * Update documentation * Update docs/src/rules/quotes.md Co-authored-by: Nicholas C. Zakas --------- Co-authored-by: Nicholas C. Zakas --- docs/src/rules/quotes.md | 6 +++- lib/rules/quotes.js | 28 +++++++++--------- tests/lib/rules/quotes.js | 60 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 77 insertions(+), 17 deletions(-) diff --git a/docs/src/rules/quotes.md b/docs/src/rules/quotes.md index 38d21b428f9e..1b876ebdd3ef 100644 --- a/docs/src/rules/quotes.md +++ b/docs/src/rules/quotes.md @@ -15,7 +15,7 @@ var single = 'single'; var backtick = `backtick`; // ES6 only ``` -Each of these lines creates a string and, in some cases, can be used interchangeably. The choice of how to define strings in a codebase is a stylistic one outside of template literals (which allow embedded of expressions to be interpreted). +Each of these lines creates a string and, in some cases, can be used interchangeably. The choice of how to define strings in a codebase is a stylistic one outside of template literals (which allow embedded expressions to be interpreted). Many codebases require strings to be defined in a consistent manner. @@ -23,6 +23,8 @@ Many codebases require strings to be defined in a consistent manner. This rule enforces the consistent use of either backticks, double, or single quotes. +This rule is aware of directive prologues such as `"use strict"` and will not flag or autofix them if doing so will change how the directive prologue is interpreted. + ## Options This rule has two options, a string option and an object option. @@ -125,7 +127,9 @@ Examples of **correct** code for this rule with the `"backtick"` option: /*eslint quotes: ["error", "backtick"]*/ /*eslint-env es6*/ +"use strict"; // directives must use single or double quotes var backtick = `backtick`; +var obj = { 'prop-name': `value` }; // backticks not allowed for property names ``` ::: diff --git a/lib/rules/quotes.js b/lib/rules/quotes.js index 9efb9809aa3d..6eda7c0b40ae 100644 --- a/lib/rules/quotes.js +++ b/lib/rules/quotes.js @@ -157,7 +157,8 @@ module.exports = { /** * Checks whether or not a given node is a directive. - * The directive is a `ExpressionStatement` which has only a string literal. + * The directive is a `ExpressionStatement` which has only a string literal not surrounded by + * parentheses. * @param {ASTNode} node A node to check. * @returns {boolean} Whether or not the node is a directive. * @private @@ -166,23 +167,23 @@ module.exports = { return ( node.type === "ExpressionStatement" && node.expression.type === "Literal" && - typeof node.expression.value === "string" + typeof node.expression.value === "string" && + !astUtils.isParenthesised(sourceCode, node.expression) ); } /** - * Checks whether or not a given node is a part of directive prologues. - * See also: http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive + * Checks whether a specified node is either part of, or immediately follows a (possibly empty) directive prologue. + * @see {@link http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive} * @param {ASTNode} node A node to check. - * @returns {boolean} Whether or not the node is a part of directive prologues. + * @returns {boolean} Whether a specified node is either part of, or immediately follows a (possibly empty) directive prologue. * @private */ - function isPartOfDirectivePrologue(node) { - const block = node.parent.parent; - - if (block.type !== "Program" && (block.type !== "BlockStatement" || !astUtils.isFunction(block.parent))) { + function isExpressionInOrJustAfterDirectivePrologue(node) { + if (!astUtils.isTopLevelExpressionStatement(node.parent)) { return false; } + const block = node.parent.parent; // Check the node is at a prologue. for (let i = 0; i < block.body.length; ++i) { @@ -212,7 +213,7 @@ module.exports = { // Directive Prologues. case "ExpressionStatement": - return isPartOfDirectivePrologue(node); + return !astUtils.isParenthesised(sourceCode, node) && isExpressionInOrJustAfterDirectivePrologue(node); // LiteralPropertyName. case "Property": @@ -328,12 +329,11 @@ module.exports = { description: settings.description }, fix(fixer) { - if (isPartOfDirectivePrologue(node)) { + if (astUtils.isTopLevelExpressionStatement(node.parent) && !astUtils.isParenthesised(sourceCode, node)) { /* - * TemplateLiterals in a directive prologue aren't actually directives, but if they're - * in the directive prologue, then fixing them might turn them into directives and change - * the behavior of the code. + * TemplateLiterals aren't actually directives, but fixing them might turn + * them into directives and change the behavior of the code. */ return null; } diff --git a/tests/lib/rules/quotes.js b/tests/lib/rules/quotes.js index d1c42b73e840..11ae90248b77 100644 --- a/tests/lib/rules/quotes.js +++ b/tests/lib/rules/quotes.js @@ -440,7 +440,7 @@ ruleTester.run("quotes", rule, { }, { code: "() => { foo(); `use strict`; }", - output: "() => { foo(); \"use strict\"; }", + output: null, // no autofix parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "wrongQuotes", @@ -450,7 +450,7 @@ ruleTester.run("quotes", rule, { }, { code: "foo(); `use strict`;", - output: "foo(); \"use strict\";", + output: null, // no autofix parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "wrongQuotes", @@ -725,6 +725,62 @@ ruleTester.run("quotes", rule, { type: "Literal" } ] + }, + + // https://github.com/eslint/eslint/pull/17022 + { + code: "() => { foo(); (`use strict`); }", + output: "() => { foo(); (\"use strict\"); }", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "wrongQuotes", + data: { description: "doublequote" }, + type: "TemplateLiteral" + }] + }, + { + code: "('foo'); \"bar\";", + output: "(`foo`); `bar`;", + options: ["backtick"], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "wrongQuotes", + data: { description: "backtick" }, + type: "Literal" + }, { + messageId: "wrongQuotes", + data: { description: "backtick" }, + type: "Literal" + }] + }, + { + code: "; 'use asm';", + output: "; \"use asm\";", + errors: [{ + messageId: "wrongQuotes", + data: { description: "doublequote" }, + type: "Literal" + }] + }, + { + code: "{ `foobar`; }", + output: "{ \"foobar\"; }", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "wrongQuotes", + data: { description: "doublequote" }, + type: "TemplateLiteral" + }] + }, + { + code: "foo(() => `bar`);", + output: "foo(() => \"bar\");", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "wrongQuotes", + data: { description: "doublequote" }, + type: "TemplateLiteral" + }] } ] }); From 62bf759124811b013ad7906c2536deb8b39c31a8 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 16 Jun 2023 17:03:41 -0400 Subject: [PATCH 044/248] 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 0dce8203fe56..3af35a6915a5 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "8.42.0", + "version": "8.43.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 8b855ea058992d5446d1d6dc6394ee683c3200a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Sat, 17 Jun 2023 05:18:06 +0800 Subject: [PATCH 045/248] docs: resubmit pr17061 doc changes (#17292) credits: https://github.com/voxpelli Refs: * https://github.com/eslint/eslint/issues/17225 * https://github.com/eslint/eslint/pull/17061 --- docs/src/extend/plugins.md | 2 +- docs/src/rules/callback-return.md | 2 +- docs/src/rules/global-require.md | 2 +- docs/src/rules/handle-callback-err.md | 2 +- docs/src/rules/no-buffer-constructor.md | 2 +- docs/src/rules/no-mixed-requires.md | 2 +- docs/src/rules/no-new-require.md | 2 +- docs/src/rules/no-path-concat.md | 2 +- docs/src/rules/no-process-env.md | 2 +- docs/src/rules/no-process-exit.md | 2 +- docs/src/rules/no-restricted-modules.md | 2 +- docs/src/rules/no-sync.md | 2 +- docs/src/use/configure/language-options.md | 4 ++-- docs/src/use/integrations.md | 21 +++------------------ docs/src/use/migrate-to-8.0.0.md | 8 ++++---- docs/src/use/migrating-to-7.0.0.md | 2 +- 16 files changed, 22 insertions(+), 37 deletions(-) diff --git a/docs/src/extend/plugins.md b/docs/src/extend/plugins.md index 9d7983e67f27..70bc3ee7c746 100644 --- a/docs/src/extend/plugins.md +++ b/docs/src/extend/plugins.md @@ -183,7 +183,7 @@ ESLint plugins should be linted too! It's suggested to lint your plugin with the * [eslint](https://www.npmjs.com/package/eslint) * [eslint-plugin-eslint-plugin](https://www.npmjs.com/package/eslint-plugin-eslint-plugin) -* [eslint-plugin-node](https://www.npmjs.com/package/eslint-plugin-node) +* [eslint-plugin-n](https://www.npmjs.com/package/eslint-plugin-n) ## Share Plugins diff --git a/docs/src/rules/callback-return.md b/docs/src/rules/callback-return.md index d3d621814a30..92ec02628f31 100644 --- a/docs/src/rules/callback-return.md +++ b/docs/src/rules/callback-return.md @@ -9,7 +9,7 @@ further_reading: --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). The callback pattern is at the heart of most I/O and event-driven programming in JavaScript. diff --git a/docs/src/rules/global-require.md b/docs/src/rules/global-require.md index 4a2d20a75d74..e960468b3807 100644 --- a/docs/src/rules/global-require.md +++ b/docs/src/rules/global-require.md @@ -4,7 +4,7 @@ rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). In Node.js, module dependencies are included using the `require()` function, such as: diff --git a/docs/src/rules/handle-callback-err.md b/docs/src/rules/handle-callback-err.md index fddce708b462..3f66ad2031ef 100644 --- a/docs/src/rules/handle-callback-err.md +++ b/docs/src/rules/handle-callback-err.md @@ -7,7 +7,7 @@ further_reading: --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). In Node.js, a common pattern for dealing with asynchronous behavior is called the callback pattern. This pattern expects an `Error` object or `null` as the first argument of the callback. diff --git a/docs/src/rules/no-buffer-constructor.md b/docs/src/rules/no-buffer-constructor.md index 59b9dd8772d3..97ea1918d0b2 100644 --- a/docs/src/rules/no-buffer-constructor.md +++ b/docs/src/rules/no-buffer-constructor.md @@ -8,7 +8,7 @@ further_reading: --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). In Node.js, the behavior of the `Buffer` constructor is different depending on the type of its argument. Passing an argument from user input to `Buffer()` without validating its type can lead to security vulnerabilities such as remote memory disclosure and denial of service. As a result, the `Buffer` constructor has been deprecated and should not be used. Use the producer methods `Buffer.from`, `Buffer.alloc`, and `Buffer.allocUnsafe` instead. diff --git a/docs/src/rules/no-mixed-requires.md b/docs/src/rules/no-mixed-requires.md index 3dcf6384fd46..f747ebd6362a 100644 --- a/docs/src/rules/no-mixed-requires.md +++ b/docs/src/rules/no-mixed-requires.md @@ -4,7 +4,7 @@ rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). In the Node.js community it is often customary to separate initializations with calls to `require` modules from other variable declarations, sometimes also grouping them by the type of module. This rule helps you enforce this convention. diff --git a/docs/src/rules/no-new-require.md b/docs/src/rules/no-new-require.md index 1aa663e6669d..494d4881da2f 100644 --- a/docs/src/rules/no-new-require.md +++ b/docs/src/rules/no-new-require.md @@ -4,7 +4,7 @@ rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). The `require` function is used to include modules that exist in separate files, such as: diff --git a/docs/src/rules/no-path-concat.md b/docs/src/rules/no-path-concat.md index 840b80c35207..1042f3179208 100644 --- a/docs/src/rules/no-path-concat.md +++ b/docs/src/rules/no-path-concat.md @@ -4,7 +4,7 @@ rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). In Node.js, the `__dirname` and `__filename` global variables contain the directory path and the file path of the currently executing script file, respectively. Sometimes, developers try to use these variables to create paths to other files, such as: diff --git a/docs/src/rules/no-process-env.md b/docs/src/rules/no-process-env.md index 2130a89443f5..99cd1f41a0e9 100644 --- a/docs/src/rules/no-process-env.md +++ b/docs/src/rules/no-process-env.md @@ -7,7 +7,7 @@ further_reading: --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). The `process.env` object in Node.js is used to store deployment/configuration parameters. Littering it through out a project could lead to maintenance issues as it's another kind of global dependency. As such, it could lead to merge conflicts in a multi-user setup and deployment issues in a multi-server setup. Instead, one of the best practices is to define all those parameters in a single configuration/settings file which could be accessed throughout the project. diff --git a/docs/src/rules/no-process-exit.md b/docs/src/rules/no-process-exit.md index 6928a0d4843a..90e312b2d452 100644 --- a/docs/src/rules/no-process-exit.md +++ b/docs/src/rules/no-process-exit.md @@ -4,7 +4,7 @@ rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). The `process.exit()` method in Node.js is used to immediately stop the Node.js process and exit. This is a dangerous operation because it can occur in any method at any point in time, potentially stopping a Node.js application completely when an error occurs. For example: diff --git a/docs/src/rules/no-restricted-modules.md b/docs/src/rules/no-restricted-modules.md index 3ed5bd861170..eb2ca03ffc86 100644 --- a/docs/src/rules/no-restricted-modules.md +++ b/docs/src/rules/no-restricted-modules.md @@ -4,7 +4,7 @@ rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). A module in Node.js is a simple or complex functionality organized in a JavaScript file which can be reused throughout the Node.js application. The keyword `require` is used in Node.js/CommonJS to import modules into an application. This way you can have dynamic loading where the loaded module name isn't predefined /static, or where you conditionally load a module only if it's "truly required". diff --git a/docs/src/rules/no-sync.md b/docs/src/rules/no-sync.md index 33fbec47fc04..254974411e43 100644 --- a/docs/src/rules/no-sync.md +++ b/docs/src/rules/no-sync.md @@ -4,7 +4,7 @@ rule_type: suggestion --- -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-node`](https://github.com/mysticatea/eslint-plugin-node). +This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). In Node.js, most I/O is done through asynchronous methods. However, there are often synchronous versions of the asynchronous methods. For example, `fs.exists()` and `fs.existsSync()`. In some contexts, using synchronous operations is okay (if, as with ESLint, you are writing a command line utility). However, in other contexts the use of synchronous operations is considered a bad practice that should be avoided. For example, if you are running a high-travel web server on Node.js, you should consider carefully if you want to allow any synchronous operations that could lock up the server. diff --git a/docs/src/use/configure/language-options.md b/docs/src/use/configure/language-options.md index ef21c35d57e4..4f8fa148bce8 100644 --- a/docs/src/use/configure/language-options.md +++ b/docs/src/use/configure/language-options.md @@ -27,7 +27,7 @@ An environment provides predefined global variables. The available environments * `es2021` - adds all ECMAScript 2021 globals and automatically sets the `ecmaVersion` parser option to 12. * `es2022` - adds all ECMAScript 2022 globals and automatically sets the `ecmaVersion` parser option to 13. * `worker` - web workers global variables. -* `amd` - defines `require()` and `define()` as global variables as per the [amd](https://github.com/amdjs/amdjs-api/wiki/AMD) spec. +* `amd` - defines `require()` and `define()` as global variables as per the [amd](https://github.com/amdjs/amdjs-api/blob/master/AMD.md) spec. * `mocha` - adds all of the Mocha testing global variables. * `jasmine` - adds all of the Jasmine testing global variables for version 1.3 and 2.0. * `jest` - Jest global variables. @@ -187,7 +187,7 @@ For historical reasons, the boolean value `false` and the string value `"readabl ESLint allows you to specify the JavaScript language options you want to support. By default, ESLint expects ECMAScript 5 syntax. You can override that setting to enable support for other ECMAScript versions and JSX using parser options. -Please note that supporting JSX syntax is not the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) if you are using React. +Please note that supporting JSX syntax is not the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) if you are using React. By the same token, supporting ES6 syntax is not the same as supporting new ES6 globals (e.g., new types such as `Set`). For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 global variables, use `{ "env": { "es6": true } }`. Setting `{ "env": { "es6": true } }` enables ES6 syntax automatically, but `{ "parserOptions": { "ecmaVersion": 6 } }` does not enable ES6 globals automatically. diff --git a/docs/src/use/integrations.md b/docs/src/use/integrations.md index 892a5080e359..427377ed7a05 100644 --- a/docs/src/use/integrations.md +++ b/docs/src/use/integrations.md @@ -15,10 +15,10 @@ If you would like to recommend an integration to be added to this page, [submit ## Editors * Sublime Text 3: - * [SublimeLinter-eslint](https://github.com/roadhump/SublimeLinter-eslint) + * [SublimeLinter-eslint](https://github.com/SublimeLinter/SublimeLinter-eslint) * [Build Next](https://github.com/albertosantini/sublimetext-buildnext) * Vim: - * [ALE](https://github.com/w0rp/ale) + * [ALE](https://github.com/dense-analysis/ale) * [Syntastic](https://github.com/vim-syntastic/syntastic/tree/master/syntax_checkers/javascript) * Emacs: [Flycheck](http://www.flycheck.org/) supports ESLint with the [javascript-eslint](http://www.flycheck.org/en/latest/languages.html#javascript) checker. * Eclipse Orion: ESLint is the [default linter](https://dev.eclipse.org/mhonarc/lists/orion-dev/msg02718.html) @@ -26,9 +26,6 @@ If you would like to recommend an integration to be added to this page, [submit * TextMate 2: * [eslint.tmbundle](https://github.com/ryanfitzer/eslint.tmbundle) * [javascript-eslint.tmbundle](https://github.com/natesilva/javascript-eslint.tmbundle) -* Atom: - * [linter-eslint](https://atom.io/packages/linter-eslint) - * [fast-eslint-8](https://atom.io/packages/fast-eslint-8) * IntelliJ IDEA, WebStorm, PhpStorm, PyCharm, RubyMine, and other JetBrains IDEs: [How to use ESLint](https://www.jetbrains.com/help/webstorm/eslint.html) * Visual Studio: [Linting JavaScript in VS](https://learn.microsoft.com/en-us/visualstudio/javascript/linting-javascript?view=vs-2022) * Visual Studio Code: [ESLint Extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) @@ -37,16 +34,8 @@ If you would like to recommend an integration to be added to this page, [submit ## Build tools * Grunt: [grunt-eslint](https://www.npmjs.com/package/grunt-eslint) -* Gulp: [gulp-eslint](https://www.npmjs.com/package/gulp-eslint) -* Mimosa: [mimosa-eslint](https://www.npmjs.com/package/mimosa-eslint) -* Broccoli: [broccoli-eslint](https://www.npmjs.com/package/broccoli-eslint) -* Browserify: [eslintify](https://www.npmjs.com/package/eslintify) * Webpack: [eslint-webpack-plugin](https://www.npmjs.com/package/eslint-webpack-plugin) * Rollup: [@rollup/plugin-eslint](https://www.npmjs.com/package/@rollup/plugin-eslint) -* Ember-cli: [ember-cli-eslint](https://www.npmjs.com/package/ember-cli-eslint) -* Sails.js: [sails-hook-lint](https://www.npmjs.com/package/sails-hook-lint), [sails-eslint](https://www.npmjs.com/package/sails-eslint) -* Start: [@start/plugin-lib-eslint](https://www.npmjs.com/package/@start/plugin-lib-eslint) -* Brunch: [eslint-brunch](https://www.npmjs.com/package/eslint-brunch) ## Command Line Tools @@ -59,11 +48,7 @@ If you would like to recommend an integration to be added to this page, [submit * [Git Precommit Hook](https://coderwall.com/p/zq8jlq/eslint-pre-commit-hook) * [Git pre-commit hook that only lints staged changes](https://gist.github.com/dahjelle/8ddedf0aebd488208a9a7c829f19b9e8) * [overcommit Git hook manager](https://github.com/brigade/overcommit) -* [Mega-Linter](https://nvuillam.github.io/mega-linter): Linters aggregator for CI, [embedding eslint](https://nvuillam.github.io/mega-linter/descriptors/javascript_eslint/) - -## Testing - -* Mocha.js: [mocha-eslint](https://www.npmjs.com/package/mocha-eslint) +* [Mega-Linter](https://megalinter.io/latest/): Linters aggregator for CI, [embedding eslint](https://megalinter.io/latest/descriptors/javascript_eslint/) ## Other Integration Lists diff --git a/docs/src/use/migrate-to-8.0.0.md b/docs/src/use/migrate-to-8.0.0.md index 2eaa942b91b2..9fac8cd597ae 100644 --- a/docs/src/use/migrate-to-8.0.0.md +++ b/docs/src/use/migrate-to-8.0.0.md @@ -55,7 +55,7 @@ Node.js 10, 13, 15 all reached end of life either in 2020 or early 2021. ESLint ESLint v8.0.0 has removed the `codeframe` and `table` formatters from the core. These formatters required dependencies that weren't used anywhere else in ESLint, and removing them allows us to reduce the size of ESLint, allowing for faster installation. -**To address:** If you are using the `codeframe` or `table` formatters, you'll need to install the standalone [`eslint-formatter-codeframe`](https://github.com/fregante/eslint-formatter-codeframe) or [`eslint-formatter-table`](https://github.com/fregante/eslint-formatter-table) packages, respectively, to be able to use them in ESLint v8.0.0. +**To address:** If you are using the `codeframe` or `table` formatters, you'll need to install the standalone [`eslint-formatter-codeframe`](https://github.com/eslint-community/eslint-formatter-codeframe) or [`eslint-formatter-table`](https://github.com/eslint-community/eslint-formatter-table) packages, respectively, to be able to use them in ESLint v8.0.0. **Related issue(s):** [#14277](https://github.com/eslint/eslint/issues/14277), [#14316](https://github.com/eslint/eslint/pull/14316) @@ -135,7 +135,7 @@ module.exports = { }; ``` -The [eslint-plugin/require-meta-has-suggestions](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-has-suggestions.md) rule can automatically fix and enforce that your rules are properly specifying `meta.hasSuggestions`. +The [eslint-plugin/require-meta-has-suggestions](https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/main/docs/rules/require-meta-has-suggestions.md) rule can automatically fix and enforce that your rules are properly specifying `meta.hasSuggestions`. **Related issue(s):** [#14312](https://github.com/eslint/eslint/issues/14312) @@ -164,9 +164,9 @@ module.exports = { }; ``` -The [eslint-plugin/require-meta-fixable](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-fixable.md) rule can automatically fix and enforce that your rules are properly specifying `meta.fixable`. +The [eslint-plugin/require-meta-fixable](https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/main/docs/rules/require-meta-fixable.md) rule can automatically fix and enforce that your rules are properly specifying `meta.fixable`. -The [eslint-plugin/prefer-object-rule](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-object-rule.md) rule can automatically fix and enforce that your rules are written with the object format instead of the deprecated function format. +The [eslint-plugin/prefer-object-rule](https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/main/docs/rules/prefer-object-rule.md) rule can automatically fix and enforce that your rules are written with the object format instead of the deprecated function format. See the [rule documentation](../extend/custom-rules) for more information on writing rules. diff --git a/docs/src/use/migrating-to-7.0.0.md b/docs/src/use/migrating-to-7.0.0.md index 898bcad11a3f..86b81cf832a6 100644 --- a/docs/src/use/migrating-to-7.0.0.md +++ b/docs/src/use/migrating-to-7.0.0.md @@ -145,7 +145,7 @@ To allow for the colocation of comments that provide context with the directive, ## Node.js/CommonJS rules have been deprecated -The ten Node.js/CommonJS rules in core have been deprecated and moved to the [eslint-plugin-node](https://github.com/mysticatea/eslint-plugin-node) plugin. +The ten Node.js/CommonJS rules in core have been deprecated and moved to the [eslint-plugin-node](https://github.com/mysticatea/eslint-plugin-node) plugin (for ESLint v8.0.0 and later, use the maintained [eslint-plugin-n](https://github.com/eslint-community/eslint-plugin-n) fork instead) . **To address:** As per [our deprecation policy](../use/rule-deprecation), the deprecated rules will remain in core for the foreseeable future and are still available for use. However, we will no longer be updating or fixing any bugs in those rules. To use a supported version of the rules, we recommend using the corresponding rules in the plugin instead. From 78350f63045c82b7990bb7bfe5080c5ad5e1c3f5 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 16 Jun 2023 23:34:52 +0200 Subject: [PATCH 046/248] chore: upgrade @eslint/js@8.43.0 (#17295) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9798881120eb..8c092b72c6c3 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.42.0", + "@eslint/js": "8.43.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", From 69a628033a016176b44bb61f54cb3d9e762048e1 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 16 Jun 2023 17:52:20 -0400 Subject: [PATCH 047/248] Build: changelog update for 8.43.0 --- CHANGELOG.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e4ac13f0245..4bb58d74514f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +v8.43.0 - June 16, 2023 + +* [`78350f6`](https://github.com/eslint/eslint/commit/78350f63045c82b7990bb7bfe5080c5ad5e1c3f5) chore: upgrade @eslint/js@8.43.0 (#17295) (Milos Djermanovic) +* [`8b855ea`](https://github.com/eslint/eslint/commit/8b855ea058992d5446d1d6dc6394ee683c3200a0) docs: resubmit pr17061 doc changes (#17292) (ๅ”ฏ็„ถ) +* [`62bf759`](https://github.com/eslint/eslint/commit/62bf759124811b013ad7906c2536deb8b39c31a8) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`14581ff`](https://github.com/eslint/eslint/commit/14581ff15aaee5a55c46bbf4983818ddc8dd7cb1) feat: directive prologue detection and autofix condition in `quotes` (#17284) (Francesco Trotta) +* [`372722e`](https://github.com/eslint/eslint/commit/372722eac32ca9e3f31cf0d0bc10317c6f153369) docs: resubmit pr17012 doc changes (#17293) (ๅ”ฏ็„ถ) +* [`67e7af3`](https://github.com/eslint/eslint/commit/67e7af3fdbdb4648b747dfd669be4decfe24086a) docs: resubmit custom-rules doc changes (#17294) (ๅ”ฏ็„ถ) +* [`5338b56`](https://github.com/eslint/eslint/commit/5338b56fda7f47d16bdb23514f1e95b24de7b92f) fix: normalize `cwd` passed to `ESLint`/`FlatESLint` constructor (#17277) (Milos Djermanovic) +* [`9e3d77c`](https://github.com/eslint/eslint/commit/9e3d77cba65d0e38e07996e57961fb04f30d9303) docs: Resubmit Fix formatting in Custom Rules docs (#17281) (Milos Djermanovic) +* [`503647a`](https://github.com/eslint/eslint/commit/503647a0b94ca8c776d7e7e8c54c8b1d32904467) docs: Resubmit markVariableAsUsed docs (#17280) (Nicholas C. Zakas) +* [`54383e6`](https://github.com/eslint/eslint/commit/54383e69b092ef537d59a1f7799a85b1412f4e59) fix: Remove `no-extra-parens` autofix for potential directives (#17022) (Francesco Trotta) +* [`e0cf0d8`](https://github.com/eslint/eslint/commit/e0cf0d86d985ed2b2f901dd9aab5ccd2fff062ad) docs: Custom rule & plugin tutorial (#17024) (Ben Perlmutter) +* [`8e51ea9`](https://github.com/eslint/eslint/commit/8e51ea943c2fcd05bd8917cfa89e36b91209c7cd) docs: resubmit `no-new` rule documentation (#17264) (Nitin Kumar) +* [`1b217f8`](https://github.com/eslint/eslint/commit/1b217f8de15961fd3c80389621080132f517a0fb) docs: resubmit `Custom Processors` documentation (#17265) (Nitin Kumar) +* [`428fc76`](https://github.com/eslint/eslint/commit/428fc76806dea1ac82484d628261a5385f928e6a) docs: resubmit `Create Plugins` documentation (#17268) (Nitin Kumar) +* [`bdca88c`](https://github.com/eslint/eslint/commit/bdca88cf4f8b7888cb72197bfe9c1d90b490a0dd) docs: resubmit `Configuration Files` documentation (#17267) (Nitin Kumar) +* [`f5c01f2`](https://github.com/eslint/eslint/commit/f5c01f281ad288b1a0ebddbf579230ae11587c6c) docs: resubmit `Manage Issues` documentation (#17266) (Nitin Kumar) +* [`b199295`](https://github.com/eslint/eslint/commit/b1992954591a3f4d8417013f52739b5fef4e0cd7) docs: Resubmit custom rules update docs (#17273) (Ben Perlmutter) +* [`e50fac3`](https://github.com/eslint/eslint/commit/e50fac3f8f998f729e3080e256066db3a7827c67) feat: add declaration loc to message in block-scoped-var (#17252) (Milos Djermanovic) +* [`0e9980c`](https://github.com/eslint/eslint/commit/0e9980c3a8a1e554fdb377305c0ebe9e94a354c9) docs: add new `omitLastInOneLineClassBody` option to the `semi` rule (#17263) (Nitin Kumar) +* [`cb2560f`](https://github.com/eslint/eslint/commit/cb2560f7a393e74b761faa9adad938fb1deb947d) docs: Resubmit getScope/getDeclaredVariables docs (#17262) (Nicholas C. Zakas) +* [`85d2b30`](https://github.com/eslint/eslint/commit/85d2b30bc318c1355e52ebb21c56cca32f0ab198) docs: explain how to include predefined globals (#17261) (Marcus Wyatt) +* [`de4d3c1`](https://github.com/eslint/eslint/commit/de4d3c14c30a88795b9075d59827d3fe63a42c5e) docs: update flat config default ignore patterns (#17258) (Milos Djermanovic) +* [`3912f3a`](https://github.com/eslint/eslint/commit/3912f3a225c12bfb5ce9b7ba26c2b5301e6275bd) docs: Improve `ignores` documentation (#17239) (Francesco Trotta) +* [`35e11d3`](https://github.com/eslint/eslint/commit/35e11d3248e00b711fd652836edc900f22af0ebd) docs: fix typos and missing info (#17257) (Ed Lucas) +* [`e0a2448`](https://github.com/eslint/eslint/commit/e0a2448e0c0ef354e69998858846630a3fce8ebe) chore: docs package.license ISC => MIT (#17254) (ๅ”ฏ็„ถ) +* [`0bc257c`](https://github.com/eslint/eslint/commit/0bc257c290b12fcda85cb61b40d55fc2be0f938c) docs: Clarify `no-div-regex` rule docs (#17051) (#17255) (Francesco Trotta) +* [`1b7faf0`](https://github.com/eslint/eslint/commit/1b7faf0702b1af86b6a0ddafc37cf45d60f5d4d8) feat: add `skipJSXText` option to `no-irregular-whitespace` rule (#17182) (Azat S) +* [`788d836`](https://github.com/eslint/eslint/commit/788d83629a3790a7db6f52dcf0b4bddf51c6d063) docs: add references to MIT License (#17248) (Milos Djermanovic) +* [`58aab6b`](https://github.com/eslint/eslint/commit/58aab6b6c09996875418aefeeb0fd76c50caef7a) docs: Update README (GitHub Actions Bot) +* [`6a0196c`](https://github.com/eslint/eslint/commit/6a0196c51310630a0ff96a1e8d7f257c2c7adda9) chore: use eslint-plugin-eslint-plugin flat configs (#17204) (Milos Djermanovic) +* [`030a827`](https://github.com/eslint/eslint/commit/030a82737f51563f9a7b4985fc91b6d8eab54fce) Revert "feat: docs license (#17010)" (#17231) (ๅ”ฏ็„ถ) +* [`3ef5814`](https://github.com/eslint/eslint/commit/3ef58140550cf8ff34af35fc4d9a1f9a124fe0e6) docs: Revert all changes after the license change (#17227) (Milos Djermanovic) +* [`03fc4aa`](https://github.com/eslint/eslint/commit/03fc4aa847bd0445e7b3ea81bcc9523b1847facc) docs: Update README (GitHub Actions Bot) + v8.42.0 - June 2, 2023 * [`6ca5b7c`](https://github.com/eslint/eslint/commit/6ca5b7ca3bac9e10c6cfee4cdc78446e94eb7607) chore: upgrade @eslint/js@8.42.0 (#17236) (Milos Djermanovic) From 150a74b29fae11af344ebbd29f24b5c1110f5848 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 16 Jun 2023 17:52:21 -0400 Subject: [PATCH 048/248] 8.43.0 --- docs/package.json | 2 +- docs/src/use/formatters/html-formatter-example.html | 2 +- docs/src/use/formatters/index.md | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/package.json b/docs/package.json index c728f96465a4..d9eb49fb518e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.42.0", + "version": "8.43.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 1cb369185950..5075035b903e 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

- 9 problems (5 errors, 4 warnings) - Generated on Fri Jun 02 2023 19:47:00 GMT-0400 (Eastern Daylight Time) + 9 problems (5 errors, 4 warnings) - Generated on Fri Jun 16 2023 17:52:22 GMT-0400 (Eastern Daylight Time)
diff --git a/docs/src/use/formatters/index.md b/docs/src/use/formatters/index.md index b5bb8be723d9..ec6ce537ed71 100644 --- a/docs/src/use/formatters/index.md +++ b/docs/src/use/formatters/index.md @@ -122,7 +122,7 @@ Alternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) Example output: ```text -{"results":[{"filePath":"/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'addOne' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":16},{"ruleId":"use-isnan","severity":2,"message":"Use the isNaN function to compare with NaN.","line":2,"column":9,"nodeType":"BinaryExpression","messageId":"comparisonWithNaN","endLine":2,"endColumn":17},{"ruleId":"space-unary-ops","severity":2,"message":"Unexpected space before unary operator '++'.","line":3,"column":16,"nodeType":"UpdateExpression","messageId":"unexpectedBefore","endLine":3,"endColumn":20,"fix":{"range":[57,58],"text":""}},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":3,"column":20,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":4,"endColumn":1,"fix":{"range":[60,60],"text":";"}},{"ruleId":"no-else-return","severity":1,"message":"Unnecessary 'else' after 'return'.","line":4,"column":12,"nodeType":"BlockStatement","messageId":"unexpected","endLine":6,"endColumn":6,"fix":{"range":[0,94],"text":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}"}},{"ruleId":"indent","severity":1,"message":"Expected indentation of 8 spaces but found 6.","line":5,"column":1,"nodeType":"Keyword","messageId":"wrongIndentation","endLine":5,"endColumn":7,"fix":{"range":[74,80],"text":" "}},{"ruleId":"consistent-return","severity":2,"message":"Function 'addOne' expected a return value.","line":5,"column":7,"nodeType":"ReturnStatement","messageId":"missingReturnValue","endLine":5,"endColumn":13},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":13,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":6,"endColumn":1,"fix":{"range":[86,86],"text":";"}},{"ruleId":"no-extra-semi","severity":2,"message":"Unnecessary semicolon.","line":7,"column":2,"nodeType":"EmptyStatement","messageId":"unexpected","endLine":7,"endColumn":3,"fix":{"range":[93,95],"text":"}"}}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":2,"fixableWarningCount":4,"source":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};"}],"metadata":{"rulesMeta":{"no-else-return":{"type":"suggestion","docs":{"description":"Disallow `else` blocks after `return` statements in `if` statements","recommended":false,"url":"https://eslint.org/docs/rules/no-else-return"},"schema":[{"type":"object","properties":{"allowElseIf":{"type":"boolean","default":true}},"additionalProperties":false}],"fixable":"code","messages":{"unexpected":"Unnecessary 'else' after 'return'."}},"indent":{"type":"layout","docs":{"description":"Enforce consistent indentation","recommended":false,"url":"https://eslint.org/docs/rules/indent"},"fixable":"whitespace","schema":[{"oneOf":[{"enum":["tab"]},{"type":"integer","minimum":0}]},{"type":"object","properties":{"SwitchCase":{"type":"integer","minimum":0,"default":0},"VariableDeclarator":{"oneOf":[{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},{"type":"object","properties":{"var":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"let":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"const":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false}]},"outerIIFEBody":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"MemberExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"FunctionDeclaration":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"FunctionExpression":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"StaticBlock":{"type":"object","properties":{"body":{"type":"integer","minimum":0}},"additionalProperties":false},"CallExpression":{"type":"object","properties":{"arguments":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false},"ArrayExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ObjectExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ImportDeclaration":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"flatTernaryExpressions":{"type":"boolean","default":false},"offsetTernaryExpressions":{"type":"boolean","default":false},"ignoredNodes":{"type":"array","items":{"type":"string","not":{"pattern":":exit$"}}},"ignoreComments":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"wrongIndentation":"Expected indentation of {{expected}} but found {{actual}}."}},"space-unary-ops":{"type":"layout","docs":{"description":"Enforce consistent spacing before or after unary operators","recommended":false,"url":"https://eslint.org/docs/rules/space-unary-ops"},"fixable":"whitespace","schema":[{"type":"object","properties":{"words":{"type":"boolean","default":true},"nonwords":{"type":"boolean","default":false},"overrides":{"type":"object","additionalProperties":{"type":"boolean"}}},"additionalProperties":false}],"messages":{"unexpectedBefore":"Unexpected space before unary operator '{{operator}}'.","unexpectedAfter":"Unexpected space after unary operator '{{operator}}'.","unexpectedAfterWord":"Unexpected space after unary word operator '{{word}}'.","wordOperator":"Unary word operator '{{word}}' must be followed by whitespace.","operator":"Unary operator '{{operator}}' must be followed by whitespace.","beforeUnaryExpressions":"Space is required before unary expressions '{{token}}'."}},"semi":{"type":"layout","docs":{"description":"Require or disallow semicolons instead of ASI","recommended":false,"url":"https://eslint.org/docs/rules/semi"},"fixable":"code","schema":{"anyOf":[{"type":"array","items":[{"enum":["never"]},{"type":"object","properties":{"beforeStatementContinuationChars":{"enum":["always","any","never"]}},"additionalProperties":false}],"minItems":0,"maxItems":2},{"type":"array","items":[{"enum":["always"]},{"type":"object","properties":{"omitLastInOneLineBlock":{"type":"boolean"}},"additionalProperties":false}],"minItems":0,"maxItems":2}]},"messages":{"missingSemi":"Missing semicolon.","extraSemi":"Extra semicolon."}},"consistent-return":{"type":"suggestion","docs":{"description":"Require `return` statements to either always or never specify values","recommended":false,"url":"https://eslint.org/docs/rules/consistent-return"},"schema":[{"type":"object","properties":{"treatUndefinedAsUnspecified":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"missingReturn":"Expected to return a value at the end of {{name}}.","missingReturnValue":"{{name}} expected a return value.","unexpectedReturnValue":"{{name}} expected no return value."}}}}} +{"results":[{"filePath":"/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'addOne' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":16},{"ruleId":"use-isnan","severity":2,"message":"Use the isNaN function to compare with NaN.","line":2,"column":9,"nodeType":"BinaryExpression","messageId":"comparisonWithNaN","endLine":2,"endColumn":17},{"ruleId":"space-unary-ops","severity":2,"message":"Unexpected space before unary operator '++'.","line":3,"column":16,"nodeType":"UpdateExpression","messageId":"unexpectedBefore","endLine":3,"endColumn":20,"fix":{"range":[57,58],"text":""}},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":3,"column":20,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":4,"endColumn":1,"fix":{"range":[60,60],"text":";"}},{"ruleId":"no-else-return","severity":1,"message":"Unnecessary 'else' after 'return'.","line":4,"column":12,"nodeType":"BlockStatement","messageId":"unexpected","endLine":6,"endColumn":6,"fix":{"range":[0,94],"text":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}"}},{"ruleId":"indent","severity":1,"message":"Expected indentation of 8 spaces but found 6.","line":5,"column":1,"nodeType":"Keyword","messageId":"wrongIndentation","endLine":5,"endColumn":7,"fix":{"range":[74,80],"text":" "}},{"ruleId":"consistent-return","severity":2,"message":"Function 'addOne' expected a return value.","line":5,"column":7,"nodeType":"ReturnStatement","messageId":"missingReturnValue","endLine":5,"endColumn":13},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":13,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":6,"endColumn":1,"fix":{"range":[86,86],"text":";"}},{"ruleId":"no-extra-semi","severity":2,"message":"Unnecessary semicolon.","line":7,"column":2,"nodeType":"EmptyStatement","messageId":"unexpected","endLine":7,"endColumn":3,"fix":{"range":[93,95],"text":"}"}}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":2,"fixableWarningCount":4,"source":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};"}],"metadata":{"rulesMeta":{"no-else-return":{"type":"suggestion","docs":{"description":"Disallow `else` blocks after `return` statements in `if` statements","recommended":false,"url":"https://eslint.org/docs/latest/rules/no-else-return"},"schema":[{"type":"object","properties":{"allowElseIf":{"type":"boolean","default":true}},"additionalProperties":false}],"fixable":"code","messages":{"unexpected":"Unnecessary 'else' after 'return'."}},"indent":{"type":"layout","docs":{"description":"Enforce consistent indentation","recommended":false,"url":"https://eslint.org/docs/latest/rules/indent"},"fixable":"whitespace","schema":[{"oneOf":[{"enum":["tab"]},{"type":"integer","minimum":0}]},{"type":"object","properties":{"SwitchCase":{"type":"integer","minimum":0,"default":0},"VariableDeclarator":{"oneOf":[{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},{"type":"object","properties":{"var":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"let":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"const":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false}]},"outerIIFEBody":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"MemberExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"FunctionDeclaration":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"FunctionExpression":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"StaticBlock":{"type":"object","properties":{"body":{"type":"integer","minimum":0}},"additionalProperties":false},"CallExpression":{"type":"object","properties":{"arguments":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false},"ArrayExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ObjectExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ImportDeclaration":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"flatTernaryExpressions":{"type":"boolean","default":false},"offsetTernaryExpressions":{"type":"boolean","default":false},"ignoredNodes":{"type":"array","items":{"type":"string","not":{"pattern":":exit$"}}},"ignoreComments":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"wrongIndentation":"Expected indentation of {{expected}} but found {{actual}}."}},"space-unary-ops":{"type":"layout","docs":{"description":"Enforce consistent spacing before or after unary operators","recommended":false,"url":"https://eslint.org/docs/latest/rules/space-unary-ops"},"fixable":"whitespace","schema":[{"type":"object","properties":{"words":{"type":"boolean","default":true},"nonwords":{"type":"boolean","default":false},"overrides":{"type":"object","additionalProperties":{"type":"boolean"}}},"additionalProperties":false}],"messages":{"unexpectedBefore":"Unexpected space before unary operator '{{operator}}'.","unexpectedAfter":"Unexpected space after unary operator '{{operator}}'.","unexpectedAfterWord":"Unexpected space after unary word operator '{{word}}'.","wordOperator":"Unary word operator '{{word}}' must be followed by whitespace.","operator":"Unary operator '{{operator}}' must be followed by whitespace.","beforeUnaryExpressions":"Space is required before unary expressions '{{token}}'."}},"semi":{"type":"layout","docs":{"description":"Require or disallow semicolons instead of ASI","recommended":false,"url":"https://eslint.org/docs/latest/rules/semi"},"fixable":"code","schema":{"anyOf":[{"type":"array","items":[{"enum":["never"]},{"type":"object","properties":{"beforeStatementContinuationChars":{"enum":["always","any","never"]}},"additionalProperties":false}],"minItems":0,"maxItems":2},{"type":"array","items":[{"enum":["always"]},{"type":"object","properties":{"omitLastInOneLineBlock":{"type":"boolean"},"omitLastInOneLineClassBody":{"type":"boolean"}},"additionalProperties":false}],"minItems":0,"maxItems":2}]},"messages":{"missingSemi":"Missing semicolon.","extraSemi":"Extra semicolon."}},"consistent-return":{"type":"suggestion","docs":{"description":"Require `return` statements to either always or never specify values","recommended":false,"url":"https://eslint.org/docs/latest/rules/consistent-return"},"schema":[{"type":"object","properties":{"treatUndefinedAsUnspecified":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"missingReturn":"Expected to return a value at the end of {{name}}.","missingReturnValue":"{{name}} expected a return value.","unexpectedReturnValue":"{{name}} expected no return value."}}}}} ``` ### json diff --git a/package.json b/package.json index 8c092b72c6c3..8a35ba94ed1d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.42.0", + "version": "8.43.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 19a8c5d84596a9f7f2aa428c1696ba86daf854e6 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Tue, 20 Jun 2023 08:06:25 +0000 Subject: [PATCH 049/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6722238a53ea..16f6513a3b22 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

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

+

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition Mercedes-Benz Group HeroCoders QuickBooks Tool hub

## Technology Sponsors From e1314bf85a52bb0d05b1c9ca3b4c1732bae22172 Mon Sep 17 00:00:00 2001 From: Ben Perlmutter <57849986+bpmutter@users.noreply.github.com> Date: Tue, 20 Jun 2023 06:22:50 -0400 Subject: [PATCH 050/248] docs: Integration section and tutorial (#17132) * docs: Integration section and tutorial * add navigation and landing pages for integration section * copy edits * working sample rule * complete integration tutorial draft * remove TODO * Apply suggestions from code review Co-authored-by: Nicholas C. Zakas * update source code * update tutorial * copy / linting edits * lint fix * Apply suggestions from code review Co-authored-by: Nicholas C. Zakas * implement nz feedback * Copy edit * Apply suggestions from code review Co-authored-by: Nitin Kumar * Apply suggestions from code review Co-authored-by: Milos Djermanovic * Apply suggestions from code review Co-authored-by: Milos Djermanovic * implement MD feedback * copy edit --------- Co-authored-by: Nicholas C. Zakas Co-authored-by: Nitin Kumar Co-authored-by: Milos Djermanovic --- .../integration-tutorial-code/.gitignore | 2 + .../example-eslint-integration.js | 62 +++++ .../example-eslint-integration.test.js | 27 ++ .../integration-tutorial-code/package.json | 15 ++ .../sample-data/test-file.js | 29 +++ docs/src/contribute/index.md | 2 +- docs/src/extend/index.md | 4 - docs/src/integrate/index.md | 25 ++ docs/src/integrate/integration-tutorial.md | 236 ++++++++++++++++++ docs/src/integrate/nodejs-api.md | 4 +- docs/src/maintain/index.md | 2 +- docs/src/pages/index.md | 6 +- 12 files changed, 405 insertions(+), 9 deletions(-) create mode 100644 docs/_examples/integration-tutorial-code/.gitignore create mode 100644 docs/_examples/integration-tutorial-code/example-eslint-integration.js create mode 100644 docs/_examples/integration-tutorial-code/example-eslint-integration.test.js create mode 100644 docs/_examples/integration-tutorial-code/package.json create mode 100644 docs/_examples/integration-tutorial-code/sample-data/test-file.js create mode 100644 docs/src/integrate/index.md create mode 100644 docs/src/integrate/integration-tutorial.md diff --git a/docs/_examples/integration-tutorial-code/.gitignore b/docs/_examples/integration-tutorial-code/.gitignore new file mode 100644 index 000000000000..28f1ba7565f4 --- /dev/null +++ b/docs/_examples/integration-tutorial-code/.gitignore @@ -0,0 +1,2 @@ +node_modules +.DS_Store \ No newline at end of file diff --git a/docs/_examples/integration-tutorial-code/example-eslint-integration.js b/docs/_examples/integration-tutorial-code/example-eslint-integration.js new file mode 100644 index 000000000000..f36b4e46e760 --- /dev/null +++ b/docs/_examples/integration-tutorial-code/example-eslint-integration.js @@ -0,0 +1,62 @@ +/** + * @fileoverview An example of how to integrate ESLint into your own tool + * @author Ben Perlmutter + */ + +const { ESLint } = require("eslint"); + +// Create an instance of ESLint with the configuration passed to the function +function createESLintInstance(overrideConfig){ + return new ESLint({ useEslintrc: false, overrideConfig: overrideConfig, fix: true }); +} + +// Lint the specified files and return the error results +async function lintAndFix(eslint, filePaths) { + const results = await eslint.lintFiles(filePaths); + + // Apply automatic fixes and output fixed code + await ESLint.outputFixes(results); + + return results; +} + +// Log results to console if there are any problems +function outputLintingResults(results) { + // Identify the number of problems found + const problems = results.reduce((acc, result) => acc + result.errorCount + result.warningCount, 0); + + if (problems > 0) { + console.log("Linting errors found!"); + console.log(results); + } else { + console.log("No linting errors found."); + } + return results; +} + +// Put previous functions all together +async function lintFiles(filePaths) { + + // The ESLint configuration. Alternatively, you could load the configuration + // from a .eslintrc file or just use the default config. + const overrideConfig = { + env: { + es6: true, + node: true, + }, + parserOptions: { + ecmaVersion: 2018, + }, + rules: { + "no-console": "error", + "no-unused-vars": "warn", + }, + }; + + const eslint = createESLintInstance(overrideConfig); + const results = await lintAndFix(eslint, filePaths); + return outputLintingResults(results); +} + +// Export integration +module.exports = { lintFiles } \ No newline at end of file diff --git a/docs/_examples/integration-tutorial-code/example-eslint-integration.test.js b/docs/_examples/integration-tutorial-code/example-eslint-integration.test.js new file mode 100644 index 000000000000..5db9aead60ac --- /dev/null +++ b/docs/_examples/integration-tutorial-code/example-eslint-integration.test.js @@ -0,0 +1,27 @@ +/** + * @fileoverview Test ESLint integration example code + * @author Ben Perlmutter + */ + +const { lintFiles } = require("./example-eslint-integration"); + +async function testExampleEslintIntegration(){ + const filePaths = ["sample-data/test-file.js"]; + const lintResults = await lintFiles(filePaths); + + // Test cases + if(lintResults[0].messages.length !== 6){ + throw new Error("Expected 6 linting problems, got " + lintResults[0].messages.length); + } + const messageRuleIds = new Set() + lintResults[0].messages.forEach(msg => messageRuleIds.add(msg.ruleId)); + if(messageRuleIds.size !== 2){ + throw new Error("Expected 2 linting rule, got " + messageRuleIds.size); + } + if(!messageRuleIds.has("no-console")){ + throw new Error("Expected linting rule 'no-console', got " + messageRuleIds); + } + console.log("All tests passed!"); +} + +testExampleEslintIntegration() \ No newline at end of file diff --git a/docs/_examples/integration-tutorial-code/package.json b/docs/_examples/integration-tutorial-code/package.json new file mode 100644 index 000000000000..df00d7382f19 --- /dev/null +++ b/docs/_examples/integration-tutorial-code/package.json @@ -0,0 +1,15 @@ +{ + "name": "_integration-tutorial-code", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "node example-eslint-integration.test.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "eslint": "^8.39.0" + } +} diff --git a/docs/_examples/integration-tutorial-code/sample-data/test-file.js b/docs/_examples/integration-tutorial-code/sample-data/test-file.js new file mode 100644 index 000000000000..425375f8f8ad --- /dev/null +++ b/docs/_examples/integration-tutorial-code/sample-data/test-file.js @@ -0,0 +1,29 @@ +/** + * @fileoverview Example data to lint using ESLint. This file contains a variety of errors. + * @author Ben Perlmutter + */ + +// Unused variable 'y' (no-unused-vars from configured rules) +const y = 20; + +function add(a, b) { + // Unexpected console statement (no-console from configured rules) + console.log('Adding two numbers'); + return a + b; +} + +// 'result' is assigned a value but never used (no-unused-vars from configured rules) +const result = add(x, 5); + +if (x > 5) { + // Unexpected console statement (no-console from configured rules) + console.log('x is greater than 5'); +} else { + // Unexpected console statement (no-console from configured rules) + console.log('x is not greater than 5'); +} + +// 'subtract' is defined but never used (no-unused-vars from configured rules) +function subtract(a, b) { + return a - b; +} diff --git a/docs/src/contribute/index.md b/docs/src/contribute/index.md index 3a20390db3d5..3b0a6d8f5c53 100644 --- a/docs/src/contribute/index.md +++ b/docs/src/contribute/index.md @@ -3,7 +3,7 @@ title: Contribute to ESLint eleventyNavigation: key: contribute to eslint title: Contribute to ESLint - order: 3 + order: 4 --- One of the great things about open source projects is that anyone can contribute in any number of meaningful ways. ESLint couldn't exist without the help of the many contributors it's had since the project began, and we want you to feel like you can contribute and make a difference as well. diff --git a/docs/src/extend/index.md b/docs/src/extend/index.md index 844d0789875a..815a42b457bd 100644 --- a/docs/src/extend/index.md +++ b/docs/src/extend/index.md @@ -48,7 +48,3 @@ This section explains how you can use a custom processor to have ESLint process ## [Share Configurations](shareable-configs) This section explains how you can bundle and share ESLint configuration in a JavaScript package. - -## [Node.js API Reference](../integrate/nodejs-api) - -If you're interested in writing a tool that uses ESLint, then you can use the Node.js API to get programmatic access to functionality. diff --git a/docs/src/integrate/index.md b/docs/src/integrate/index.md new file mode 100644 index 000000000000..19164a585308 --- /dev/null +++ b/docs/src/integrate/index.md @@ -0,0 +1,25 @@ +--- +title: Integrate ESLint +eleventyNavigation: + key: integrate eslint + title: integrate ESLint + order: 3 + +--- + +This guide is intended for those who wish to integrate the functionality of ESLint into other applications by using the ESLint API. + +In order to integrate ESLint, it's recommended that: + +* You know JavaScript since ESLint is written in JavaScript. +* You have some familiarity with Node.js since ESLint runs on it. + +If that sounds like you, then continue reading to get started. + +## [Integrate with the Node.js API Tutorial](integration-tutorial) + +This tutorial walks you through the process of creating a basic integration with ESLint using the Node.js API. + +## [Node.js API Reference](nodejs-api) + +If you're interested in writing a tool that uses ESLint, then you can use the Node.js API to get programmatic access to functionality. diff --git a/docs/src/integrate/integration-tutorial.md b/docs/src/integrate/integration-tutorial.md new file mode 100644 index 000000000000..08d77c7253b8 --- /dev/null +++ b/docs/src/integrate/integration-tutorial.md @@ -0,0 +1,236 @@ +--- +title: Integrate with the Node.js API Tutorial +eleventyNavigation: + key: integrate with the node.js api tutorial + parent: integrate eslint + title: Integrate with the Node.js API Tutorial + order: 1 +--- + +This guide walks you through integrating the `ESLint` class to lint files and retrieve results, which can be useful for creating integrations with other projects. + +## Why Create an Integration? + +You might want to create an ESLint integration if you're creating developer tooling, such as the following: + +* **Code editors and IDEs**: Integrating ESLint with code editors and IDEs can provide real-time feedback on code quality and automatically highlight potential issues as you type. Many editors already have ESLint plugins available, but you may need to create a custom integration if the existing plugins do not meet your specific requirements. + +* **Custom linter tools**: If you're building a custom linter tool that combines multiple linters or adds specific functionality, you may want to integrate ESLint into your tool to provide JavaScript linting capabilities. + +* **Code review tools**: Integrating ESLint with code review tools can help automate the process of identifying potential issues in the codebase. + +* **Learning platforms**: If you are developing a learning platform or coding tutorial, integrating ESLint can provide real-time feedback to users as they learn JavaScript, helping them improve their coding skills and learn best practices. + +* **Developer tool integration**: If you're creating or extending a developer tool, such as a bundler or testing framework, you may want to integrate ESLint to provide linting capabilities. You can integrate ESLint directly into the tool or as a plugin. + +## What You'll Build + +In this guide, you'll create a simple Node.js project that uses the `ESLint` class to lint files and retrieve results. + +## Requirements + +This tutorial assumes you are familiar with JavaScript and Node.js. + +To follow this tutorial, you'll need to have the following: + +* Node.js (v12.22.0 or higher) +* npm +* A text editor + +## Step 1: Setup + +First, create a new project directory: + +```shell +mkdir eslint-integration +cd eslint-integration +``` + +Initialize the project with a `package.json` file: + +```shell +npm init -y +``` + +Install the `eslint` package as a dependency (**not** as a dev dependency): + +```shell +npm install eslint +``` + +Create a new file called `example-eslint-integration.js` in the project root: + +```shell +touch example-eslint-integration.js +``` + +## Step 2: Import and Configure the `ESLint` Instance + +Import the `ESLint` class from the `eslint` package and create a new instance. + +You can customize the ESLint configuration by passing an options object to the `ESLint` constructor: + +```javascript +// example-eslint-integration.js + +const { ESLint } = require("eslint"); + +// Create an instance of ESLint with the configuration passed to the function +function createESLintInstance(overrideConfig){ + return new ESLint({ useEslintrc: false, overrideConfig: overrideConfig, fix: true }); +} +``` + +## Step 3: Lint and Fix Files + +To lint a file, use the `lintFiles` method of the `ESLint` instance. The `filePaths` argument passed to `ESLint#lintFiles()` can be a string or an array of strings, representing the file path(s) you want to lint. The file paths can be globs or filenames. + +The static method `ESLint.outputFixes()` takes the linting results from the call to `ESLint#lintFiles()`, and then writes the fixed code back to the source files. + +```javascript +// example-eslint-integration.js + +// ... previous step's code to instantiate the ESLint instance + +// Lint the specified files and return the results +async function lintAndFix(eslint, filePaths) { + const results = await eslint.lintFiles(filePaths); + + // Apply automatic fixes and output fixed code + await ESLint.outputFixes(results); + + return results; +} +``` + +## Step 4: Output Results + +Define a function to output the linting results to the console. This should be specific to your integration's needs. For example, you could report the linting results to a user interface. + +In this example, we'll simply log the results to the console: + +```javascript +// example-eslint-integration.js + +// ... previous step's code to instantiate the ESLint instance +// and get linting results. + +// Log results to console if there are any problems +function outputLintingResults(results) { + // Identify the number of problems found + const problems = results.reduce((acc, result) => acc + result.errorCount + result.warningCount, 0); + + if (problems > 0) { + console.log("Linting errors found!"); + console.log(results); + } else { + console.log("No linting errors found."); + } + return results; +} +``` + +## Step 5: Put It All Together + +Put the above functions together in a new function called `lintFiles`. This function will be the main entry point for your integration: + +```javascript +// example-eslint-integration.js + +// Put previous functions all together +async function lintFiles(filePaths) { + + // The ESLint configuration. Alternatively, you could load the configuration + // from a .eslintrc file or just use the default config. + const overrideConfig = { + env: { + es6: true, + node: true, + }, + parserOptions: { + ecmaVersion: 2018, + }, + rules: { + "no-console": "error", + "no-unused-vars": "warn", + }, + }; + + const eslint = createESLintInstance(overrideConfig); + const results = await lintAndFix(eslint, filePaths); + return outputLintingResults(results); +} + +// Export integration +module.exports = { lintFiles } +``` + +Here's the complete code example for `example-eslint-integration.js`: + +```javascript +const { ESLint } = require("eslint"); + +// Create an instance of ESLint with the configuration passed to the function +function createESLintInstance(overrideConfig){ + return new ESLint({ useEslintrc: false, overrideConfig: overrideConfig, fix: true }); +} + +// Lint the specified files and return the results +async function lintAndFix(eslint, filePaths) { + const results = await eslint.lintFiles(filePaths); + + // Apply automatic fixes and output fixed code + await ESLint.outputFixes(results); + + return results; +} + +// Log results to console if there are any problems +function outputLintingResults(results) { + // Identify the number of problems found + const problems = results.reduce((acc, result) => acc + result.errorCount + result.warningCount, 0); + + if (problems > 0) { + console.log("Linting errors found!"); + console.log(results); + } else { + console.log("No linting errors found."); + } + return results; +} + +// Put previous functions all together +async function lintFiles(filePaths) { + + // The ESLint configuration. Alternatively, you could load the configuration + // from a .eslintrc file or just use the default config. + const overrideConfig = { + env: { + es6: true, + node: true, + }, + parserOptions: { + ecmaVersion: 2018, + }, + rules: { + "no-console": "error", + "no-unused-vars": "warn", + }, + }; + + const eslint = createESLintInstance(overrideConfig); + const results = await lintAndFix(eslint, filePaths); + return outputLintingResults(results); +} + +// Export integration +module.exports = { lintFiles } +``` + +## Conclusion + +In this tutorial, we have covered the essentials of using the `ESLint` class to lint files and retrieve results in your projects. This knowledge can be applied to create custom integrations, such as code editor plugins, to provide real-time feedback on code quality. + +## View the Tutorial Code + +You can view the annotated source code for the tutorial [here](https://github.com/eslint/eslint/tree/main/docs/_examples/integration-tutorial-code). diff --git a/docs/src/integrate/nodejs-api.md b/docs/src/integrate/nodejs-api.md index 853abdc57b8d..5560a8080a0e 100644 --- a/docs/src/integrate/nodejs-api.md +++ b/docs/src/integrate/nodejs-api.md @@ -2,9 +2,9 @@ title: Node.js API Reference eleventyNavigation: key: node.js api - parent: extend eslint + parent: integrate eslint title: Node.js API Reference - order: 6 + order: 2 --- While ESLint is designed to be run on the command line, it's possible to use ESLint programmatically through the Node.js API. The purpose of the Node.js API is to allow plugin and tool authors to use the ESLint functionality directly, without going through the command line interface. diff --git a/docs/src/maintain/index.md b/docs/src/maintain/index.md index 29c3ab89f308..1b083836f3f0 100644 --- a/docs/src/maintain/index.md +++ b/docs/src/maintain/index.md @@ -3,7 +3,7 @@ title: Maintain ESLint eleventyNavigation: key: maintain eslint title: Maintain ESLint - order: 4 + order: 5 --- diff --git a/docs/src/pages/index.md b/docs/src/pages/index.md index c8f5f62dcaa8..f1a112a00103 100644 --- a/docs/src/pages/index.md +++ b/docs/src/pages/index.md @@ -12,7 +12,11 @@ as well as guides for migrating from earlier versions of ESLint. ## [Extend ESLint](extend/) -Intended for people who wish to extend ESLint. Contains information about creating custom rules, configurations, plugins, and formatters; and information about our Node.js API. +Intended for people who wish to extend ESLint. Contains information about creating custom rules, configurations, plugins, and formatters. + +## [Integrate ESLint](integrate/) + +Intended for people who wish to create integrations with ESLint. Contains information about creating integrations and using the Node.js API. ## [Contribute to ESLint](contribute/) From 391ed38b09bd1a3abe85db65b8fcda980ab3d6f4 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Tue, 20 Jun 2023 13:13:15 +0200 Subject: [PATCH 051/248] fix: Remove `no-extra-semi` autofix before potential directives (#17297) --- docs/src/rules/no-extra-semi.md | 2 ++ lib/rules/no-extra-semi.js | 40 +++++++++++++++++++++++--------- tests/lib/rules/no-extra-semi.js | 37 +++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/docs/src/rules/no-extra-semi.md b/docs/src/rules/no-extra-semi.md index 9755c5167fda..683c76cc5f0b 100644 --- a/docs/src/rules/no-extra-semi.md +++ b/docs/src/rules/no-extra-semi.md @@ -16,6 +16,8 @@ Typing mistakes and misunderstandings about where semicolons are required can le This rule disallows unnecessary semicolons. +Problems reported by this rule can be fixed automatically, except when removing a semicolon would cause a following statement to become a directive such as `"use strict"`. + Examples of **incorrect** code for this rule: ::: incorrect diff --git a/lib/rules/no-extra-semi.js b/lib/rules/no-extra-semi.js index ebf145f9cd11..3473df10dbfb 100644 --- a/lib/rules/no-extra-semi.js +++ b/lib/rules/no-extra-semi.js @@ -38,6 +38,23 @@ module.exports = { create(context) { const sourceCode = context.sourceCode; + /** + * Checks if a node or token is fixable. + * A node is fixable if it can be removed without turning a subsequent statement into a directive after fixing other nodes. + * @param {Token} nodeOrToken The node or token to check. + * @returns {boolean} Whether or not the node is fixable. + */ + function isFixable(nodeOrToken) { + const nextToken = sourceCode.getTokenAfter(nodeOrToken); + + if (!nextToken || nextToken.type !== "String") { + return true; + } + const stringNode = sourceCode.getNodeByRangeIndex(nextToken.range[0]); + + return !astUtils.isTopLevelExpressionStatement(stringNode.parent); + } + /** * Reports an unnecessary semicolon error. * @param {Node|Token} nodeOrToken A node or a token to be reported. @@ -47,17 +64,18 @@ module.exports = { context.report({ node: nodeOrToken, messageId: "unexpected", - fix(fixer) { - - /* - * Expand the replacement range to include the surrounding - * tokens to avoid conflicting with semi. - * https://github.com/eslint/eslint/issues/7928 - */ - return new FixTracker(fixer, context.sourceCode) - .retainSurroundingTokens(nodeOrToken) - .remove(nodeOrToken); - } + fix: isFixable(nodeOrToken) + ? fixer => + + /* + * Expand the replacement range to include the surrounding + * tokens to avoid conflicting with semi. + * https://github.com/eslint/eslint/issues/7928 + */ + new FixTracker(fixer, context.sourceCode) + .retainSurroundingTokens(nodeOrToken) + .remove(nodeOrToken) + : null }); } diff --git a/tests/lib/rules/no-extra-semi.js b/tests/lib/rules/no-extra-semi.js index 4f19fb722fa8..9a2d9a31beee 100644 --- a/tests/lib/rules/no-extra-semi.js +++ b/tests/lib/rules/no-extra-semi.js @@ -190,6 +190,43 @@ ruleTester.run("no-extra-semi", rule, { output: "class A { static { a; } foo(){} }", parserOptions: { ecmaVersion: 2022 }, errors: [{ messageId: "unexpected", type: "Punctuator", column: 24 }] + }, + + // https://github.com/eslint/eslint/issues/16988 + { + code: "; 'use strict'", + output: null, + errors: [{ messageId: "unexpected", type: "EmptyStatement" }] + }, + { + code: "; ; 'use strict'", + output: " ; 'use strict'", + errors: [{ messageId: "unexpected", type: "EmptyStatement" }, { messageId: "unexpected", type: "EmptyStatement" }] + }, + { + code: "debugger;\n;\n'use strict'", + output: null, + errors: [{ messageId: "unexpected", type: "EmptyStatement", line: 2 }] + }, + { + code: "function foo() { ; 'bar'; }", + output: null, + errors: [{ messageId: "unexpected", type: "EmptyStatement" }] + }, + { + code: "{ ; 'foo'; }", + output: "{ 'foo'; }", + errors: [{ messageId: "unexpected", type: "EmptyStatement" }] + }, + { + code: "; ('use strict');", + output: " ('use strict');", + errors: [{ messageId: "unexpected", type: "EmptyStatement" }] + }, + { + code: "; 1;", + output: " 1;", + errors: [{ messageId: "unexpected", type: "EmptyStatement" }] } ] }); From b991640176d5dce4750f7cc71c56cd6f284c882f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Wed, 21 Jun 2023 01:12:06 +0800 Subject: [PATCH 052/248] chore: switch eslint-config-eslint to the flat format (#17247) * chore: switch eslint-config-eslint to the flat format 1. convert eslint-config-eslint to the flat format. 2. export eslint-config-eslint/eslintrc (used by testing). 3. upgrade `eslint-plugin-n` v16 (added flat config supports). 4. change eslint plugins peerDependencies => dependencies. 5. change lib/eslint/eslint.js, lib/eslint/flat-eslint.js test cases to make the ci happy. note: it's a breaking change for eslint-config-eslint. refs: https://eslint.org/docs/latest/use/configure/configuration-files-new * fix: add linterOptions * chore: update comments as .eslintrc.js is no longer needed for testing --- .eslintrc.js | 5 +- eslint.config.js | 10 +- package.json | 2 +- packages/eslint-config-eslint/README.md | 9 +- packages/eslint-config-eslint/default.yml | 340 ---------------- packages/eslint-config-eslint/eslintrc.js | 452 +++++++++++++++++++++ packages/eslint-config-eslint/index.js | 409 ++++++++++++++++++- packages/eslint-config-eslint/package.json | 11 +- tests/lib/cli-engine/cli-engine.js | 8 +- tests/lib/eslint/eslint.js | 21 +- tests/lib/eslint/flat-eslint.js | 18 +- 11 files changed, 897 insertions(+), 388 deletions(-) delete mode 100644 packages/eslint-config-eslint/default.yml create mode 100644 packages/eslint-config-eslint/eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js index ae258dc64415..79812915fbfc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,8 +6,7 @@ * Internally, ESLint is using the eslint.config.js file to lint itself. * This file is needed too, because: * - * 1. There are tests that expect .eslintrc.js to be present to actually run. - * 2. ESLint VS Code extension expects eslintrc config files to be + * 1. ESLint VS Code extension expects eslintrc config files to be * present to work correctly. * * Once we no longer need to support both eslintrc and flat config, we will @@ -63,7 +62,7 @@ module.exports = { "internal-rules" ], extends: [ - "eslint" + "eslint/eslintrc" ], parserOptions: { ecmaVersion: 2021 diff --git a/eslint.config.js b/eslint.config.js index 492e6793ffa2..95612e901ca7 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -29,20 +29,14 @@ const path = require("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"); -const { FlatCompat } = require("@eslint/eslintrc"); -const js = require("./packages/js"); const globals = require("globals"); const merge = require("lodash.merge"); +const baseConfig = require("eslint-config-eslint"); //----------------------------------------------------------------------------- // Helpers //----------------------------------------------------------------------------- -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended -}); - const INTERNAL_FILES = { CLI_ENGINE_PATTERN: "lib/cli-engine/**/*", LINTER_PATTERN: "lib/linter/**/*", @@ -81,7 +75,7 @@ function createInternalFilesPatterns(pattern = null) { } module.exports = [ - ...compat.extends("eslint"), + ...baseConfig, { ignores: [ "build/**", diff --git a/package.json b/package.json index 8a35ba94ed1d..05c76fa41229 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "eslint-plugin-eslint-plugin": "^5.1.0", "eslint-plugin-internal-rules": "file:tools/internal-rules", "eslint-plugin-jsdoc": "^38.1.6", - "eslint-plugin-n": "^15.2.4", + "eslint-plugin-n": "^16.0.0", "eslint-plugin-unicorn": "^42.0.0", "eslint-release": "^3.2.0", "eslump": "^3.0.0", diff --git a/packages/eslint-config-eslint/README.md b/packages/eslint-config-eslint/README.md index c12625d58d3b..6be0d1c91f10 100644 --- a/packages/eslint-config-eslint/README.md +++ b/packages/eslint-config-eslint/README.md @@ -22,12 +22,11 @@ npm install eslint-config-eslint --save-dev ## Usage -In your `.eslintrc` file, add: +In your `eslint.config.js` file, add: -```json -{ - "extends": "eslint" -} +```js +const eslintConfig = require("eslint-config-eslint"); +module.exports = eslintConfig; ``` ### Where to ask for help? diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml deleted file mode 100644 index afaabe4f7ab5..000000000000 --- a/packages/eslint-config-eslint/default.yml +++ /dev/null @@ -1,340 +0,0 @@ -reportUnusedDisableDirectives: true -extends: - - "eslint:recommended" - - "plugin:n/recommended" - - "plugin:jsdoc/recommended" - - "plugin:eslint-comments/recommended" -plugins: - - unicorn -settings: - jsdoc: - 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 `GenericCallback` if truly arbitrary in form" - # replacement: "GenericCallback" - # function: - # message: "Point to a `@callback` namepath or `GenericCallback` if truly arbitrary in form" - # replacement: "GenericCallback" - 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 `PlainObject` if truly arbitrary" - # replacement: "PlainObject" - # object: - # message: "Use the specific object type or `PlainObject` if truly arbitrary" - # replacement: "PlainObject" - object: - message: "Use the specific object type or `Object` if truly arbitrary" - replacement: "Object" - # Array: - # message: "Use the specific array type or `GenericArray` if it is truly arbitrary." - # replacement: "GenericArray" - # array: - # message: "Use specific array type or `GenericArray` if it is truly arbitrary." - # replacement: "GenericArray" - 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: adopt non-recommended rules or change recommended configuration - # jsdoc/check-examples: "error" - # jsdoc/check-indentation: "error" # Revisit if allowing configurable spaces after tag line breaks - jsdoc/check-line-alignment: ["error", "never"] - jsdoc/check-syntax: "error" - # jsdoc/check-types': ["error', { exemptTagContexts: [ { tag: "typedef", types: ["object", "GenericObject"] } ] } - - jsdoc/check-values: ["error", { allowedLicenses: true }] - jsdoc/newline-after-description: ["error", "never"] - jsdoc/no-bad-blocks: "error" - jsdoc/require-asterisk-prefix: "error" - jsdoc/require-description: ["error", { checkConstructors: false }] - # jsdoc/require-file-overview: "error" - 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" }, - }, - }, - ] - - # jsdoc: disable recommended rules - jsdoc/no-undefined-types: "off" - jsdoc/require-yields: "off" - - # jsdoc: change recommended rules from warnings into errors - 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" - 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-floating-decimal: "error" - no-implied-eval: "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] # Modified from recommended - 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 }] # Modified from recommended - 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" }, - ] # Modified from recommended - no-use-before-define: "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-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" - - # Selectively-enable unicorn rules since many of its recommended rules are too aggressive/stylistic for us. - 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-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/eslintrc.js b/packages/eslint-config-eslint/eslintrc.js new file mode 100644 index 000000000000..cdb312e40e86 --- /dev/null +++ b/packages/eslint-config-eslint/eslintrc.js @@ -0,0 +1,452 @@ +/** + * 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: { + 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-line-alignment": ["error", "never"], + "jsdoc/check-syntax": "error", + "jsdoc/check-values": [ + "error", + { + allowedLicenses: true + } + ], + "jsdoc/newline-after-description": ["error", "never"], + "jsdoc/no-bad-blocks": "error", + "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" + } + } + } + ], + "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", + "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-floating-decimal": "error", + "no-implied-eval": "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-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-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-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/index.js b/packages/eslint-config-eslint/index.js index 1304f34a870d..14e044726d97 100644 --- a/packages/eslint-config-eslint/index.js +++ b/packages/eslint-config-eslint/index.js @@ -1,9 +1,404 @@ -/** - * @fileoverview Index file to allow YAML file to be loaded - * @author Teddy Katz - */ "use strict"; -module.exports = { - extends: ["./default.yml"] -}; +const nodeRecommendedConfig = require("eslint-plugin-n/configs/recommended-script"); +const js = require("@eslint/js"); +const jsdoc = require("eslint-plugin-jsdoc"); +const eslintComments = require("eslint-plugin-eslint-comments"); +const unicorn = require("eslint-plugin-unicorn"); + +/* + * the plugins' configs are not updated to support the flat config, + * need to manually update the `plugins` property + */ +jsdoc.configs.recommended.plugins = { jsdoc }; +eslintComments.configs.recommended.plugins = { "eslint-comments": eslintComments }; + +// extends eslint-plugin-n's recommended config +const nodeConfigs = [nodeRecommendedConfig, { + rules: { + "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" + } +}]; + +// extends eslint recommended config +const jsConfigs = [js.configs.recommended, { + 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", + "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", + "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-floating-decimal": "error", + "no-implied-eval": "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-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", + "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-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", + "wrap-iife": "error", + "yield-star-spacing": "error", + yoda: ["error", "never", { exceptRange: true }] + } +}]; + +// extends eslint-plugin-jsdoc's recommended config +const jsdocConfigs = [jsdoc.configs.recommended, { + settings: { + jsdoc: { + 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: { + "jsdoc/check-line-alignment": ["error", "never"], + "jsdoc/check-syntax": "error", + "jsdoc/check-values": ["error", { allowedLicenses: true }], + "jsdoc/newline-after-description": ["error", "never"], + "jsdoc/no-bad-blocks": "error", + "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" + } + } + } + ], + "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", + "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" + } +}]; + +// extends eslint-plugin-unicorn's config +const unicornConfigs = [{ + plugins: { unicorn }, + rules: { + "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-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" + } +}]; + +// extends eslint-plugin-eslint-comments's recommended config +const eslintCommentsConfigs = [eslintComments.configs.recommended, { + rules: { + "eslint-comments/disable-enable-pair": ["error"], + "eslint-comments/no-unused-disable": "error", + "eslint-comments/require-description": "error" + } +}]; + +module.exports = [ + { linterOptions: { reportUnusedDisableDirectives: true } }, + ...jsConfigs, + ...nodeConfigs, + ...unicornConfigs, + ...jsdocConfigs, + ...eslintCommentsConfigs +]; diff --git a/packages/eslint-config-eslint/package.json b/packages/eslint-config-eslint/package.json index 00c10811fb8e..6a01573446a7 100644 --- a/packages/eslint-config-eslint/package.json +++ b/packages/eslint-config-eslint/package.json @@ -19,11 +19,12 @@ }, "homepage": "https://eslint.org", "bugs": "https://github.com/eslint/eslint/issues/", - "peerDependencies": { - "eslint-plugin-eslint-comments": ">=3.2.0", - "eslint-plugin-jsdoc": ">=38.1.6", - "eslint-plugin-n": ">=15.2.4", - "eslint-plugin-unicorn": ">=42.0.0" + "dependencies": { + "@eslint/js": "^8.42.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-jsdoc": "^38.1.6", + "eslint-plugin-n": "^16.0.0", + "eslint-plugin-unicorn": "^42.0.0" }, "keywords": [ "eslintconfig", diff --git a/tests/lib/cli-engine/cli-engine.js b/tests/lib/cli-engine/cli-engine.js index 971d6e609640..5012867c3954 100644 --- a/tests/lib/cli-engine/cli-engine.js +++ b/tests/lib/cli-engine/cli-engine.js @@ -5184,9 +5184,9 @@ describe("CLIEngine", () => { }); it("should expose the list of plugin rules", () => { - const engine = new CLIEngine({ plugins: ["n"] }); + const engine = new CLIEngine({ plugins: ["internal-rules"] }); - assert(engine.getRules().has("n/no-deprecated-api"), "n/no-deprecated-api is present"); + assert(engine.getRules().has("internal-rules/no-invalid-meta"), "internal-rules/no-invalid-meta is present"); }); it("should expose the list of rules from a preloaded plugin", () => { @@ -5194,11 +5194,11 @@ describe("CLIEngine", () => { plugins: ["foo"] }, { preloadedPlugins: { - foo: require("eslint-plugin-n") + foo: require("eslint-plugin-internal-rules") } }); - assert(engine.getRules().has("foo/no-deprecated-api"), "foo/no-deprecated-api is present"); + assert(engine.getRules().has("foo/no-invalid-meta"), "foo/no-invalid-meta is present"); }); }); diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index cb43823fbc5f..74abaf42e5d9 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -46,7 +46,7 @@ describe("ESLint", () => { const originalDir = process.cwd(); const fixtureDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint/fixtures"); - /** @type {import("../../../lib/eslint").ESLint} */ + /** @type {import("../../../lib/eslint/eslint").ESLint} */ let ESLint; /** @@ -5125,30 +5125,35 @@ describe("ESLint", () => { }); it("should return multiple rule meta when there are multiple linting errors from a plugin", async () => { - const nodePlugin = require("eslint-plugin-n"); + const customPlugin = { + rules: { + "no-var": require("../../../lib/rules/no-var") + } + }; + const engine = new ESLint({ useEslintrc: false, plugins: { - node: nodePlugin + "custom-plugin": customPlugin }, overrideConfig: { - plugins: ["n"], + plugins: ["custom-plugin"], rules: { - "n/no-new-require": 2, + "custom-plugin/no-var": 2, semi: 2, quotes: [2, "double"] } } }); - const results = await engine.lintText("new require('hi')"); + const results = await engine.lintText("var foo = 0; var bar = '1'"); const rulesMeta = engine.getRulesMetaForResults(results); assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); assert.strictEqual( - rulesMeta["n/no-new-require"], - nodePlugin.rules["no-new-require"].meta + rulesMeta["custom-plugin/no-var"], + customPlugin.rules["no-var"].meta ); }); diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index 0a692e9e7f96..0865a071070b 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -69,7 +69,7 @@ describe("FlatESLint", () => { const originalDir = process.cwd(); const fixtureDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint/fixtures"); - /** @type {import("../../../lib/flat-eslint").FlatESLint} */ + /** @type {import("../../../lib/eslint/flat-eslint").FlatESLint} */ let FlatESLint; /** @@ -4379,29 +4379,33 @@ describe("FlatESLint", () => { }); it("should return multiple rule meta when there are multiple linting errors from a plugin", async () => { - const nodePlugin = require("eslint-plugin-n"); + const customPlugin = { + rules: { + "no-var": require("../../../lib/rules/no-var") + } + }; const engine = new FlatESLint({ overrideConfigFile: true, overrideConfig: { plugins: { - n: nodePlugin + "custom-plugin": customPlugin }, rules: { - "n/no-new-require": 2, + "custom-plugin/no-var": 2, semi: 2, quotes: [2, "double"] } } }); - const results = await engine.lintText("new require('hi')"); + const results = await engine.lintText("var foo = 0; var bar = '1'"); const rulesMeta = engine.getRulesMetaForResults(results); assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); assert.strictEqual( - rulesMeta["n/no-new-require"], - nodePlugin.rules["no-new-require"].meta + rulesMeta["custom-plugin/no-var"], + customPlugin.rules["no-var"].meta ); }); From 526e91106e6fe101578e9478a9d7f4844d4f72ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Wed, 21 Jun 2023 18:57:28 +0800 Subject: [PATCH 053/248] docs: resubmit pr 17115 doc changes (#17291) credits: https://github.com/domdomegg Refs: * https://github.com/eslint/eslint/issues/17225 * https://github.com/eslint/eslint/pull/17115 --- docs/src/extend/custom-rules.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index 3ba8845b9542..76c9e88741dd 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -614,12 +614,17 @@ You can also access comments through many of `sourceCode`'s methods using the `i Rules may export a `schema` property, which is a [JSON Schema](https://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. -There are two formats for a rule's exported `schema`. The first is a full JSON Schema object describing all possible options the rule accepts, including the rule's error level as the first argument and any optional arguments thereafter. +There are two formats for a rule's exported `schema`: -However, to simplify schema creation, rules may also export an array of schemas for each optional positional argument. ESLint automatically validates the required error level first. For example, the `yoda` rule accepts a primary mode argument, as well as an extra options object with named properties. +1. A full JSON Schema object describing all possible options the rule accepts. +2. An array of JSON Schema objects for each optional positional argument. + +In both cases, these should exclude the [severity](../use/configure/rules#rule-severities), as ESLint automatically validates this first. + +For example, the `yoda` rule accepts a primary mode argument of `"always"` or `"never"`, as well as an extra options object with an optional property `exceptRange`: ```js -// "yoda": [2, "never", { "exceptRange": true }] +// "yoda": ["error", "never", { "exceptRange": true }] module.exports = { meta: { schema: [ @@ -640,12 +645,10 @@ module.exports = { }; ``` -In the preceding example, the error level is assumed to be the first argument. It is followed by the first optional argument, a string that may be either `"always"` or `"never"`. The final optional argument is an object, which may have a boolean property named `exceptRange`. - -To learn more about JSON Schema, we recommend looking at some examples in [website](https://json-schema.org/learn/) to start, and also reading [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) (a free ebook). - **Note:** If your rule schema uses JSON schema [`$ref`](https://json-schema.org/understanding-json-schema/structuring.html#ref) properties, you must use the full JSON Schema object rather than the array of positional property schemas. This is because ESLint transforms the array shorthand into a single schema without updating references that makes them incorrect (they are ignored). +To learn more about JSON Schema, we recommend looking at some examples on the [JSON Schema website](https://json-schema.org/learn/), or reading the free [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) ebook. + ### Accessing Shebangs [Shebangs (#!)](https://en.wikipedia.org/wiki/Shebang_(Unix)) are represented by the unique tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined in the [Accessing Comments](#accessing-comments) section, such as `sourceCode.getAllComments()`. From da81e66e22b4f3d3fe292cf70c388753304deaad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Wed, 21 Jun 2023 23:49:19 +0800 Subject: [PATCH 054/248] chore: update eslint-plugin-jsdoc to 46.2.5 (#17245) 1. Removes `jsdoc/newline-after-description` rule in favor of `jsdoc/tag-lines` with option `startLines: 0` for "never" and `startLines: 1` for "always". Refs: https://github.com/gajus/eslint-plugin-jsdoc/releases/tag/v42.0.0 2. disabled rule jsdoc/no-defaults: it's a new added rule in its recommended config that eslint does not follow. 3. disabled rule jsdoc/check-line-alignment: I've tried its option "never"|"always"|"any" - none of them fully meet our needs. --- lib/rule-tester/flat-rule-tester.js | 3 +-- lib/rule-tester/rule-tester.js | 3 +-- package.json | 2 +- packages/eslint-config-eslint/eslintrc.js | 7 ++++--- packages/eslint-config-eslint/index.js | 14 +++++--------- packages/eslint-config-eslint/package.json | 2 +- 6 files changed, 13 insertions(+), 18 deletions(-) diff --git a/lib/rule-tester/flat-rule-tester.js b/lib/rule-tester/flat-rule-tester.js index 97055d104f41..f143873f7bc7 100644 --- a/lib/rule-tester/flat-rule-tester.js +++ b/lib/rule-tester/flat-rule-tester.js @@ -33,7 +33,7 @@ const { ConfigArraySymbol } = require("@humanwhocodes/config-array"); /** @typedef {import("../shared/types").Parser} Parser */ /** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */ -/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */ + /** * A test case that is expected to pass lint. * @typedef {Object} ValidTestCase @@ -72,7 +72,6 @@ const { ConfigArraySymbol } = require("@humanwhocodes/config-array"); * @property {number} [endLine] The 1-based line number of the reported end location. * @property {number} [endColumn] The 1-based column number of the reported end location. */ -/* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */ //------------------------------------------------------------------------------ // Private Members diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index 8518299d0b04..e4dc126783c8 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -63,7 +63,7 @@ const { SourceCode } = require("../source-code"); /** @typedef {import("../shared/types").Parser} Parser */ -/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */ + /** * A test case that is expected to pass lint. * @typedef {Object} ValidTestCase @@ -108,7 +108,6 @@ const { SourceCode } = require("../source-code"); * @property {number} [endLine] The 1-based line number of the reported end location. * @property {number} [endColumn] The 1-based column number of the reported end location. */ -/* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */ //------------------------------------------------------------------------------ // Private Members diff --git a/package.json b/package.json index 05c76fa41229..c71234373479 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-eslint-plugin": "^5.1.0", "eslint-plugin-internal-rules": "file:tools/internal-rules", - "eslint-plugin-jsdoc": "^38.1.6", + "eslint-plugin-jsdoc": "^46.2.5", "eslint-plugin-n": "^16.0.0", "eslint-plugin-unicorn": "^42.0.0", "eslint-release": "^3.2.0", diff --git a/packages/eslint-config-eslint/eslintrc.js b/packages/eslint-config-eslint/eslintrc.js index cdb312e40e86..a821ab0a07c3 100644 --- a/packages/eslint-config-eslint/eslintrc.js +++ b/packages/eslint-config-eslint/eslintrc.js @@ -101,7 +101,6 @@ module.exports = { "generator-star-spacing": "error", "grouped-accessor-pairs": "error", "guard-for-in": "error", - "jsdoc/check-line-alignment": ["error", "never"], "jsdoc/check-syntax": "error", "jsdoc/check-values": [ "error", @@ -109,8 +108,8 @@ module.exports = { allowedLicenses: true } ], - "jsdoc/newline-after-description": ["error", "never"], "jsdoc/no-bad-blocks": "error", + "jsdoc/no-defaults": "off", "jsdoc/require-asterisk-prefix": "error", "jsdoc/require-description": [ "error", @@ -138,8 +137,10 @@ module.exports = { fileoverview: { lines: "any" } - } + }, + startLines: 0 } + ], "jsdoc/no-undefined-types": "off", "jsdoc/require-yields": "off", diff --git a/packages/eslint-config-eslint/index.js b/packages/eslint-config-eslint/index.js index 14e044726d97..1f629c5cd837 100644 --- a/packages/eslint-config-eslint/index.js +++ b/packages/eslint-config-eslint/index.js @@ -312,11 +312,10 @@ const jsdocConfigs = [jsdoc.configs.recommended, { } }, rules: { - "jsdoc/check-line-alignment": ["error", "never"], "jsdoc/check-syntax": "error", "jsdoc/check-values": ["error", { allowedLicenses: true }], - "jsdoc/newline-after-description": ["error", "never"], "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"], @@ -330,13 +329,10 @@ const jsdocConfigs = [jsdoc.configs.recommended, { "jsdoc/tag-lines": ["error", "never", { tags: { - example: { - lines: "always" - }, - fileoverview: { - lines: "any" - } - } + example: { lines: "always" }, + fileoverview: { lines: "any" } + }, + startLines: 0 } ], "jsdoc/no-undefined-types": "off", diff --git a/packages/eslint-config-eslint/package.json b/packages/eslint-config-eslint/package.json index 6a01573446a7..ae1728a3e17b 100644 --- a/packages/eslint-config-eslint/package.json +++ b/packages/eslint-config-eslint/package.json @@ -22,7 +22,7 @@ "dependencies": { "@eslint/js": "^8.42.0", "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-jsdoc": "^38.1.6", + "eslint-plugin-jsdoc": "^46.2.5", "eslint-plugin-n": "^16.0.0", "eslint-plugin-unicorn": "^42.0.0" }, From f82e56e9acfb9562ece76441472d5657d7d5e296 Mon Sep 17 00:00:00 2001 From: moonlightaria <72459063+moonlightaria@users.noreply.github.com> Date: Thu, 22 Jun 2023 09:14:32 -0400 Subject: [PATCH 055/248] perf: various performance improvements (#17135) * perf: addParensIndent replaced shifting with reverse iteration * perf: changed string handling from regex to manual parsing * refactor: more explicit * perf: remove unnessisary array and object creation * pref: opimized out reduce for basic iteration * perf: replaced reduce with basic iteration * perf: optimized unessisary array creation and objects * perf: replaced reduce with basic iteration * perf: replaced reduce with basic iteration * perf: replaced filter with basic loop for counting valid elements * perf: removed array creation in indent program exit --- lib/cli-engine/cli-engine.js | 45 ++++++++++------- lib/eslint/flat-eslint.js | 44 ++++++++++------- lib/rules/accessor-pairs.js | 74 +++++++++++++--------------- lib/rules/array-element-newline.js | 14 ++++-- lib/rules/grouped-accessor-pairs.js | 75 +++++++++++++---------------- lib/rules/indent.js | 74 ++++++++++++++++------------ lib/rules/max-len.js | 30 +++++++----- lib/rules/no-loss-of-precision.js | 20 +++++--- 8 files changed, 203 insertions(+), 173 deletions(-) diff --git a/lib/cli-engine/cli-engine.js b/lib/cli-engine/cli-engine.js index 093a20b1ded4..311dc61e81ca 100644 --- a/lib/cli-engine/cli-engine.js +++ b/lib/cli-engine/cli-engine.js @@ -158,7 +158,17 @@ function validateFixTypes(fixTypes) { * @private */ function calculateStatsPerFile(messages) { - return messages.reduce((stat, message) => { + const stat = { + errorCount: 0, + fatalErrorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0 + }; + + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + if (message.fatal || message.severity === 2) { stat.errorCount++; if (message.fatal) { @@ -173,14 +183,8 @@ function calculateStatsPerFile(messages) { stat.fixableWarningCount++; } } - return stat; - }, { - errorCount: 0, - fatalErrorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - }); + } + return stat; } /** @@ -190,20 +194,25 @@ function calculateStatsPerFile(messages) { * @private */ function calculateStatsPerRun(results) { - return results.reduce((stat, result) => { - stat.errorCount += result.errorCount; - stat.fatalErrorCount += result.fatalErrorCount; - stat.warningCount += result.warningCount; - stat.fixableErrorCount += result.fixableErrorCount; - stat.fixableWarningCount += result.fixableWarningCount; - return stat; - }, { + const stat = { errorCount: 0, fatalErrorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0 - }); + }; + + for (let i = 0; i < results.length; i++) { + const result = results[i]; + + stat.errorCount += result.errorCount; + stat.fatalErrorCount += result.fatalErrorCount; + stat.warningCount += result.warningCount; + stat.fixableErrorCount += result.fixableErrorCount; + stat.fixableWarningCount += result.fixableWarningCount; + } + + return stat; } /** diff --git a/lib/eslint/flat-eslint.js b/lib/eslint/flat-eslint.js index f615ae171556..3245808c2af5 100644 --- a/lib/eslint/flat-eslint.js +++ b/lib/eslint/flat-eslint.js @@ -103,7 +103,17 @@ const importedConfigFileModificationTime = new Map(); * @private */ function calculateStatsPerFile(messages) { - return messages.reduce((stat, message) => { + const stat = { + errorCount: 0, + fatalErrorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0 + }; + + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + if (message.fatal || message.severity === 2) { stat.errorCount++; if (message.fatal) { @@ -118,14 +128,8 @@ function calculateStatsPerFile(messages) { stat.fixableWarningCount++; } } - return stat; - }, { - errorCount: 0, - fatalErrorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - }); + } + return stat; } /** @@ -135,20 +139,24 @@ function calculateStatsPerFile(messages) { * @private */ function calculateStatsPerRun(results) { - return results.reduce((stat, result) => { - stat.errorCount += result.errorCount; - stat.fatalErrorCount += result.fatalErrorCount; - stat.warningCount += result.warningCount; - stat.fixableErrorCount += result.fixableErrorCount; - stat.fixableWarningCount += result.fixableWarningCount; - return stat; - }, { + const stat = { errorCount: 0, fatalErrorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0 - }); + }; + + for (let i = 0; i < results.length; i++) { + const result = results[i]; + + stat.errorCount += result.errorCount; + stat.fatalErrorCount += result.fatalErrorCount; + stat.warningCount += result.warningCount; + stat.fixableErrorCount += result.fixableErrorCount; + stat.fixableWarningCount += result.fixableWarningCount; + } + return stat; } /** diff --git a/lib/rules/accessor-pairs.js b/lib/rules/accessor-pairs.js index 03b51e461c00..f97032895df0 100644 --- a/lib/rules/accessor-pairs.js +++ b/lib/rules/accessor-pairs.js @@ -223,43 +223,6 @@ module.exports = { } } - /** - * Creates a new `AccessorData` object for the given getter or setter node. - * @param {ASTNode} node A getter or setter node. - * @returns {AccessorData} New `AccessorData` object that contains the given node. - * @private - */ - function createAccessorData(node) { - const name = astUtils.getStaticPropertyName(node); - const key = (name !== null) ? name : sourceCode.getTokens(node.key); - - return { - key, - getters: node.kind === "get" ? [node] : [], - setters: node.kind === "set" ? [node] : [] - }; - } - - /** - * Merges the given `AccessorData` object into the given accessors list. - * @param {AccessorData[]} accessors The list to merge into. - * @param {AccessorData} accessorData The object to merge. - * @returns {AccessorData[]} The same instance with the merged object. - * @private - */ - function mergeAccessorData(accessors, accessorData) { - const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key)); - - if (equalKeyElement) { - equalKeyElement.getters.push(...accessorData.getters); - equalKeyElement.setters.push(...accessorData.setters); - } else { - accessors.push(accessorData); - } - - return accessors; - } - /** * Checks accessor pairs in the given list of nodes. * @param {ASTNode[]} nodes The list to check. @@ -267,10 +230,39 @@ module.exports = { * @private */ function checkList(nodes) { - const accessors = nodes - .filter(isAccessorKind) - .map(createAccessorData) - .reduce(mergeAccessorData, []); + const accessors = []; + let found = false; + + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + + if (isAccessorKind(node)) { + + // Creates a new `AccessorData` object for the given getter or setter node. + const name = astUtils.getStaticPropertyName(node); + const key = (name !== null) ? name : sourceCode.getTokens(node.key); + + // Merges the given `AccessorData` object into the given accessors list. + for (let j = 0; j < accessors.length; j++) { + const accessor = accessors[j]; + + if (areEqualKeys(accessor.key, key)) { + accessor.getters.push(...node.kind === "get" ? [node] : []); + accessor.setters.push(...node.kind === "set" ? [node] : []); + found = true; + break; + } + } + if (!found) { + accessors.push({ + key, + getters: node.kind === "get" ? [node] : [], + setters: node.kind === "set" ? [node] : [] + }); + } + found = false; + } + } for (const { getters, setters } of accessors) { if (checkSetWithoutGet && setters.length && !getters.length) { diff --git a/lib/rules/array-element-newline.js b/lib/rules/array-element-newline.js index be547ec36174..0c806ef3a82c 100644 --- a/lib/rules/array-element-newline.js +++ b/lib/rules/array-element-newline.js @@ -240,19 +240,25 @@ module.exports = { .some(element => element.loc.start.line !== element.loc.end.line); } - const linebreaksCount = node.elements.map((element, i) => { + let linebreaksCount = 0; + + for (let i = 0; i < node.elements.length; i++) { + const element = node.elements[i]; + const previousElement = elements[i - 1]; if (i === 0 || element === null || previousElement === null) { - return false; + continue; } const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken); const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken); const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken); - return !astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement); - }).filter(isBreak => isBreak === true).length; + if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) { + linebreaksCount++; + } + } const needsLinebreaks = ( elements.length >= options.minItems || diff --git a/lib/rules/grouped-accessor-pairs.js b/lib/rules/grouped-accessor-pairs.js index c08e1c4973e9..9556f4756825 100644 --- a/lib/rules/grouped-accessor-pairs.js +++ b/lib/rules/grouped-accessor-pairs.js @@ -137,43 +137,6 @@ module.exports = { }); } - /** - * Creates a new `AccessorData` object for the given getter or setter node. - * @param {ASTNode} node A getter or setter node. - * @returns {AccessorData} New `AccessorData` object that contains the given node. - * @private - */ - function createAccessorData(node) { - const name = astUtils.getStaticPropertyName(node); - const key = (name !== null) ? name : sourceCode.getTokens(node.key); - - return { - key, - getters: node.kind === "get" ? [node] : [], - setters: node.kind === "set" ? [node] : [] - }; - } - - /** - * Merges the given `AccessorData` object into the given accessors list. - * @param {AccessorData[]} accessors The list to merge into. - * @param {AccessorData} accessorData The object to merge. - * @returns {AccessorData[]} The same instance with the merged object. - * @private - */ - function mergeAccessorData(accessors, accessorData) { - const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key)); - - if (equalKeyElement) { - equalKeyElement.getters.push(...accessorData.getters); - equalKeyElement.setters.push(...accessorData.setters); - } else { - accessors.push(accessorData); - } - - return accessors; - } - /** * Checks accessor pairs in the given list of nodes. * @param {ASTNode[]} nodes The list to check. @@ -182,11 +145,39 @@ module.exports = { * @private */ function checkList(nodes, shouldCheck) { - const accessors = nodes - .filter(shouldCheck) - .filter(isAccessorKind) - .map(createAccessorData) - .reduce(mergeAccessorData, []); + const accessors = []; + let found = false; + + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + + if (shouldCheck(node) && isAccessorKind(node)) { + + // Creates a new `AccessorData` object for the given getter or setter node. + const name = astUtils.getStaticPropertyName(node); + const key = (name !== null) ? name : sourceCode.getTokens(node.key); + + // Merges the given `AccessorData` object into the given accessors list. + for (let j = 0; j < accessors.length; j++) { + const accessor = accessors[j]; + + if (areEqualKeys(accessor.key, key)) { + accessor.getters.push(...node.kind === "get" ? [node] : []); + accessor.setters.push(...node.kind === "set" ? [node] : []); + found = true; + break; + } + } + if (!found) { + accessors.push({ + key, + getters: node.kind === "get" ? [node] : [], + setters: node.kind === "set" ? [node] : [] + }); + } + found = false; + } + } for (const { getters, setters } of accessors) { diff --git a/lib/rules/indent.js b/lib/rules/indent.js index bcc5143d26e9..9068006d4972 100644 --- a/lib/rules/indent.js +++ b/lib/rules/indent.js @@ -188,15 +188,19 @@ class TokenInfo { */ constructor(sourceCode) { this.sourceCode = sourceCode; - this.firstTokensByLineNumber = sourceCode.tokensAndComments.reduce((map, token) => { - if (!map.has(token.loc.start.line)) { - map.set(token.loc.start.line, token); + this.firstTokensByLineNumber = new Map(); + const tokens = sourceCode.tokensAndComments; + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + + if (!this.firstTokensByLineNumber.has(token.loc.start.line)) { + this.firstTokensByLineNumber.set(token.loc.start.line, token); } - if (!map.has(token.loc.end.line) && sourceCode.text.slice(token.range[1] - token.loc.end.column, token.range[1]).trim()) { - map.set(token.loc.end.line, token); + if (!this.firstTokensByLineNumber.has(token.loc.end.line) && sourceCode.text.slice(token.range[1] - token.loc.end.column, token.range[1]).trim()) { + this.firstTokensByLineNumber.set(token.loc.end.line, token); } - return map; - }, new Map()); + } } /** @@ -964,19 +968,19 @@ module.exports = { const parenStack = []; const parenPairs = []; - tokens.forEach(nextToken => { + for (let i = 0; i < tokens.length; i++) { + const nextToken = tokens[i]; - // Accumulate a list of parenthesis pairs if (astUtils.isOpeningParenToken(nextToken)) { parenStack.push(nextToken); } else if (astUtils.isClosingParenToken(nextToken)) { - parenPairs.unshift({ left: parenStack.pop(), right: nextToken }); + parenPairs.push({ left: parenStack.pop(), right: nextToken }); } - }); + } - parenPairs.forEach(pair => { - const leftParen = pair.left; - const rightParen = pair.right; + for (let i = parenPairs.length - 1; i >= 0; i--) { + const leftParen = parenPairs[i].left; + const rightParen = parenPairs[i].right; // We only want to handle parens around expressions, so exclude parentheses that are in function parameters and function call arguments. if (!parameterParens.has(leftParen) && !parameterParens.has(rightParen)) { @@ -990,7 +994,7 @@ module.exports = { } offsets.setDesiredOffset(rightParen, leftParen, 0); - }); + } } /** @@ -1711,9 +1715,13 @@ module.exports = { } // Invoke the queued offset listeners for the nodes that aren't ignored. - listenerCallQueue - .filter(nodeInfo => !ignoredNodes.has(nodeInfo.node)) - .forEach(nodeInfo => nodeInfo.listener(nodeInfo.node)); + for (let i = 0; i < listenerCallQueue.length; i++) { + const nodeInfo = listenerCallQueue[i]; + + if (!ignoredNodes.has(nodeInfo.node)) { + nodeInfo.listener(nodeInfo.node); + } + } // Update the offsets for ignored nodes to prevent their child tokens from being reported. ignoredNodes.forEach(ignoreNode); @@ -1724,27 +1732,31 @@ module.exports = { * Create a Map from (tokenOrComment) => (precedingToken). * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly. */ - const precedingTokens = sourceCode.ast.comments.reduce((commentMap, comment) => { + const precedingTokens = new WeakMap(); + + for (let i = 0; i < sourceCode.ast.comments.length; i++) { + const comment = sourceCode.ast.comments[i]; + const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { includeComments: true }); + const hasToken = precedingTokens.has(tokenOrCommentBefore) ? precedingTokens.get(tokenOrCommentBefore) : tokenOrCommentBefore; - return commentMap.set(comment, commentMap.has(tokenOrCommentBefore) ? commentMap.get(tokenOrCommentBefore) : tokenOrCommentBefore); - }, new WeakMap()); + precedingTokens.set(comment, hasToken); + } - sourceCode.lines.forEach((line, lineIndex) => { - const lineNumber = lineIndex + 1; + for (let i = 1; i < sourceCode.lines.length + 1; i++) { - if (!tokenInfo.firstTokensByLineNumber.has(lineNumber)) { + if (!tokenInfo.firstTokensByLineNumber.has(i)) { // Don't check indentation on blank lines - return; + continue; } - const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(lineNumber); + const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(i); - if (firstTokenOfLine.loc.start.line !== lineNumber) { + if (firstTokenOfLine.loc.start.line !== i) { // Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice. - return; + continue; } if (astUtils.isCommentToken(firstTokenOfLine)) { @@ -1769,18 +1781,18 @@ module.exports = { mayAlignWithBefore && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenBefore)) || mayAlignWithAfter && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenAfter)) ) { - return; + continue; } } // If the token matches the expected indentation, don't report it. if (validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine))) { - return; + continue; } // Otherwise, report the token/comment. report(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine)); - }); + } } } ); diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js index 59e85214a292..53ad5310799b 100644 --- a/lib/rules/max-len.js +++ b/lib/rules/max-len.js @@ -252,19 +252,23 @@ module.exports = { return sourceCode.ast.tokens.filter(token => token.type === "RegularExpression"); } - /** - * A reducer to group an AST node by line number, both start and end. - * @param {Object} acc the accumulator - * @param {ASTNode} node the AST node in question - * @returns {Object} the modified accumulator - * @private + * + * reduce an array of AST nodes by line number, both start and end. + * @param {ASTNode[]} arr array of AST nodes + * @returns {Object} accululated AST nodes */ - function groupByLineNumber(acc, node) { - for (let i = node.loc.start.line; i <= node.loc.end.line; ++i) { - ensureArrayAndPush(acc, i, node); + function groupArrayByLineNumber(arr) { + const obj = {}; + + for (let i = 0; i < arr.length; i++) { + const node = arr[i]; + + for (let j = node.loc.start.line; j <= node.loc.end.line; ++j) { + ensureArrayAndPush(obj, j, node); + } } - return acc; + return obj; } /** @@ -312,13 +316,13 @@ module.exports = { let commentsIndex = 0; const strings = getAllStrings(); - const stringsByLine = strings.reduce(groupByLineNumber, {}); + const stringsByLine = groupArrayByLineNumber(strings); const templateLiterals = getAllTemplateLiterals(); - const templateLiteralsByLine = templateLiterals.reduce(groupByLineNumber, {}); + const templateLiteralsByLine = groupArrayByLineNumber(templateLiterals); const regExpLiterals = getAllRegExpLiterals(); - const regExpLiteralsByLine = regExpLiterals.reduce(groupByLineNumber, {}); + const regExpLiteralsByLine = groupArrayByLineNumber(regExpLiterals); lines.forEach((line, i) => { diff --git a/lib/rules/no-loss-of-precision.js b/lib/rules/no-loss-of-precision.js index 22ca7f93e3ad..b3635e3d509f 100644 --- a/lib/rules/no-loss-of-precision.js +++ b/lib/rules/no-loss-of-precision.js @@ -83,7 +83,7 @@ module.exports = { * @returns {string} the numeric string with a decimal point in the proper place */ function addDecimalPointToNumber(stringNumber) { - return `${stringNumber.slice(0, 1)}.${stringNumber.slice(1)}`; + return `${stringNumber[0]}.${stringNumber.slice(1)}`; } /** @@ -92,7 +92,12 @@ module.exports = { * @returns {string} the stripped string */ function removeLeadingZeros(numberAsString) { - return numberAsString.replace(/^0*/u, ""); + for (let i = 0; i < numberAsString.length; i++) { + if (numberAsString[i] !== "0") { + return numberAsString.slice(i); + } + } + return numberAsString; } /** @@ -101,7 +106,12 @@ module.exports = { * @returns {string} the stripped string */ function removeTrailingZeros(numberAsString) { - return numberAsString.replace(/0*$/u, ""); + for (let i = numberAsString.length - 1; i >= 0; i--) { + if (numberAsString[i] !== "0") { + return numberAsString.slice(0, i + 1); + } + } + return numberAsString; } /** @@ -128,7 +138,7 @@ module.exports = { const trimmedFloat = removeLeadingZeros(stringFloat); if (trimmedFloat.startsWith(".")) { - const decimalDigits = trimmedFloat.split(".").pop(); + const decimalDigits = trimmedFloat.slice(1); const significantDigits = removeLeadingZeros(decimalDigits); return { @@ -144,7 +154,6 @@ module.exports = { }; } - /** * Converts a base ten number to proper scientific notation * @param {string} stringNumber the string representation of the base ten number to be converted @@ -160,7 +169,6 @@ module.exports = { : normalizedNumber.magnitude; return `${normalizedCoefficient}e${magnitude}`; - } /** From 1866e1df6175e4ba0ae4a0d88dc3c956bb310035 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Thu, 22 Jun 2023 22:30:06 +0200 Subject: [PATCH 056/248] feat: allow flat config files to export a Promise (#17301) * feat: allow flat config files to export a Promise Fixes #16864, #16580 * Update docs/src/use/configure/configuration-files-new.md Co-authored-by: Nicholas C. Zakas * Update docs/src/use/configure/configuration-files-new.md Co-authored-by: Nicholas C. Zakas --------- Co-authored-by: Nicholas C. Zakas --- .../use/configure/configuration-files-new.md | 29 ++++++++++++++++++- tests/fixtures/promise-config/a.js | 1 + .../fixtures/promise-config/eslint.config.js | 5 ++++ tests/lib/eslint/flat-eslint.js | 25 ++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/promise-config/a.js create mode 100644 tests/fixtures/promise-config/eslint.config.js diff --git a/docs/src/use/configure/configuration-files-new.md b/docs/src/use/configure/configuration-files-new.md index 62394c0b2e73..4c2bd1fa4df1 100644 --- a/docs/src/use/configure/configuration-files-new.md +++ b/docs/src/use/configure/configuration-files-new.md @@ -26,11 +26,38 @@ export default [ "prefer-const": "error" } } -] +]; ``` In this example, the configuration array contains just one configuration object. The configuration object enables two rules: `semi` and `prefer-const`. These rules are applied to all of the files ESLint processes using this config file. +If your project does not specify `"type":"module"` in its `package.json` file, then `eslint.config.js` must be in CommonJS format, such as: + +```js +module.exports = [ + { + rules: { + semi: "error", + "prefer-const": "error" + } + } +]; +``` + +The configuration file can also export a promise that resolves to the configuration array. This can be useful for using ESM dependencies in CommonJS configuration files, as in this example: + +```js +module.exports = (async () => { + + const someDependency = await import("some-esm-dependency"); + + return [ + // ... use `someDependency` here + ]; + +})(); +``` + ## Configuration Objects Each configuration object contains all of the information ESLint needs to execute on a set of files. Each configuration object is made up of these properties: diff --git a/tests/fixtures/promise-config/a.js b/tests/fixtures/promise-config/a.js new file mode 100644 index 000000000000..4ec1fa479e22 --- /dev/null +++ b/tests/fixtures/promise-config/a.js @@ -0,0 +1 @@ +var foo = "bar"; diff --git a/tests/fixtures/promise-config/eslint.config.js b/tests/fixtures/promise-config/eslint.config.js new file mode 100644 index 000000000000..852686881f0a --- /dev/null +++ b/tests/fixtures/promise-config/eslint.config.js @@ -0,0 +1,5 @@ +module.exports = Promise.resolve([{ + rules: { + quotes: ["error", "single"] + } +}]); diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index 0865a071070b..c3a8240e7b24 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -761,6 +761,18 @@ describe("FlatESLint", () => { eslint = new FlatESLint(); await assert.rejects(() => eslint.lintText("var a = 0", { warnIgnored: "" }), /'options.warnIgnored' must be a boolean or undefined/u); }); + + it("should work with config file that exports a promise", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("promise-config") + }); + const results = await eslint.lintText('var foo = "bar";'); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + }); }); describe("lintFiles()", () => { @@ -991,6 +1003,19 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].suppressedMessages.length, 0); }); + it("should work with config file that exports a promise", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("promise-config") + }); + const results = await eslint.lintFiles(["a*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("promise-config", "a.js")); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + }); + // https://github.com/eslint/eslint/issues/16265 describe("Dot files in searches", () => { From 9718a9781d69d2c40b68c631aed97700b32c0082 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sun, 25 Jun 2023 12:36:06 +0200 Subject: [PATCH 057/248] refactor: remove unnecessary code in `flat-eslint.js` (#17308) --- lib/eslint/flat-eslint.js | 106 +------------------------------------- 1 file changed, 2 insertions(+), 104 deletions(-) diff --git a/lib/eslint/flat-eslint.js b/lib/eslint/flat-eslint.js index 3245808c2af5..9d511aab2e84 100644 --- a/lib/eslint/flat-eslint.js +++ b/lib/eslint/flat-eslint.js @@ -132,33 +132,6 @@ function calculateStatsPerFile(messages) { return stat; } -/** - * It will calculate the error and warning count for collection of results from all files - * @param {LintResult[]} results Collection of messages from all the files - * @returns {Object} Contains the stats - * @private - */ -function calculateStatsPerRun(results) { - const stat = { - errorCount: 0, - fatalErrorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - }; - - for (let i = 0; i < results.length; i++) { - const result = results[i]; - - stat.errorCount += result.errorCount; - stat.fatalErrorCount += result.fatalErrorCount; - stat.warningCount += result.warningCount; - stat.fixableErrorCount += result.fixableErrorCount; - stat.fixableWarningCount += result.fixableWarningCount; - } - return stat; -} - /** * Create rulesMeta object. * @param {Map} rules a map of rules from which to generate the object. @@ -558,43 +531,6 @@ function shouldMessageBeFixed(message, config, fixTypes) { return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type)); } -/** - * Collect used deprecated rules. - * @param {Array} configs The configs to evaluate. - * @returns {IterableIterator} Used deprecated rules. - */ -function *iterateRuleDeprecationWarnings(configs) { - const processedRuleIds = new Set(); - - for (const config of configs) { - for (const [ruleId, ruleConfig] of Object.entries(config.rules)) { - - // Skip if it was processed. - if (processedRuleIds.has(ruleId)) { - continue; - } - processedRuleIds.add(ruleId); - - // Skip if it's not used. - if (!getRuleSeverity(ruleConfig)) { - continue; - } - const rule = getRuleFromConfig(ruleId, config); - - // Skip if it's not deprecated. - if (!(rule && rule.meta && rule.meta.deprecated)) { - continue; - } - - // This rule was used and deprecated. - yield { - ruleId, - replacedBy: rule.meta.replacedBy || [] - }; - } - } -} - /** * Creates an error to be thrown when an array of results passed to `getRulesMetaForResults` was not created by the current engine. * @returns {TypeError} An error object. @@ -819,7 +755,6 @@ class FlatESLint { errorOnUnmatchedPattern } = eslintOptions; const startTime = Date.now(); - const usedConfigs = []; const fixTypesSet = fixTypes ? new Set(fixTypes) : null; // Delete cache file; should this be done here? @@ -877,15 +812,6 @@ class FlatESLint { return void 0; } - /* - * Store used configs for: - * - this method uses to collect used deprecated rules. - * - `--fix-type` option uses to get the loaded rule's meta data. - */ - if (!usedConfigs.includes(config)) { - usedConfigs.push(config); - } - // Skip if there is cached result. if (lintResultCache) { const cachedResult = @@ -954,22 +880,10 @@ class FlatESLint { lintResultCache.reconcile(); } - let usedDeprecatedRules; const finalResults = results.filter(result => !!result); return processLintReport(this, { - results: finalResults, - ...calculateStatsPerRun(finalResults), - - // Initialize it lazily because CLI and `ESLint` API don't use it. - get usedDeprecatedRules() { - if (!usedDeprecatedRules) { - usedDeprecatedRules = Array.from( - iterateRuleDeprecationWarnings(usedConfigs) - ); - } - return usedDeprecatedRules; - } + results: finalResults }); } @@ -1031,7 +945,6 @@ class FlatESLint { const results = []; const startTime = Date.now(); const resolvedFilename = path.resolve(cwd, filePath || "__placeholder__.js"); - let config; // Clear the last used config arrays. if (resolvedFilename && await this.isPathIgnored(resolvedFilename)) { @@ -1040,9 +953,6 @@ class FlatESLint { } } else { - // TODO: Needed? - config = configs.getConfig(resolvedFilename); - // Do lint. results.push(verifyText({ text: code, @@ -1057,21 +967,9 @@ class FlatESLint { } debug(`Linting complete in: ${Date.now() - startTime}ms`); - let usedDeprecatedRules; return processLintReport(this, { - results, - ...calculateStatsPerRun(results), - - // Initialize it lazily because CLI and `ESLint` API don't use it. - get usedDeprecatedRules() { - if (!usedDeprecatedRules) { - usedDeprecatedRules = Array.from( - iterateRuleDeprecationWarnings(config) - ); - } - return usedDeprecatedRules; - } + results }); } From ef6e24e42670f321d996948623846d9caaedac99 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 28 Jun 2023 20:55:45 +0930 Subject: [PATCH 058/248] feat: treat unknown nodes as having the lowest precedence (#17302) * feat: treat unknown nodes as having the lowest precedence * fix test typo * more tests and handle prefer-exponentiation case * review fixes and more cases * Apply suggestions from code review Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- lib/rules/logical-assignment-operators.js | 7 +- lib/rules/prefer-exponentiation-operator.js | 3 +- lib/rules/utils/ast-utils.js | 12 +- .../boolean-cast-with-assertion.js | 333 ++++++++++ .../exponentiation-with-assertion-1.js | 321 ++++++++++ .../exponentiation-with-assertion-2.js | 321 ++++++++++ .../exponentiation-with-assertion-3.js | 321 ++++++++++ .../logical-assignment-with-assertion.js | 209 +++++++ ...ogical-with-assignment-with-assertion-1.js | 538 +++++++++++++++++ ...ogical-with-assignment-with-assertion-2.js | 568 ++++++++++++++++++ ...ogical-with-assignment-with-assertion-3.js | 568 ++++++++++++++++++ .../member-call-expr-with-assertion.js | 366 +++++++++++ .../typescript-parsers/unneeded-ternary-1.js | 238 ++++++++ .../typescript-parsers/unneeded-ternary-2.js | 240 ++++++++ .../lib/rules/logical-assignment-operators.js | 60 +- tests/lib/rules/no-extra-boolean-cast.js | 12 +- tests/lib/rules/no-extra-parens.js | 9 +- tests/lib/rules/no-unneeded-ternary.js | 26 +- .../rules/prefer-exponentiation-operator.js | 38 +- 19 files changed, 4180 insertions(+), 10 deletions(-) create mode 100644 tests/fixtures/parsers/typescript-parsers/boolean-cast-with-assertion.js create mode 100644 tests/fixtures/parsers/typescript-parsers/exponentiation-with-assertion-1.js create mode 100644 tests/fixtures/parsers/typescript-parsers/exponentiation-with-assertion-2.js create mode 100644 tests/fixtures/parsers/typescript-parsers/exponentiation-with-assertion-3.js create mode 100644 tests/fixtures/parsers/typescript-parsers/logical-assignment-with-assertion.js create mode 100644 tests/fixtures/parsers/typescript-parsers/logical-with-assignment-with-assertion-1.js create mode 100644 tests/fixtures/parsers/typescript-parsers/logical-with-assignment-with-assertion-2.js create mode 100644 tests/fixtures/parsers/typescript-parsers/logical-with-assignment-with-assertion-3.js create mode 100644 tests/fixtures/parsers/typescript-parsers/member-call-expr-with-assertion.js create mode 100644 tests/fixtures/parsers/typescript-parsers/unneeded-ternary-1.js create mode 100644 tests/fixtures/parsers/typescript-parsers/unneeded-ternary-2.js diff --git a/lib/rules/logical-assignment-operators.js b/lib/rules/logical-assignment-operators.js index cb1cbe5a16b9..27ca585e9958 100644 --- a/lib/rules/logical-assignment-operators.js +++ b/lib/rules/logical-assignment-operators.js @@ -370,8 +370,11 @@ module.exports = { return; } - const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" && - (astUtils.getPrecedence({ type: "AssignmentExpression" }) < astUtils.getPrecedence(logical.parent)); + const parentPrecedence = astUtils.getPrecedence(logical.parent); + const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" && ( + parentPrecedence === -1 || + astUtils.getPrecedence({ type: "AssignmentExpression" }) < parentPrecedence + ); if (!astUtils.isParenthesised(sourceCode, logical) && requiresOuterParenthesis) { yield ruleFixer.insertTextBefore(logical, "("); diff --git a/lib/rules/prefer-exponentiation-operator.js b/lib/rules/prefer-exponentiation-operator.js index dd4ba2c8e040..6d807f9cfeaf 100644 --- a/lib/rules/prefer-exponentiation-operator.js +++ b/lib/rules/prefer-exponentiation-operator.js @@ -55,11 +55,12 @@ function doesExponentNeedParens(exponent) { function doesExponentiationExpressionNeedParens(node, sourceCode) { const parent = node.parent.type === "ChainExpression" ? node.parent.parent : node.parent; + const parentPrecedence = astUtils.getPrecedence(parent); const needsParens = ( parent.type === "ClassDeclaration" || ( parent.type.endsWith("Expression") && - astUtils.getPrecedence(parent) >= PRECEDENCE_OF_EXPONENTIATION_EXPR && + (parentPrecedence === -1 || parentPrecedence >= PRECEDENCE_OF_EXPONENTIATION_EXPR) && !(parent.type === "BinaryExpression" && parent.operator === "**" && parent.right === node) && !((parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node)) && !(parent.type === "MemberExpression" && parent.computed && parent.property === node) && diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index aed2c42f9a3f..39e53b1057db 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -9,6 +9,7 @@ // Requirements //------------------------------------------------------------------------------ +const { KEYS: eslintVisitorKeys } = require("eslint-visitor-keys"); const esutils = require("esutils"); const espree = require("espree"); const escapeRegExp = require("escape-string-regexp"); @@ -1461,7 +1462,16 @@ module.exports = { return 19; default: - return 20; + if (node.type in eslintVisitorKeys) { + return 20; + } + + /* + * if the node is not a standard node that we know about, then assume it has the lowest precedence + * this will mean that rules will wrap unknown nodes in parentheses where applicable instead of + * unwrapping them and potentially changing the meaning of the code or introducing a syntax error. + */ + return -1; } }, diff --git a/tests/fixtures/parsers/typescript-parsers/boolean-cast-with-assertion.js b/tests/fixtures/parsers/typescript-parsers/boolean-cast-with-assertion.js new file mode 100644 index 000000000000..caecf5c9191c --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/boolean-cast-with-assertion.js @@ -0,0 +1,333 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser 5.59.11 (TS 5.1.3) + * Source code: + * if (!Boolean(a as any)) { } + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "IfStatement", + test: { + type: "UnaryExpression", + operator: "!", + prefix: true, + argument: { + type: "CallExpression", + callee: { + type: "Identifier", + decorators: [], + name: "Boolean", + optional: false, + range: [5, 12], + loc: { + start: { + line: 1, + column: 5, + }, + end: { + line: 1, + column: 12, + }, + }, + }, + arguments: [ + { + type: "TSAsExpression", + expression: { + type: "Identifier", + decorators: [], + name: "a", + optional: false, + range: [13, 14], + loc: { + start: { + line: 1, + column: 13, + }, + end: { + line: 1, + column: 14, + }, + }, + }, + typeAnnotation: { + type: "TSAnyKeyword", + range: [18, 21], + loc: { + start: { + line: 1, + column: 18, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + range: [13, 21], + loc: { + start: { + line: 1, + column: 13, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + ], + optional: false, + range: [5, 22], + loc: { + start: { + line: 1, + column: 5, + }, + end: { + line: 1, + column: 22, + }, + }, + }, + range: [4, 22], + loc: { + start: { + line: 1, + column: 4, + }, + end: { + line: 1, + column: 22, + }, + }, + }, + consequent: { + type: "BlockStatement", + body: [], + range: [24, 27], + loc: { + start: { + line: 1, + column: 24, + }, + end: { + line: 1, + column: 27, + }, + }, + }, + alternate: null, + range: [0, 27], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 27, + }, + }, + }, + ], + comments: [], + range: [0, 27], + sourceType: "script", + tokens: [ + { + type: "Keyword", + value: "if", + range: [0, 2], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 2, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [3, 4], + loc: { + start: { + line: 1, + column: 3, + }, + end: { + line: 1, + column: 4, + }, + }, + }, + { + type: "Punctuator", + value: "!", + range: [4, 5], + loc: { + start: { + line: 1, + column: 4, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + { + type: "Identifier", + value: "Boolean", + range: [5, 12], + loc: { + start: { + line: 1, + column: 5, + }, + end: { + line: 1, + column: 12, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [12, 13], + loc: { + start: { + line: 1, + column: 12, + }, + end: { + line: 1, + column: 13, + }, + }, + }, + { + type: "Identifier", + value: "a", + range: [13, 14], + loc: { + start: { + line: 1, + column: 13, + }, + end: { + line: 1, + column: 14, + }, + }, + }, + { + type: "Identifier", + value: "as", + range: [15, 17], + loc: { + start: { + line: 1, + column: 15, + }, + end: { + line: 1, + column: 17, + }, + }, + }, + { + type: "Identifier", + value: "any", + range: [18, 21], + loc: { + start: { + line: 1, + column: 18, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [21, 22], + loc: { + start: { + line: 1, + column: 21, + }, + end: { + line: 1, + column: 22, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [22, 23], + loc: { + start: { + line: 1, + column: 22, + }, + end: { + line: 1, + column: 23, + }, + }, + }, + { + type: "Punctuator", + value: "{", + range: [24, 25], + loc: { + start: { + line: 1, + column: 24, + }, + end: { + line: 1, + column: 25, + }, + }, + }, + { + type: "Punctuator", + value: "}", + range: [26, 27], + loc: { + start: { + line: 1, + column: 26, + }, + end: { + line: 1, + column: 27, + }, + }, + }, + ], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 27, + }, + }, +}); diff --git a/tests/fixtures/parsers/typescript-parsers/exponentiation-with-assertion-1.js b/tests/fixtures/parsers/typescript-parsers/exponentiation-with-assertion-1.js new file mode 100644 index 000000000000..7921af528724 --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/exponentiation-with-assertion-1.js @@ -0,0 +1,321 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser 5.59.11 (TS 5.1.3) + * Source code: + * Math.pow(a, b as any) + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "CallExpression", + callee: { + type: "MemberExpression", + object: { + type: "Identifier", + decorators: [], + name: "Math", + optional: false, + range: [0, 4], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 4, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "pow", + optional: false, + range: [5, 8], + loc: { + start: { + line: 1, + column: 5, + }, + end: { + line: 1, + column: 8, + }, + }, + }, + computed: false, + optional: false, + range: [0, 8], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 8, + }, + }, + }, + arguments: [ + { + type: "Identifier", + decorators: [], + name: "a", + optional: false, + range: [9, 10], + loc: { + start: { + line: 1, + column: 9, + }, + end: { + line: 1, + column: 10, + }, + }, + }, + { + type: "TSAsExpression", + expression: { + type: "Identifier", + decorators: [], + name: "b", + optional: false, + range: [12, 13], + loc: { + start: { + line: 1, + column: 12, + }, + end: { + line: 1, + column: 13, + }, + }, + }, + typeAnnotation: { + type: "TSAnyKeyword", + range: [17, 20], + loc: { + start: { + line: 1, + column: 17, + }, + end: { + line: 1, + column: 20, + }, + }, + }, + range: [12, 20], + loc: { + start: { + line: 1, + column: 12, + }, + end: { + line: 1, + column: 20, + }, + }, + }, + ], + optional: false, + range: [0, 21], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + range: [0, 21], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + ], + comments: [], + range: [0, 21], + sourceType: "script", + tokens: [ + { + type: "Identifier", + value: "Math", + range: [0, 4], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 4, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [4, 5], + loc: { + start: { + line: 1, + column: 4, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + { + type: "Identifier", + value: "pow", + range: [5, 8], + loc: { + start: { + line: 1, + column: 5, + }, + end: { + line: 1, + column: 8, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [8, 9], + loc: { + start: { + line: 1, + column: 8, + }, + end: { + line: 1, + column: 9, + }, + }, + }, + { + type: "Identifier", + value: "a", + range: [9, 10], + loc: { + start: { + line: 1, + column: 9, + }, + end: { + line: 1, + column: 10, + }, + }, + }, + { + type: "Punctuator", + value: ",", + range: [10, 11], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 11, + }, + }, + }, + { + type: "Identifier", + value: "b", + range: [12, 13], + loc: { + start: { + line: 1, + column: 12, + }, + end: { + line: 1, + column: 13, + }, + }, + }, + { + type: "Identifier", + value: "as", + range: [14, 16], + loc: { + start: { + line: 1, + column: 14, + }, + end: { + line: 1, + column: 16, + }, + }, + }, + { + type: "Identifier", + value: "any", + range: [17, 20], + loc: { + start: { + line: 1, + column: 17, + }, + end: { + line: 1, + column: 20, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [20, 21], + loc: { + start: { + line: 1, + column: 20, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + ], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 21, + }, + }, +}); diff --git a/tests/fixtures/parsers/typescript-parsers/exponentiation-with-assertion-2.js b/tests/fixtures/parsers/typescript-parsers/exponentiation-with-assertion-2.js new file mode 100644 index 000000000000..cbdfa49e1421 --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/exponentiation-with-assertion-2.js @@ -0,0 +1,321 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser 5.59.11 (TS 5.1.3) + * Source code: + * Math.pow(a as any, b) + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "CallExpression", + callee: { + type: "MemberExpression", + object: { + type: "Identifier", + decorators: [], + name: "Math", + optional: false, + range: [0, 4], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 4, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "pow", + optional: false, + range: [5, 8], + loc: { + start: { + line: 1, + column: 5, + }, + end: { + line: 1, + column: 8, + }, + }, + }, + computed: false, + optional: false, + range: [0, 8], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 8, + }, + }, + }, + arguments: [ + { + type: "TSAsExpression", + expression: { + type: "Identifier", + decorators: [], + name: "a", + optional: false, + range: [9, 10], + loc: { + start: { + line: 1, + column: 9, + }, + end: { + line: 1, + column: 10, + }, + }, + }, + typeAnnotation: { + type: "TSAnyKeyword", + range: [14, 17], + loc: { + start: { + line: 1, + column: 14, + }, + end: { + line: 1, + column: 17, + }, + }, + }, + range: [9, 17], + loc: { + start: { + line: 1, + column: 9, + }, + end: { + line: 1, + column: 17, + }, + }, + }, + { + type: "Identifier", + decorators: [], + name: "b", + optional: false, + range: [19, 20], + loc: { + start: { + line: 1, + column: 19, + }, + end: { + line: 1, + column: 20, + }, + }, + }, + ], + optional: false, + range: [0, 21], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + range: [0, 21], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + ], + comments: [], + range: [0, 21], + sourceType: "script", + tokens: [ + { + type: "Identifier", + value: "Math", + range: [0, 4], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 4, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [4, 5], + loc: { + start: { + line: 1, + column: 4, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + { + type: "Identifier", + value: "pow", + range: [5, 8], + loc: { + start: { + line: 1, + column: 5, + }, + end: { + line: 1, + column: 8, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [8, 9], + loc: { + start: { + line: 1, + column: 8, + }, + end: { + line: 1, + column: 9, + }, + }, + }, + { + type: "Identifier", + value: "a", + range: [9, 10], + loc: { + start: { + line: 1, + column: 9, + }, + end: { + line: 1, + column: 10, + }, + }, + }, + { + type: "Identifier", + value: "as", + range: [11, 13], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 13, + }, + }, + }, + { + type: "Identifier", + value: "any", + range: [14, 17], + loc: { + start: { + line: 1, + column: 14, + }, + end: { + line: 1, + column: 17, + }, + }, + }, + { + type: "Punctuator", + value: ",", + range: [17, 18], + loc: { + start: { + line: 1, + column: 17, + }, + end: { + line: 1, + column: 18, + }, + }, + }, + { + type: "Identifier", + value: "b", + range: [19, 20], + loc: { + start: { + line: 1, + column: 19, + }, + end: { + line: 1, + column: 20, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [20, 21], + loc: { + start: { + line: 1, + column: 20, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + ], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 21, + }, + }, +}); diff --git a/tests/fixtures/parsers/typescript-parsers/exponentiation-with-assertion-3.js b/tests/fixtures/parsers/typescript-parsers/exponentiation-with-assertion-3.js new file mode 100644 index 000000000000..422d8736ff32 --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/exponentiation-with-assertion-3.js @@ -0,0 +1,321 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser 5.59.11 (TS 5.1.3) + * Source code: + * Math.pow(a, b) as any + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "TSAsExpression", + expression: { + type: "CallExpression", + callee: { + type: "MemberExpression", + object: { + type: "Identifier", + decorators: [], + name: "Math", + optional: false, + range: [0, 4], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 4, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "pow", + optional: false, + range: [5, 8], + loc: { + start: { + line: 1, + column: 5, + }, + end: { + line: 1, + column: 8, + }, + }, + }, + computed: false, + optional: false, + range: [0, 8], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 8, + }, + }, + }, + arguments: [ + { + type: "Identifier", + decorators: [], + name: "a", + optional: false, + range: [9, 10], + loc: { + start: { + line: 1, + column: 9, + }, + end: { + line: 1, + column: 10, + }, + }, + }, + { + type: "Identifier", + decorators: [], + name: "b", + optional: false, + range: [12, 13], + loc: { + start: { + line: 1, + column: 12, + }, + end: { + line: 1, + column: 13, + }, + }, + }, + ], + optional: false, + range: [0, 14], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 14, + }, + }, + }, + typeAnnotation: { + type: "TSAnyKeyword", + range: [18, 21], + loc: { + start: { + line: 1, + column: 18, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + range: [0, 21], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + range: [0, 21], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + ], + comments: [], + range: [0, 21], + sourceType: "script", + tokens: [ + { + type: "Identifier", + value: "Math", + range: [0, 4], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 4, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [4, 5], + loc: { + start: { + line: 1, + column: 4, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + { + type: "Identifier", + value: "pow", + range: [5, 8], + loc: { + start: { + line: 1, + column: 5, + }, + end: { + line: 1, + column: 8, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [8, 9], + loc: { + start: { + line: 1, + column: 8, + }, + end: { + line: 1, + column: 9, + }, + }, + }, + { + type: "Identifier", + value: "a", + range: [9, 10], + loc: { + start: { + line: 1, + column: 9, + }, + end: { + line: 1, + column: 10, + }, + }, + }, + { + type: "Punctuator", + value: ",", + range: [10, 11], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 11, + }, + }, + }, + { + type: "Identifier", + value: "b", + range: [12, 13], + loc: { + start: { + line: 1, + column: 12, + }, + end: { + line: 1, + column: 13, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [13, 14], + loc: { + start: { + line: 1, + column: 13, + }, + end: { + line: 1, + column: 14, + }, + }, + }, + { + type: "Identifier", + value: "as", + range: [15, 17], + loc: { + start: { + line: 1, + column: 15, + }, + end: { + line: 1, + column: 17, + }, + }, + }, + { + type: "Identifier", + value: "any", + range: [18, 21], + loc: { + start: { + line: 1, + column: 18, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + ], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 21, + }, + }, +}); diff --git a/tests/fixtures/parsers/typescript-parsers/logical-assignment-with-assertion.js b/tests/fixtures/parsers/typescript-parsers/logical-assignment-with-assertion.js new file mode 100644 index 000000000000..675559196e17 --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/logical-assignment-with-assertion.js @@ -0,0 +1,209 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser 5.59.11 (TS 5.1.3) + * Source code: + * a ||= b as number; + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "AssignmentExpression", + operator: "||=", + left: { + type: "Identifier", + decorators: [], + name: "a", + optional: false, + range: [0, 1], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 1, + }, + }, + }, + right: { + type: "TSAsExpression", + expression: { + type: "Identifier", + decorators: [], + name: "b", + optional: false, + range: [6, 7], + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 7, + }, + }, + }, + typeAnnotation: { + type: "TSNumberKeyword", + range: [11, 17], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 17, + }, + }, + }, + range: [6, 17], + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 17, + }, + }, + }, + range: [0, 17], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 17, + }, + }, + }, + range: [0, 18], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 18, + }, + }, + }, + ], + comments: [], + range: [0, 18], + sourceType: "script", + tokens: [ + { + type: "Identifier", + value: "a", + range: [0, 1], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 1, + }, + }, + }, + { + type: "Punctuator", + value: "||=", + range: [2, 5], + loc: { + start: { + line: 1, + column: 2, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + { + type: "Identifier", + value: "b", + range: [6, 7], + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 7, + }, + }, + }, + { + type: "Identifier", + value: "as", + range: [8, 10], + loc: { + start: { + line: 1, + column: 8, + }, + end: { + line: 1, + column: 10, + }, + }, + }, + { + type: "Identifier", + value: "number", + range: [11, 17], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 17, + }, + }, + }, + { + type: "Punctuator", + value: ";", + range: [17, 18], + loc: { + start: { + line: 1, + column: 17, + }, + end: { + line: 1, + column: 18, + }, + }, + }, + ], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 18, + }, + }, +}); diff --git a/tests/fixtures/parsers/typescript-parsers/logical-with-assignment-with-assertion-1.js b/tests/fixtures/parsers/typescript-parsers/logical-with-assignment-with-assertion-1.js new file mode 100644 index 000000000000..914ff4d0c241 --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/logical-with-assignment-with-assertion-1.js @@ -0,0 +1,538 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser 5.59.11 (TS 5.1.3) + * Source code: + * a.b.c || (a.b.c = d as number) + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "LogicalExpression", + operator: "||", + left: { + type: "MemberExpression", + object: { + type: "MemberExpression", + object: { + type: "Identifier", + decorators: [], + name: "a", + optional: false, + range: [0, 1], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 1, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "b", + optional: false, + range: [2, 3], + loc: { + start: { + line: 1, + column: 2, + }, + end: { + line: 1, + column: 3, + }, + }, + }, + computed: false, + optional: false, + range: [0, 3], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 3, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "c", + optional: false, + range: [4, 5], + loc: { + start: { + line: 1, + column: 4, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + computed: false, + optional: false, + range: [0, 5], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + right: { + type: "AssignmentExpression", + operator: "=", + left: { + type: "MemberExpression", + object: { + type: "MemberExpression", + object: { + type: "Identifier", + decorators: [], + name: "a", + optional: false, + range: [10, 11], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 11, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "b", + optional: false, + range: [12, 13], + loc: { + start: { + line: 1, + column: 12, + }, + end: { + line: 1, + column: 13, + }, + }, + }, + computed: false, + optional: false, + range: [10, 13], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 13, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "c", + optional: false, + range: [14, 15], + loc: { + start: { + line: 1, + column: 14, + }, + end: { + line: 1, + column: 15, + }, + }, + }, + computed: false, + optional: false, + range: [10, 15], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 15, + }, + }, + }, + right: { + type: "TSAsExpression", + expression: { + type: "Identifier", + decorators: [], + name: "d", + optional: false, + range: [18, 19], + loc: { + start: { + line: 1, + column: 18, + }, + end: { + line: 1, + column: 19, + }, + }, + }, + typeAnnotation: { + type: "TSNumberKeyword", + range: [23, 29], + loc: { + start: { + line: 1, + column: 23, + }, + end: { + line: 1, + column: 29, + }, + }, + }, + range: [18, 29], + loc: { + start: { + line: 1, + column: 18, + }, + end: { + line: 1, + column: 29, + }, + }, + }, + range: [10, 29], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 29, + }, + }, + }, + range: [0, 30], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 30, + }, + }, + }, + range: [0, 30], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 30, + }, + }, + }, + ], + comments: [], + range: [0, 30], + sourceType: "script", + tokens: [ + { + type: "Identifier", + value: "a", + range: [0, 1], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 1, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [1, 2], + loc: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 2, + }, + }, + }, + { + type: "Identifier", + value: "b", + range: [2, 3], + loc: { + start: { + line: 1, + column: 2, + }, + end: { + line: 1, + column: 3, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [3, 4], + loc: { + start: { + line: 1, + column: 3, + }, + end: { + line: 1, + column: 4, + }, + }, + }, + { + type: "Identifier", + value: "c", + range: [4, 5], + loc: { + start: { + line: 1, + column: 4, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + { + type: "Punctuator", + value: "||", + range: [6, 8], + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 8, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [9, 10], + loc: { + start: { + line: 1, + column: 9, + }, + end: { + line: 1, + column: 10, + }, + }, + }, + { + type: "Identifier", + value: "a", + range: [10, 11], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 11, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [11, 12], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 12, + }, + }, + }, + { + type: "Identifier", + value: "b", + range: [12, 13], + loc: { + start: { + line: 1, + column: 12, + }, + end: { + line: 1, + column: 13, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [13, 14], + loc: { + start: { + line: 1, + column: 13, + }, + end: { + line: 1, + column: 14, + }, + }, + }, + { + type: "Identifier", + value: "c", + range: [14, 15], + loc: { + start: { + line: 1, + column: 14, + }, + end: { + line: 1, + column: 15, + }, + }, + }, + { + type: "Punctuator", + value: "=", + range: [16, 17], + loc: { + start: { + line: 1, + column: 16, + }, + end: { + line: 1, + column: 17, + }, + }, + }, + { + type: "Identifier", + value: "d", + range: [18, 19], + loc: { + start: { + line: 1, + column: 18, + }, + end: { + line: 1, + column: 19, + }, + }, + }, + { + type: "Identifier", + value: "as", + range: [20, 22], + loc: { + start: { + line: 1, + column: 20, + }, + end: { + line: 1, + column: 22, + }, + }, + }, + { + type: "Identifier", + value: "number", + range: [23, 29], + loc: { + start: { + line: 1, + column: 23, + }, + end: { + line: 1, + column: 29, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [29, 30], + loc: { + start: { + line: 1, + column: 29, + }, + end: { + line: 1, + column: 30, + }, + }, + }, + ], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 30, + }, + }, +}); diff --git a/tests/fixtures/parsers/typescript-parsers/logical-with-assignment-with-assertion-2.js b/tests/fixtures/parsers/typescript-parsers/logical-with-assignment-with-assertion-2.js new file mode 100644 index 000000000000..644060ab1d74 --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/logical-with-assignment-with-assertion-2.js @@ -0,0 +1,568 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser 5.59.11 (TS 5.1.3) + * Source code: + * a.b.c || (a.b.c = (d as number)) + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "LogicalExpression", + operator: "||", + left: { + type: "MemberExpression", + object: { + type: "MemberExpression", + object: { + type: "Identifier", + decorators: [], + name: "a", + optional: false, + range: [0, 1], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 1, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "b", + optional: false, + range: [2, 3], + loc: { + start: { + line: 1, + column: 2, + }, + end: { + line: 1, + column: 3, + }, + }, + }, + computed: false, + optional: false, + range: [0, 3], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 3, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "c", + optional: false, + range: [4, 5], + loc: { + start: { + line: 1, + column: 4, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + computed: false, + optional: false, + range: [0, 5], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + right: { + type: "AssignmentExpression", + operator: "=", + left: { + type: "MemberExpression", + object: { + type: "MemberExpression", + object: { + type: "Identifier", + decorators: [], + name: "a", + optional: false, + range: [10, 11], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 11, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "b", + optional: false, + range: [12, 13], + loc: { + start: { + line: 1, + column: 12, + }, + end: { + line: 1, + column: 13, + }, + }, + }, + computed: false, + optional: false, + range: [10, 13], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 13, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "c", + optional: false, + range: [14, 15], + loc: { + start: { + line: 1, + column: 14, + }, + end: { + line: 1, + column: 15, + }, + }, + }, + computed: false, + optional: false, + range: [10, 15], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 15, + }, + }, + }, + right: { + type: "TSAsExpression", + expression: { + type: "Identifier", + decorators: [], + name: "d", + optional: false, + range: [19, 20], + loc: { + start: { + line: 1, + column: 19, + }, + end: { + line: 1, + column: 20, + }, + }, + }, + typeAnnotation: { + type: "TSNumberKeyword", + range: [24, 30], + loc: { + start: { + line: 1, + column: 24, + }, + end: { + line: 1, + column: 30, + }, + }, + }, + range: [19, 30], + loc: { + start: { + line: 1, + column: 19, + }, + end: { + line: 1, + column: 30, + }, + }, + }, + range: [10, 31], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 31, + }, + }, + }, + range: [0, 32], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 32, + }, + }, + }, + range: [0, 32], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 32, + }, + }, + }, + ], + comments: [], + range: [0, 32], + sourceType: "script", + tokens: [ + { + type: "Identifier", + value: "a", + range: [0, 1], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 1, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [1, 2], + loc: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 2, + }, + }, + }, + { + type: "Identifier", + value: "b", + range: [2, 3], + loc: { + start: { + line: 1, + column: 2, + }, + end: { + line: 1, + column: 3, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [3, 4], + loc: { + start: { + line: 1, + column: 3, + }, + end: { + line: 1, + column: 4, + }, + }, + }, + { + type: "Identifier", + value: "c", + range: [4, 5], + loc: { + start: { + line: 1, + column: 4, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + { + type: "Punctuator", + value: "||", + range: [6, 8], + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 8, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [9, 10], + loc: { + start: { + line: 1, + column: 9, + }, + end: { + line: 1, + column: 10, + }, + }, + }, + { + type: "Identifier", + value: "a", + range: [10, 11], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 11, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [11, 12], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 12, + }, + }, + }, + { + type: "Identifier", + value: "b", + range: [12, 13], + loc: { + start: { + line: 1, + column: 12, + }, + end: { + line: 1, + column: 13, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [13, 14], + loc: { + start: { + line: 1, + column: 13, + }, + end: { + line: 1, + column: 14, + }, + }, + }, + { + type: "Identifier", + value: "c", + range: [14, 15], + loc: { + start: { + line: 1, + column: 14, + }, + end: { + line: 1, + column: 15, + }, + }, + }, + { + type: "Punctuator", + value: "=", + range: [16, 17], + loc: { + start: { + line: 1, + column: 16, + }, + end: { + line: 1, + column: 17, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [18, 19], + loc: { + start: { + line: 1, + column: 18, + }, + end: { + line: 1, + column: 19, + }, + }, + }, + { + type: "Identifier", + value: "d", + range: [19, 20], + loc: { + start: { + line: 1, + column: 19, + }, + end: { + line: 1, + column: 20, + }, + }, + }, + { + type: "Identifier", + value: "as", + range: [21, 23], + loc: { + start: { + line: 1, + column: 21, + }, + end: { + line: 1, + column: 23, + }, + }, + }, + { + type: "Identifier", + value: "number", + range: [24, 30], + loc: { + start: { + line: 1, + column: 24, + }, + end: { + line: 1, + column: 30, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [30, 31], + loc: { + start: { + line: 1, + column: 30, + }, + end: { + line: 1, + column: 31, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [31, 32], + loc: { + start: { + line: 1, + column: 31, + }, + end: { + line: 1, + column: 32, + }, + }, + }, + ], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 32, + }, + }, +}); diff --git a/tests/fixtures/parsers/typescript-parsers/logical-with-assignment-with-assertion-3.js b/tests/fixtures/parsers/typescript-parsers/logical-with-assignment-with-assertion-3.js new file mode 100644 index 000000000000..8824fc2f6094 --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/logical-with-assignment-with-assertion-3.js @@ -0,0 +1,568 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser 5.59.11 (TS 5.1.3) + * Source code: + * (a.b.c || (a.b.c = d)) as number + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "TSAsExpression", + expression: { + type: "LogicalExpression", + operator: "||", + left: { + type: "MemberExpression", + object: { + type: "MemberExpression", + object: { + type: "Identifier", + decorators: [], + name: "a", + optional: false, + range: [1, 2], + loc: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 2, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "b", + optional: false, + range: [3, 4], + loc: { + start: { + line: 1, + column: 3, + }, + end: { + line: 1, + column: 4, + }, + }, + }, + computed: false, + optional: false, + range: [1, 4], + loc: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 4, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "c", + optional: false, + range: [5, 6], + loc: { + start: { + line: 1, + column: 5, + }, + end: { + line: 1, + column: 6, + }, + }, + }, + computed: false, + optional: false, + range: [1, 6], + loc: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 6, + }, + }, + }, + right: { + type: "AssignmentExpression", + operator: "=", + left: { + type: "MemberExpression", + object: { + type: "MemberExpression", + object: { + type: "Identifier", + decorators: [], + name: "a", + optional: false, + range: [11, 12], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 12, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "b", + optional: false, + range: [13, 14], + loc: { + start: { + line: 1, + column: 13, + }, + end: { + line: 1, + column: 14, + }, + }, + }, + computed: false, + optional: false, + range: [11, 14], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 14, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "c", + optional: false, + range: [15, 16], + loc: { + start: { + line: 1, + column: 15, + }, + end: { + line: 1, + column: 16, + }, + }, + }, + computed: false, + optional: false, + range: [11, 16], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 16, + }, + }, + }, + right: { + type: "Identifier", + decorators: [], + name: "d", + optional: false, + range: [19, 20], + loc: { + start: { + line: 1, + column: 19, + }, + end: { + line: 1, + column: 20, + }, + }, + }, + range: [11, 20], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 20, + }, + }, + }, + range: [1, 21], + loc: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + typeAnnotation: { + type: "TSNumberKeyword", + range: [26, 32], + loc: { + start: { + line: 1, + column: 26, + }, + end: { + line: 1, + column: 32, + }, + }, + }, + range: [0, 32], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 32, + }, + }, + }, + range: [0, 32], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 32, + }, + }, + }, + ], + comments: [], + range: [0, 32], + sourceType: "script", + tokens: [ + { + type: "Punctuator", + value: "(", + range: [0, 1], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 1, + }, + }, + }, + { + type: "Identifier", + value: "a", + range: [1, 2], + loc: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 2, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [2, 3], + loc: { + start: { + line: 1, + column: 2, + }, + end: { + line: 1, + column: 3, + }, + }, + }, + { + type: "Identifier", + value: "b", + range: [3, 4], + loc: { + start: { + line: 1, + column: 3, + }, + end: { + line: 1, + column: 4, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [4, 5], + loc: { + start: { + line: 1, + column: 4, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + { + type: "Identifier", + value: "c", + range: [5, 6], + loc: { + start: { + line: 1, + column: 5, + }, + end: { + line: 1, + column: 6, + }, + }, + }, + { + type: "Punctuator", + value: "||", + range: [7, 9], + loc: { + start: { + line: 1, + column: 7, + }, + end: { + line: 1, + column: 9, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [10, 11], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 11, + }, + }, + }, + { + type: "Identifier", + value: "a", + range: [11, 12], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 12, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [12, 13], + loc: { + start: { + line: 1, + column: 12, + }, + end: { + line: 1, + column: 13, + }, + }, + }, + { + type: "Identifier", + value: "b", + range: [13, 14], + loc: { + start: { + line: 1, + column: 13, + }, + end: { + line: 1, + column: 14, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [14, 15], + loc: { + start: { + line: 1, + column: 14, + }, + end: { + line: 1, + column: 15, + }, + }, + }, + { + type: "Identifier", + value: "c", + range: [15, 16], + loc: { + start: { + line: 1, + column: 15, + }, + end: { + line: 1, + column: 16, + }, + }, + }, + { + type: "Punctuator", + value: "=", + range: [17, 18], + loc: { + start: { + line: 1, + column: 17, + }, + end: { + line: 1, + column: 18, + }, + }, + }, + { + type: "Identifier", + value: "d", + range: [19, 20], + loc: { + start: { + line: 1, + column: 19, + }, + end: { + line: 1, + column: 20, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [20, 21], + loc: { + start: { + line: 1, + column: 20, + }, + end: { + line: 1, + column: 21, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [21, 22], + loc: { + start: { + line: 1, + column: 21, + }, + end: { + line: 1, + column: 22, + }, + }, + }, + { + type: "Identifier", + value: "as", + range: [23, 25], + loc: { + start: { + line: 1, + column: 23, + }, + end: { + line: 1, + column: 25, + }, + }, + }, + { + type: "Identifier", + value: "number", + range: [26, 32], + loc: { + start: { + line: 1, + column: 26, + }, + end: { + line: 1, + column: 32, + }, + }, + }, + ], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 32, + }, + }, +}); diff --git a/tests/fixtures/parsers/typescript-parsers/member-call-expr-with-assertion.js b/tests/fixtures/parsers/typescript-parsers/member-call-expr-with-assertion.js new file mode 100644 index 000000000000..e26e115e94e1 --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/member-call-expr-with-assertion.js @@ -0,0 +1,366 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser 5.59.11 (TS 5.1.3) + * Source code: + * const x = (1 satisfies number).toFixed(); + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "VariableDeclaration", + declarations: [ + { + type: "VariableDeclarator", + definite: false, + id: { + type: "Identifier", + decorators: [], + name: "x", + optional: false, + range: [6, 7], + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 7, + }, + }, + }, + init: { + type: "CallExpression", + callee: { + type: "MemberExpression", + object: { + type: "TSSatisfiesExpression", + expression: { + type: "Literal", + value: 1, + raw: "1", + range: [11, 12], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 12, + }, + }, + }, + typeAnnotation: { + type: "TSNumberKeyword", + range: [23, 29], + loc: { + start: { + line: 1, + column: 23, + }, + end: { + line: 1, + column: 29, + }, + }, + }, + range: [11, 29], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 29, + }, + }, + }, + property: { + type: "Identifier", + decorators: [], + name: "toFixed", + optional: false, + range: [31, 38], + loc: { + start: { + line: 1, + column: 31, + }, + end: { + line: 1, + column: 38, + }, + }, + }, + computed: false, + optional: false, + range: [10, 38], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 38, + }, + }, + }, + arguments: [], + optional: false, + range: [10, 40], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 40, + }, + }, + }, + range: [6, 40], + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 40, + }, + }, + }, + ], + declare: false, + kind: "const", + range: [0, 41], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 41, + }, + }, + }, + ], + comments: [], + range: [0, 41], + sourceType: "script", + tokens: [ + { + type: "Keyword", + value: "const", + range: [0, 5], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + { + type: "Identifier", + value: "x", + range: [6, 7], + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 7, + }, + }, + }, + { + type: "Punctuator", + value: "=", + range: [8, 9], + loc: { + start: { + line: 1, + column: 8, + }, + end: { + line: 1, + column: 9, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [10, 11], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 11, + }, + }, + }, + { + type: "Numeric", + value: "1", + range: [11, 12], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 12, + }, + }, + }, + { + type: "Identifier", + value: "satisfies", + range: [13, 22], + loc: { + start: { + line: 1, + column: 13, + }, + end: { + line: 1, + column: 22, + }, + }, + }, + { + type: "Identifier", + value: "number", + range: [23, 29], + loc: { + start: { + line: 1, + column: 23, + }, + end: { + line: 1, + column: 29, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [29, 30], + loc: { + start: { + line: 1, + column: 29, + }, + end: { + line: 1, + column: 30, + }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [30, 31], + loc: { + start: { + line: 1, + column: 30, + }, + end: { + line: 1, + column: 31, + }, + }, + }, + { + type: "Identifier", + value: "toFixed", + range: [31, 38], + loc: { + start: { + line: 1, + column: 31, + }, + end: { + line: 1, + column: 38, + }, + }, + }, + { + type: "Punctuator", + value: "(", + range: [38, 39], + loc: { + start: { + line: 1, + column: 38, + }, + end: { + line: 1, + column: 39, + }, + }, + }, + { + type: "Punctuator", + value: ")", + range: [39, 40], + loc: { + start: { + line: 1, + column: 39, + }, + end: { + line: 1, + column: 40, + }, + }, + }, + { + type: "Punctuator", + value: ";", + range: [40, 41], + loc: { + start: { + line: 1, + column: 40, + }, + end: { + line: 1, + column: 41, + }, + }, + }, + ], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 41, + }, + }, +}); diff --git a/tests/fixtures/parsers/typescript-parsers/unneeded-ternary-1.js b/tests/fixtures/parsers/typescript-parsers/unneeded-ternary-1.js new file mode 100644 index 000000000000..2b44484d8924 --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/unneeded-ternary-1.js @@ -0,0 +1,238 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser 5.59.11 (TS 5.1.3) + * Source code: + * foo as any ? false : true + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "ConditionalExpression", + test: { + type: "TSAsExpression", + expression: { + type: "Identifier", + decorators: [], + name: "foo", + optional: false, + range: [0, 3], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 3, + }, + }, + }, + typeAnnotation: { + type: "TSAnyKeyword", + range: [7, 10], + loc: { + start: { + line: 1, + column: 7, + }, + end: { + line: 1, + column: 10, + }, + }, + }, + range: [0, 10], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 10, + }, + }, + }, + consequent: { + type: "Literal", + value: false, + raw: "false", + range: [13, 18], + loc: { + start: { + line: 1, + column: 13, + }, + end: { + line: 1, + column: 18, + }, + }, + }, + alternate: { + type: "Literal", + value: true, + raw: "true", + range: [21, 25], + loc: { + start: { + line: 1, + column: 21, + }, + end: { + line: 1, + column: 25, + }, + }, + }, + range: [0, 25], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 25, + }, + }, + }, + range: [0, 25], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 25, + }, + }, + }, + ], + comments: [], + range: [0, 25], + sourceType: "script", + tokens: [ + { + type: "Identifier", + value: "foo", + range: [0, 3], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 3, + }, + }, + }, + { + type: "Identifier", + value: "as", + range: [4, 6], + loc: { + start: { + line: 1, + column: 4, + }, + end: { + line: 1, + column: 6, + }, + }, + }, + { + type: "Identifier", + value: "any", + range: [7, 10], + loc: { + start: { + line: 1, + column: 7, + }, + end: { + line: 1, + column: 10, + }, + }, + }, + { + type: "Punctuator", + value: "?", + range: [11, 12], + loc: { + start: { + line: 1, + column: 11, + }, + end: { + line: 1, + column: 12, + }, + }, + }, + { + type: "Boolean", + value: "false", + range: [13, 18], + loc: { + start: { + line: 1, + column: 13, + }, + end: { + line: 1, + column: 18, + }, + }, + }, + { + type: "Punctuator", + value: ":", + range: [19, 20], + loc: { + start: { + line: 1, + column: 19, + }, + end: { + line: 1, + column: 20, + }, + }, + }, + { + type: "Boolean", + value: "true", + range: [21, 25], + loc: { + start: { + line: 1, + column: 21, + }, + end: { + line: 1, + column: 25, + }, + }, + }, + ], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 25, + }, + }, +}); diff --git a/tests/fixtures/parsers/typescript-parsers/unneeded-ternary-2.js b/tests/fixtures/parsers/typescript-parsers/unneeded-ternary-2.js new file mode 100644 index 000000000000..eaac150cf1ab --- /dev/null +++ b/tests/fixtures/parsers/typescript-parsers/unneeded-ternary-2.js @@ -0,0 +1,240 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser 5.59.11 (TS 5.1.3) + * Source code: + * foo ? foo : bar as any + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "ConditionalExpression", + test: { + type: "Identifier", + decorators: [], + name: "foo", + optional: false, + range: [0, 3], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 3, + }, + }, + }, + consequent: { + type: "Identifier", + decorators: [], + name: "foo", + optional: false, + range: [6, 9], + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 9, + }, + }, + }, + alternate: { + type: "TSAsExpression", + expression: { + type: "Identifier", + decorators: [], + name: "bar", + optional: false, + range: [12, 15], + loc: { + start: { + line: 1, + column: 12, + }, + end: { + line: 1, + column: 15, + }, + }, + }, + typeAnnotation: { + type: "TSAnyKeyword", + range: [19, 22], + loc: { + start: { + line: 1, + column: 19, + }, + end: { + line: 1, + column: 22, + }, + }, + }, + range: [12, 22], + loc: { + start: { + line: 1, + column: 12, + }, + end: { + line: 1, + column: 22, + }, + }, + }, + range: [0, 22], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 22, + }, + }, + }, + range: [0, 22], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 22, + }, + }, + }, + ], + comments: [], + range: [0, 22], + sourceType: "script", + tokens: [ + { + type: "Identifier", + value: "foo", + range: [0, 3], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 3, + }, + }, + }, + { + type: "Punctuator", + value: "?", + range: [4, 5], + loc: { + start: { + line: 1, + column: 4, + }, + end: { + line: 1, + column: 5, + }, + }, + }, + { + type: "Identifier", + value: "foo", + range: [6, 9], + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 9, + }, + }, + }, + { + type: "Punctuator", + value: ":", + range: [10, 11], + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 11, + }, + }, + }, + { + type: "Identifier", + value: "bar", + range: [12, 15], + loc: { + start: { + line: 1, + column: 12, + }, + end: { + line: 1, + column: 15, + }, + }, + }, + { + type: "Identifier", + value: "as", + range: [16, 18], + loc: { + start: { + line: 1, + column: 16, + }, + end: { + line: 1, + column: 18, + }, + }, + }, + { + type: "Identifier", + value: "any", + range: [19, 22], + loc: { + start: { + line: 1, + column: 19, + }, + end: { + line: 1, + column: 22, + }, + }, + }, + ], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 22, + }, + }, +}); diff --git a/tests/lib/rules/logical-assignment-operators.js b/tests/lib/rules/logical-assignment-operators.js index ba839b5c6a80..36756815a90f 100644 --- a/tests/lib/rules/logical-assignment-operators.js +++ b/tests/lib/rules/logical-assignment-operators.js @@ -9,7 +9,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/logical-assignment-operators"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + parser = require("../../fixtures/fixture-parser"); //------------------------------------------------------------------------------ // Tests @@ -1456,5 +1457,60 @@ ruleTester.run("logical-assignment-operators", rule, { output: "a = a ?? b + c", options: ["never"], errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "??=" } }] - }] + }, + + // https://github.com/eslint/eslint/issues/17173 + { + code: "a ||= b as number;", + output: "a = a || (b as number);", + options: ["never"], + parser: parser("typescript-parsers/logical-assignment-with-assertion"), + errors: [{ messageId: "unexpected", type: "AssignmentExpression", data: { operator: "||=" } }] + }, + { + code: "a.b.c || (a.b.c = d as number)", + output: null, + parser: parser("typescript-parsers/logical-with-assignment-with-assertion-1"), + errors: [{ + messageId: "logical", + type: "LogicalExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "convertLogical", + data: { operator: "||=" }, + output: "a.b.c ||= d as number" + }] + }] + }, + { + code: "a.b.c || (a.b.c = (d as number))", + output: null, + parser: parser("typescript-parsers/logical-with-assignment-with-assertion-2"), + errors: [{ + messageId: "logical", + type: "LogicalExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "convertLogical", + data: { operator: "||=" }, + output: "a.b.c ||= (d as number)" + }] + }] + }, + { + code: "(a.b.c || (a.b.c = d)) as number", + output: null, + parser: parser("typescript-parsers/logical-with-assignment-with-assertion-3"), + errors: [{ + messageId: "logical", + type: "LogicalExpression", + data: { operator: "||=" }, + suggestions: [{ + messageId: "convertLogical", + data: { operator: "||=" }, + output: "(a.b.c ||= d) as number" + }] + }] + } + ] }); diff --git a/tests/lib/rules/no-extra-boolean-cast.js b/tests/lib/rules/no-extra-boolean-cast.js index 8aecb4ca37f4..2e95cb740bb2 100644 --- a/tests/lib/rules/no-extra-boolean-cast.js +++ b/tests/lib/rules/no-extra-boolean-cast.js @@ -10,7 +10,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/no-extra-boolean-cast"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + parser = require("../../fixtures/fixture-parser"); //------------------------------------------------------------------------------ // Tests @@ -2423,6 +2424,15 @@ ruleTester.run("no-extra-boolean-cast", rule, { options: [{ enforceForLogicalOperands: true }], parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "unexpectedCall" }] + }, + + // https://github.com/eslint/eslint/issues/17173 + { + code: "if (!Boolean(a as any)) { }", + output: "if (!(a as any)) { }", + parser: parser("typescript-parsers/boolean-cast-with-assertion"), + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpectedCall" }] } ] }); diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js index 71e1e2e98457..c160ace3c36a 100644 --- a/tests/lib/rules/no-extra-parens.js +++ b/tests/lib/rules/no-extra-parens.js @@ -10,7 +10,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/no-extra-parens"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + parser = require("../../fixtures/fixture-parser"); //------------------------------------------------------------------------------ // Helpers @@ -783,6 +784,12 @@ ruleTester.run("no-extra-parens", rule, { { code: "((a)) = function () {};", options: ["functions"] + }, + + // https://github.com/eslint/eslint/issues/17173 + { + code: "const x = (1 satisfies number).toFixed();", + parser: parser("typescript-parsers/member-call-expr-with-assertion") } ], diff --git a/tests/lib/rules/no-unneeded-ternary.js b/tests/lib/rules/no-unneeded-ternary.js index 7ad11d2b2e51..3714e70bec8c 100644 --- a/tests/lib/rules/no-unneeded-ternary.js +++ b/tests/lib/rules/no-unneeded-ternary.js @@ -10,7 +10,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/no-unneeded-ternary"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + parser = require("../../fixtures/fixture-parser"); //------------------------------------------------------------------------------ // Tests @@ -427,6 +428,29 @@ ruleTester.run("no-unneeded-ternary", rule, { endLine: 1, endColumn: 27 }] + }, + + // https://github.com/eslint/eslint/issues/17173 + { + code: "foo as any ? false : true", + output: "!(foo as any)", + parser: parser("typescript-parsers/unneeded-ternary-1"), + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unnecessaryConditionalExpression", + type: "ConditionalExpression" + }] + }, + { + code: "foo ? foo : bar as any", + output: "foo || (bar as any)", + options: [{ defaultAssignment: false }], + parser: parser("typescript-parsers/unneeded-ternary-2"), + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unnecessaryConditionalAssignment", + type: "ConditionalExpression" + }] } ] }); diff --git a/tests/lib/rules/prefer-exponentiation-operator.js b/tests/lib/rules/prefer-exponentiation-operator.js index 2de358e2c8fc..8765330c00f0 100644 --- a/tests/lib/rules/prefer-exponentiation-operator.js +++ b/tests/lib/rules/prefer-exponentiation-operator.js @@ -11,6 +11,7 @@ const rule = require("../../../lib/rules/prefer-exponentiation-operator"); const { RuleTester } = require("../../../lib/rule-tester"); +const parser = require("../../fixtures/fixture-parser"); //------------------------------------------------------------------------------ // Helpers @@ -357,6 +358,41 @@ ruleTester.run("prefer-exponentiation-operator", rule, { invalid("Math?.pow(a, b)", "a**b"), invalid("Math?.pow?.(a, b)", "a**b"), invalid("(Math?.pow)(a, b)", "a**b"), - invalid("(Math?.pow)?.(a, b)", "a**b") + invalid("(Math?.pow)?.(a, b)", "a**b"), + + // https://github.com/eslint/eslint/issues/17173 + { + code: "Math.pow(a, b as any)", + output: "a**(b as any)", + parser: parser("typescript-parsers/exponentiation-with-assertion-1"), + errors: [ + { + messageId: "useExponentiation", + type: "CallExpression" + } + ] + }, + { + code: "Math.pow(a as any, b)", + output: "(a as any)**b", + parser: parser("typescript-parsers/exponentiation-with-assertion-2"), + errors: [ + { + messageId: "useExponentiation", + type: "CallExpression" + } + ] + }, + { + code: "Math.pow(a, b) as any", + output: "(a**b) as any", + parser: parser("typescript-parsers/exponentiation-with-assertion-3"), + errors: [ + { + messageId: "useExponentiation", + type: "CallExpression" + } + ] + } ] }); From 7620b891e81c234f30f9dbcceb64a05fd0dde65e Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Wed, 28 Jun 2023 14:14:15 +0200 Subject: [PATCH 059/248] fix: Remove `no-unused-labels` autofix before potential directives (#17314) --- docs/src/rules/no-unused-labels.md | 2 + lib/rules/dot-notation.js | 3 +- lib/rules/no-restricted-modules.js | 17 ++++---- lib/rules/no-unused-labels.js | 59 +++++++++++++++++++++------ lib/rules/prefer-regex-literals.js | 15 ++----- lib/rules/utils/ast-utils.js | 9 +++++ lib/rules/valid-typeof.js | 8 +++- lib/rules/yoda.js | 13 +----- tests/lib/rules/no-unused-labels.js | 63 +++++++++++++++++++++++++++++ tests/lib/rules/utils/ast-utils.js | 18 +++++++++ 10 files changed, 158 insertions(+), 49 deletions(-) diff --git a/docs/src/rules/no-unused-labels.md b/docs/src/rules/no-unused-labels.md index 971c56722035..fcbe2e179abe 100644 --- a/docs/src/rules/no-unused-labels.md +++ b/docs/src/rules/no-unused-labels.md @@ -30,6 +30,8 @@ Such labels take up space in the code and can lead to confusion by readers. This rule is aimed at eliminating unused labels. +Problems reported by this rule can be fixed automatically, except when there are any comments between the label and the following statement, or when removing a label would cause the following statement to become a directive such as `"use strict"`. + Examples of **incorrect** code for this rule: ::: incorrect diff --git a/lib/rules/dot-notation.js b/lib/rules/dot-notation.js index 78f136867742..21cba54e2a52 100644 --- a/lib/rules/dot-notation.js +++ b/lib/rules/dot-notation.js @@ -133,8 +133,7 @@ module.exports = { } if ( node.computed && - node.property.type === "TemplateLiteral" && - node.property.expressions.length === 0 + astUtils.isStaticTemplateLiteral(node.property) ) { checkComputedProperty(node, node.property.quasis[0].value.cooked); } diff --git a/lib/rules/no-restricted-modules.js b/lib/rules/no-restricted-modules.js index d79bdbe57485..4a9b0a4c0368 100644 --- a/lib/rules/no-restricted-modules.js +++ b/lib/rules/no-restricted-modules.js @@ -5,6 +5,12 @@ */ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -116,15 +122,6 @@ module.exports = { return node && node.type === "Literal" && typeof node.value === "string"; } - /** - * Function to check if a node is a static string template literal. - * @param {ASTNode} node The node to check. - * @returns {boolean} If the node is a string template literal. - */ - function isStaticTemplateLiteral(node) { - return node && node.type === "TemplateLiteral" && node.expressions.length === 0; - } - /** * Function to check if a node is a require call. * @param {ASTNode} node The node to check. @@ -144,7 +141,7 @@ module.exports = { return node.value.trim(); } - if (isStaticTemplateLiteral(node)) { + if (astUtils.isStaticTemplateLiteral(node)) { return node.quasis[0].value.cooked.trim(); } diff --git a/lib/rules/no-unused-labels.js b/lib/rules/no-unused-labels.js index 9aa1067c681c..be06b324c49c 100644 --- a/lib/rules/no-unused-labels.js +++ b/lib/rules/no-unused-labels.js @@ -5,6 +5,12 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -46,6 +52,45 @@ module.exports = { }; } + /** + * Checks if a `LabeledStatement` node is fixable. + * For a node to be fixable, there must be no comments between the label and the body. + * Furthermore, is must be possible to remove the label without turning the body statement into a + * directive after other fixes are applied. + * @param {ASTNode} node The node to evaluate. + * @returns {boolean} Whether or not the node is fixable. + */ + function isFixable(node) { + + /* + * Only perform a fix if there are no comments between the label and the body. This will be the case + * when there is exactly one token/comment (the ":") between the label and the body. + */ + if (sourceCode.getTokenAfter(node.label, { includeComments: true }) !== + sourceCode.getTokenBefore(node.body, { includeComments: true })) { + return false; + } + + // Looking for the node's deepest ancestor which is not a `LabeledStatement`. + let ancestor = node.parent; + + while (ancestor.type === "LabeledStatement") { + ancestor = ancestor.parent; + } + + if (ancestor.type === "Program" || + (ancestor.type === "BlockStatement" && astUtils.isFunction(ancestor.parent))) { + const { body } = node; + + if (body.type === "ExpressionStatement" && + ((body.expression.type === "Literal" && typeof body.expression.value === "string") || + astUtils.isStaticTemplateLiteral(body.expression))) { + return false; // potential directive + } + } + return true; + } + /** * Removes the top of the stack. * At the same time, this reports the label if it's never used. @@ -58,19 +103,7 @@ module.exports = { node: node.label, messageId: "unused", data: node.label, - fix(fixer) { - - /* - * Only perform a fix if there are no comments between the label and the body. This will be the case - * when there is exactly one token/comment (the ":") between the label and the body. - */ - if (sourceCode.getTokenAfter(node.label, { includeComments: true }) === - sourceCode.getTokenBefore(node.body, { includeComments: true })) { - return fixer.removeRange([node.range[0], node.body.range[0]]); - } - - return null; - } + fix: isFixable(node) ? fixer => fixer.removeRange([node.range[0], node.body.range[0]]) : null }); } diff --git a/lib/rules/prefer-regex-literals.js b/lib/rules/prefer-regex-literals.js index 39e29506421c..eca805483f4a 100644 --- a/lib/rules/prefer-regex-literals.js +++ b/lib/rules/prefer-regex-literals.js @@ -37,15 +37,6 @@ function isRegexLiteral(node) { return node.type === "Literal" && Object.prototype.hasOwnProperty.call(node, "regex"); } -/** - * Determines whether the given node is a template literal without expressions. - * @param {ASTNode} node Node to check. - * @returns {boolean} True if the node is a template literal without expressions. - */ -function isStaticTemplateLiteral(node) { - return node.type === "TemplateLiteral" && node.expressions.length === 0; -} - const validPrecedingTokens = new Set([ "(", ";", @@ -178,7 +169,7 @@ module.exports = { return node.type === "TaggedTemplateExpression" && astUtils.isSpecificMemberAccess(node.tag, "String", "raw") && isGlobalReference(astUtils.skipChainExpression(node.tag).object) && - isStaticTemplateLiteral(node.quasi); + astUtils.isStaticTemplateLiteral(node.quasi); } /** @@ -191,7 +182,7 @@ module.exports = { return node.value; } - if (isStaticTemplateLiteral(node)) { + if (astUtils.isStaticTemplateLiteral(node)) { return node.quasis[0].value.cooked; } @@ -209,7 +200,7 @@ module.exports = { */ function isStaticString(node) { return isStringLiteral(node) || - isStaticTemplateLiteral(node) || + astUtils.isStaticTemplateLiteral(node) || isStringRawTaggedStaticTemplateLiteral(node); } diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index 39e53b1057db..e8ed3edd85c8 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -2133,6 +2133,15 @@ module.exports = { return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString); }, + /** + * Determines whether the given node is a template literal without expressions. + * @param {ASTNode} node Node to check. + * @returns {boolean} True if the node is a template literal without expressions. + */ + isStaticTemplateLiteral(node) { + return node.type === "TemplateLiteral" && node.expressions.length === 0; + }, + isReferenceToGlobalVariable, isLogicalExpression, isCoalesceExpression, diff --git a/lib/rules/valid-typeof.js b/lib/rules/valid-typeof.js index 82af130f98ab..3818dafeae9f 100644 --- a/lib/rules/valid-typeof.js +++ b/lib/rules/valid-typeof.js @@ -4,6 +4,12 @@ */ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -88,7 +94,7 @@ module.exports = { if (parent.type === "BinaryExpression" && OPERATORS.has(parent.operator)) { const sibling = parent.left === node ? parent.right : parent.left; - if (sibling.type === "Literal" || sibling.type === "TemplateLiteral" && !sibling.expressions.length) { + if (sibling.type === "Literal" || astUtils.isStaticTemplateLiteral(sibling)) { const value = sibling.type === "Literal" ? sibling.value : sibling.quasis[0].value.cooked; if (!VALID_TYPES.has(value)) { diff --git a/lib/rules/yoda.js b/lib/rules/yoda.js index 60a6ad2f2dbd..af8f525182ed 100644 --- a/lib/rules/yoda.js +++ b/lib/rules/yoda.js @@ -58,22 +58,13 @@ function isNegativeNumericLiteral(node) { ); } -/** - * Determines whether a node is a Template Literal which can be determined statically. - * @param {ASTNode} node Node to test - * @returns {boolean} True if the node is a Template Literal without expression. - */ -function isStaticTemplateLiteral(node) { - return node.type === "TemplateLiteral" && node.expressions.length === 0; -} - /** * Determines whether a non-Literal node should be treated as a single Literal node. * @param {ASTNode} node Node to test * @returns {boolean} True if the node should be treated as a single Literal node. */ function looksLikeLiteral(node) { - return isNegativeNumericLiteral(node) || isStaticTemplateLiteral(node); + return isNegativeNumericLiteral(node) || astUtils.isStaticTemplateLiteral(node); } /** @@ -100,7 +91,7 @@ function getNormalizedLiteral(node) { }; } - if (isStaticTemplateLiteral(node)) { + if (astUtils.isStaticTemplateLiteral(node)) { return { type: "Literal", value: node.quasis[0].value.cooked, diff --git a/tests/lib/rules/no-unused-labels.js b/tests/lib/rules/no-unused-labels.js index feb6c82d5f06..46b7b1e13e27 100644 --- a/tests/lib/rules/no-unused-labels.js +++ b/tests/lib/rules/no-unused-labels.js @@ -73,6 +73,69 @@ ruleTester.run("no-unused-labels", rule, { code: "A /* comment */: foo", output: null, errors: [{ messageId: "unused" }] + }, + + // https://github.com/eslint/eslint/issues/16988 + { + code: 'A: "use strict"', + output: null, + errors: [{ messageId: "unused" }] + }, + { + code: '"use strict"; foo: "bar"', + output: null, + errors: [{ messageId: "unused" }] + }, + { + code: 'A: ("use strict")', // Parentheses may be removed by another rule. + output: null, + errors: [{ messageId: "unused" }] + }, + { + code: "A: `use strict`", // `use strict` may be changed to "use strict" by another rule. + output: null, + parserOptions: { ecmaVersion: 6 }, + errors: [{ messageId: "unused" }] + }, + { + code: "if (foo) { bar: 'baz' }", + output: "if (foo) { 'baz' }", + errors: [{ messageId: "unused" }] + }, + { + code: "A: B: 'foo'", + output: "B: 'foo'", + errors: [{ messageId: "unused" }, { messageId: "unused" }] + }, + { + code: "A: B: C: 'foo'", + output: "B: C: 'foo'", // Becomes "C: 'foo'" on the second pass. + errors: [{ messageId: "unused" }, { messageId: "unused" }, { messageId: "unused" }] + }, + { + code: "A: B: C: D: 'foo'", + output: "B: D: 'foo'", // Becomes "D: 'foo'" on the second pass. + errors: [ + { messageId: "unused" }, + { messageId: "unused" }, + { messageId: "unused" }, + { messageId: "unused" }] + }, + { + code: "A: B: C: D: E: 'foo'", + output: "B: D: E: 'foo'", // Becomes "E: 'foo'" on the third pass. + errors: [ + { messageId: "unused" }, + { messageId: "unused" }, + { messageId: "unused" }, + { messageId: "unused" }, + { messageId: "unused" } + ] + }, + { + code: "A: 42", + output: "42", + errors: [{ messageId: "unused" }] } /* diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js index b0b9cb57cdf8..59486b237ef4 100644 --- a/tests/lib/rules/utils/ast-utils.js +++ b/tests/lib/rules/utils/ast-utils.js @@ -1857,4 +1857,22 @@ describe("ast-utils", () => { }); }); }); + + describe("isStaticTemplateLiteral", () => { + const expectedResults = { + "``": true, + "`foo`": true, + "`foo${bar}`": false, + "\"foo\"": false, + "foo`bar`": false + }; + + Object.entries(expectedResults).forEach(([code, expectedResult]) => { + it(`returns ${expectedResult} for ${code}`, () => { + const ast = espree.parse(code, { ecmaVersion: 6 }); + + assert.strictEqual(astUtils.isStaticTemplateLiteral(ast.body[0].expression), expectedResult); + }); + }); + }); }); From cf884390ad8071d88eae05df9321100f1770363d Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 28 Jun 2023 20:15:27 +0200 Subject: [PATCH 060/248] chore: upgrade optionator@0.9.3 (#17319) Fixes #17117 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c71234373479..f705b0967ff8 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" From c8b1f4d61a256727755d561bf53f889b6cd712e0 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 28 Jun 2023 20:32:03 +0200 Subject: [PATCH 061/248] feat: Move `parserServices` to `SourceCode` (#17311) Deprecates `context.parserServices` in favor of `SourceCode#parserServices` Refs #16999 --- docs/src/extend/custom-parsers.md | 4 ++-- docs/src/extend/custom-rules.md | 3 ++- tests/lib/linter/linter.js | 12 ++++++++---- tests/lib/rule-tester/flat-rule-tester.js | 4 +++- tests/lib/rule-tester/rule-tester.js | 4 +++- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/src/extend/custom-parsers.md b/docs/src/extend/custom-parsers.md index 388f54b726b0..a7327fb91f60 100644 --- a/docs/src/extend/custom-parsers.md +++ b/docs/src/extend/custom-parsers.md @@ -42,7 +42,7 @@ The `parse` method should simply return the [AST](#ast-specification) object. The `parseForESLint` method should return an object that contains the required property `ast` and optional properties `services`, `scopeManager`, and `visitorKeys`. * `ast` should contain the [AST](#ast-specification) object. -* `services` can contain any parser-dependent services (such as type checkers for nodes). The value of the `services` property is available to rules as `context.parserServices`. Default is an empty object. +* `services` can contain any parser-dependent services (such as type checkers for nodes). The value of the `services` property is available to rules as `context.sourceCode.parserServices`. Default is an empty object. * `scopeManager` can be a [ScopeManager](./scope-manager-interface) object. Custom parsers can use customized scope analysis for experimental/enhancement syntaxes. The default is the `ScopeManager` object which is created by [eslint-scope](https://github.com/eslint/eslint-scope). * Support for `scopeManager` was added in ESLint v4.14.0. ESLint versions that support `scopeManager` will provide an `eslintScopeManager: true` property in `parserOptions`, which can be used for feature detection. * `visitorKeys` can be an object to customize AST traversal. The keys of the object are the type of AST nodes. Each value is an array of the property names which should be traversed. The default is [KEYS of `eslint-visitor-keys`](https://github.com/eslint/eslint-visitor-keys#evkkeys). @@ -120,7 +120,7 @@ To learn more about using ESLint parsers in your project, refer to [Configure a For a complex example of a custom parser, refer to the [`@typescript-eslint/parser`](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/parser) source code. -A simple custom parser that provides a `context.parserServices.foo()` method to rules. +A simple custom parser that provides a `context.sourceCode.parserServices.foo()` method to rules. ```javascript // awesome-custom-parser.js diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index 76c9e88741dd..944d334c0f5d 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -131,7 +131,7 @@ The `context` object has the following properties: * `sourceCode`: (`object`) A `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). * `settings`: (`object`) The [shared settings](../use/configure/configuration-files#adding-shared-settings) from the configuration. * `parserPath`: (`string`) The name of the `parser` from the configuration. -* `parserServices`: (`object`) Contains parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) +* `parserServices`: (**Deprecated:** Use `SourceCode#parserServices` instead.) Contains parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) * `parserOptions`: The parser options configured for this run (more details [here](../use/configure/language-options#specifying-parser-options)). Additionally, the `context` object has the following methods: @@ -575,6 +575,7 @@ There are also some properties you can access: * `ast`: (`object`) `Program` node of the AST for the code being linted. * `scopeManager`: [ScopeManager](./scope-manager-interface#scopemanager-interface) object of the code. * `visitorKeys`: (`object`) Visitor keys to traverse this AST. +* `parserServices`: (`object`) Contains parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) * `lines`: (`array`) Array of lines, split according to the specification's definition of line breaks. You should use a `SourceCode` object whenever you need to get more information about the code being linted. diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index be1380f6880e..3a8a8fe7e6cb 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -1197,9 +1197,10 @@ describe("Linter", () => { linter.defineRule("test-service-rule", { create: context => ({ Literal(node) { + assert.strictEqual(context.parserServices, context.sourceCode.parserServices); context.report({ node, - message: context.parserServices.test.getMessage() + message: context.sourceCode.parserServices.test.getMessage() }); } }) @@ -1219,9 +1220,10 @@ describe("Linter", () => { linter.defineRule("test-service-rule", { create: context => ({ Literal(node) { + assert.strictEqual(context.parserServices, context.sourceCode.parserServices); context.report({ node, - message: context.parserServices.test.getMessage() + message: context.sourceCode.parserServices.test.getMessage() }); } }) @@ -7956,9 +7958,10 @@ describe("Linter with FlatConfigArray", () => { "test-service-rule": { create: context => ({ Literal(node) { + assert.strictEqual(context.parserServices, context.sourceCode.parserServices); context.report({ node, - message: context.parserServices.test.getMessage() + message: context.sourceCode.parserServices.test.getMessage() }); } }) @@ -7992,9 +7995,10 @@ describe("Linter with FlatConfigArray", () => { "test-service-rule": { create: context => ({ Literal(node) { + assert.strictEqual(context.parserServices, context.sourceCode.parserServices); context.report({ node, - message: context.parserServices.test.getMessage() + message: context.sourceCode.parserServices.test.getMessage() }); } }) diff --git a/tests/lib/rule-tester/flat-rule-tester.js b/tests/lib/rule-tester/flat-rule-tester.js index f65c3bb19135..6c6188aee502 100644 --- a/tests/lib/rule-tester/flat-rule-tester.js +++ b/tests/lib/rule-tester/flat-rule-tester.js @@ -1199,7 +1199,9 @@ describe("FlatRuleTester", () => { const disallowHiRule = { create: context => ({ Literal(node) { - const disallowed = context.parserServices.test.getMessage(); // returns "Hi!" + assert.strictEqual(context.parserServices, context.sourceCode.parserServices); + + const disallowed = context.sourceCode.parserServices.test.getMessage(); // returns "Hi!" if (node.value === disallowed) { context.report({ node, message: `Don't use '${disallowed}'` }); diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js index a36edafd4096..cb1cf346f62f 100644 --- a/tests/lib/rule-tester/rule-tester.js +++ b/tests/lib/rule-tester/rule-tester.js @@ -1248,7 +1248,9 @@ describe("RuleTester", () => { const disallowHiRule = { create: context => ({ Literal(node) { - const disallowed = context.parserServices.test.getMessage(); // returns "Hi!" + assert.strictEqual(context.parserServices, context.sourceCode.parserServices); + + const disallowed = context.sourceCode.parserServices.test.getMessage(); // returns "Hi!" if (node.value === disallowed) { context.report({ node, message: `Don't use '${disallowed}'` }); From a36bcb67f26be42c794797d0cc9948b9cfd4ff71 Mon Sep 17 00:00:00 2001 From: Gweesin Chan <42909374+gweesin@users.noreply.github.com> Date: Fri, 30 Jun 2023 18:37:14 +0800 Subject: [PATCH 062/248] fix: no-unused-vars false positive with logical assignment operators (#17320) * fix: no-unused-vars should be corrected in logical assignment operator Fixes #17299 * refactor: use astUtils.isLogicalAssignmentOperator check exception --- lib/rules/no-unused-vars.js | 3 ++- tests/lib/rules/no-unused-vars.js | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js index be8af43dd411..f29e678d6fdd 100644 --- a/lib/rules/no-unused-vars.js +++ b/lib/rules/no-unused-vars.js @@ -466,7 +466,8 @@ module.exports = { ( parent.type === "AssignmentExpression" && parent.left === id && - isUnusedExpression(parent) + isUnusedExpression(parent) && + !astUtils.isLogicalAssignmentOperator(parent.operator) ) || ( parent.type === "UpdateExpression" && diff --git a/tests/lib/rules/no-unused-vars.js b/tests/lib/rules/no-unused-vars.js index 3c1997b84687..2dea2614fd20 100644 --- a/tests/lib/rules/no-unused-vars.js +++ b/tests/lib/rules/no-unused-vars.js @@ -417,6 +417,20 @@ ruleTester.run("no-unused-vars", rule, { { code: "import.meta", parserOptions: { ecmaVersion: 2020, sourceType: "module" } + }, + + // https://github.com/eslint/eslint/issues/17299 + { + code: "var a; a ||= 1;", + parserOptions: { ecmaVersion: 2021 } + }, + { + code: "var a; a &&= 1;", + parserOptions: { ecmaVersion: 2021 } + }, + { + code: "var a; a ??= 1;", + parserOptions: { ecmaVersion: 2021 } } ], invalid: [ From eb3d7946e1e9f70254008744dba2397aaa730114 Mon Sep 17 00:00:00 2001 From: Ziyad El Abid <117075713+ziyad-elabid-nw@users.noreply.github.com> Date: Fri, 30 Jun 2023 14:06:46 +0100 Subject: [PATCH 063/248] chore: upgrade semver@7.5.3 (#17323) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f705b0967ff8..5ee41b2b184e 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,7 @@ "puppeteer": "^13.7.0", "recast": "^0.20.4", "regenerator-runtime": "^0.13.2", - "semver": "^7.3.5", + "semver": "^7.5.3", "shelljs": "^0.8.2", "sinon": "^11.0.0", "temp": "^0.9.0", From e6e74f9eef0448129dd4775628aba554a2d8c8c9 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 30 Jun 2023 16:37:14 -0400 Subject: [PATCH 064/248] 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 ae1728a3e17b..1a779c1a1ef4 100644 --- a/packages/eslint-config-eslint/package.json +++ b/packages/eslint-config-eslint/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-eslint", - "version": "7.0.0", + "version": "8.0.0", "author": "Nicholas C. Zakas ", "description": "Default ESLint configuration for ESLint projects.", "scripts": { From 840a26462bbf6c27c52c01b85ee2018062157951 Mon Sep 17 00:00:00 2001 From: Elian Cordoba Date: Fri, 30 Jun 2023 17:56:07 -0300 Subject: [PATCH 065/248] test: More test cases for no-case-declarations (#17315) * feat: Added more no-case-declarations tests * Fix: Removed parserOptions --- tests/lib/rules/no-case-declarations.js | 35 ++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tests/lib/rules/no-case-declarations.js b/tests/lib/rules/no-case-declarations.js index e1bd6b101a13..bee630a1bad7 100644 --- a/tests/lib/rules/no-case-declarations.js +++ b/tests/lib/rules/no-case-declarations.js @@ -35,9 +35,42 @@ ruleTester.run("no-case-declarations", rule, { { code: "switch (a) { case 1: { class C {} break; } default: { class C {} break; } }", parserOptions: { ecmaVersion: 6 } - } + }, + ` + switch (a) { + case 1: + case 2: {} + } + `, + ` + switch (a) { + case 1: var x; + } + ` ], invalid: [ + { + code: ` + switch (a) { + case 1: + {} + function f() {} + break; + } + `, + errors: [{ messageId: "unexpected", type: "FunctionDeclaration" }] + }, + { + code: ` + switch (a) { + case 1: + case 2: + let x; + } + `, + parserOptions: { ecmaVersion: 6 }, + errors: [{ messageId: "unexpected", type: "VariableDeclaration" }] + }, { code: "switch (a) { case 1: let x = 1; break; }", parserOptions: { ecmaVersion: 6 }, From 4d411e4c7063274d6d346f1b7ee46f7575d0bbd2 Mon Sep 17 00:00:00 2001 From: Percy Ma Date: Sat, 1 Jul 2023 05:38:04 +0800 Subject: [PATCH 066/248] feat: add ternaryOperandBinaryExpressions option to no-extra-parens rule (#17270) * feat: add `conditionalTernary` option to `no-extra-parens` rule * chore: improve coverage * chore: improve coverage * chore: avoid SequenceExpression miss * chore: change option name to ternaryOperandBinaryExpressions * docs: update docs * docs: fix space inside code * chore: update * chore: clean * docs: update * chore: update --- docs/src/rules/no-extra-parens.md | 21 +++++++++++++++++++++ lib/rules/no-extra-parens.js | 14 ++++++++++++-- tests/lib/rules/no-extra-parens.js | 12 ++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/docs/src/rules/no-extra-parens.md b/docs/src/rules/no-extra-parens.md index dd2b4642008d..0476e628010f 100644 --- a/docs/src/rules/no-extra-parens.md +++ b/docs/src/rules/no-extra-parens.md @@ -52,6 +52,7 @@ This rule has an object option for exceptions to the `"all"` option: * `"conditionalAssign": false` allows extra parentheses around assignments in conditional test expressions * `"returnAssign": false` allows extra parentheses around assignments in `return` statements * `"nestedBinaryExpressions": false` allows extra parentheses in nested binary expressions +* `"ternaryOperandBinaryExpressions": false` allows extra parentheses around binary expressions that are operands of ternary `?:` * `"ignoreJSX": "none|all|multi-line|single-line"` allows extra parentheses around no/all/multi-line/single-line JSX components. Defaults to `none`. * `"enforceForArrowConditionals": false` allows extra parentheses around ternary expressions which are the body of an arrow function * `"enforceForSequenceExpressions": false` allows extra parentheses around sequence expressions @@ -189,6 +190,26 @@ x = (a * b) / c; ::: +### ternaryOperandBinaryExpressions + +Examples of **correct** code for this rule with the `"all"` and `{ "ternaryOperandBinaryExpressions": false }` options: + +::: correct + +```js +/* eslint no-extra-parens: ["error", "all", { "ternaryOperandBinaryExpressions": false }] */ + +(a && b) ? foo : bar; + +(a - b > a) ? foo : bar; + +foo ? (bar || baz) : qux; + +foo ? bar : (baz || qux); +``` + +::: + ### ignoreJSX Examples of **correct** code for this rule with the `all` and `{ "ignoreJSX": "all" }` options: diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js index e9080120fab0..bb80987858c4 100644 --- a/lib/rules/no-extra-parens.js +++ b/lib/rules/no-extra-parens.js @@ -46,6 +46,7 @@ module.exports = { type: "object", properties: { conditionalAssign: { type: "boolean" }, + ternaryOperandBinaryExpressions: { type: "boolean" }, nestedBinaryExpressions: { type: "boolean" }, returnAssign: { type: "boolean" }, ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] }, @@ -76,6 +77,7 @@ module.exports = { const precedence = astUtils.getPrecedence; const ALL_NODES = context.options[0] !== "functions"; const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false; + const EXCEPT_COND_TERNARY = ALL_NODES && context.options[1] && context.options[1].ternaryOperandBinaryExpressions === false; const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false; const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false; const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX; @@ -886,18 +888,26 @@ module.exports = { if (isReturnAssignException(node)) { return; } + + const availableTypes = new Set(["BinaryExpression", "LogicalExpression"]); + if ( + !(EXCEPT_COND_TERNARY && availableTypes.has(node.test.type)) && !isCondAssignException(node) && hasExcessParensWithPrecedence(node.test, precedence({ type: "LogicalExpression", operator: "||" })) ) { report(node.test); } - if (hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { + if ( + !(EXCEPT_COND_TERNARY && availableTypes.has(node.consequent.type)) && + hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { report(node.consequent); } - if (hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { + if ( + !(EXCEPT_COND_TERNARY && availableTypes.has(node.alternate.type)) && + hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { report(node.alternate); } }, diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js index c160ace3c36a..2d61522cb72c 100644 --- a/tests/lib/rules/no-extra-parens.js +++ b/tests/lib/rules/no-extra-parens.js @@ -304,6 +304,14 @@ ruleTester.run("no-extra-parens", rule, { { code: "while (((foo = bar()))) {}", options: ["all", { conditionalAssign: false }] }, { code: "var a = (((b = c))) ? foo : bar;", options: ["all", { conditionalAssign: false }] }, + // ["all", { ternaryOperandBinaryExpressions: false }] enables extra parens around conditional ternary + { code: "(a && b) ? foo : bar", options: ["all", { ternaryOperandBinaryExpressions: false }] }, + { code: "(a - b > a) ? foo : bar", options: ["all", { ternaryOperandBinaryExpressions: false }] }, + { code: "foo ? (bar || baz) : qux", options: ["all", { ternaryOperandBinaryExpressions: false }] }, + { code: "foo ? bar : (baz || qux)", options: ["all", { ternaryOperandBinaryExpressions: false }] }, + { code: "(a, b) ? (c, d) : (e, f)", options: ["all", { ternaryOperandBinaryExpressions: false }] }, + { code: "(a = b) ? c : d", options: ["all", { ternaryOperandBinaryExpressions: false }] }, + // ["all", { nestedBinaryExpressions: false }] enables extra parens around conditional assignments { code: "a + (b * c)", options: ["all", { nestedBinaryExpressions: false }] }, { code: "(a * b) + c", options: ["all", { nestedBinaryExpressions: false }] }, @@ -922,6 +930,10 @@ ruleTester.run("no-extra-parens", rule, { invalid("a ? b : (c = d)", "a ? b : c = d", "AssignmentExpression"), invalid("(c = d) ? (b) : c", "(c = d) ? b : c", "Identifier", null, { options: ["all", { conditionalAssign: false }] }), invalid("(c = d) ? b : (c)", "(c = d) ? b : c", "Identifier", null, { options: ["all", { conditionalAssign: false }] }), + invalid("(a) ? foo : bar", "a ? foo : bar", "Identifier", null, { options: ["all", { ternaryOperandBinaryExpressions: false }] }), + invalid("(a()) ? foo : bar", "a() ? foo : bar", "CallExpression", null, { options: ["all", { ternaryOperandBinaryExpressions: false }] }), + invalid("(a.b) ? foo : bar", "a.b ? foo : bar", "MemberExpression", null, { options: ["all", { ternaryOperandBinaryExpressions: false }] }), + invalid("(a || b) ? foo : (bar)", "(a || b) ? foo : bar", "Identifier", null, { options: ["all", { ternaryOperandBinaryExpressions: false }] }), invalid("f((a = b))", "f(a = b)", "AssignmentExpression"), invalid("a, (b = c)", "a, b = c", "AssignmentExpression"), invalid("a = (b * c)", "a = b * c", "BinaryExpression"), From 4c5040022639ae804c15b366afc6e64982bd8ae3 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 30 Jun 2023 23:59:14 +0200 Subject: [PATCH 067/248] feat: add `ecmaVersion: 2024`, regexp `v` flag parsing (#17324) * feat: add `ecmaVersion: 2024`, regexp `v` flag parsing * Update package.json --- README.md | 2 +- conf/globals.js | 7 ++++++- docs/src/use/configure/language-options.md | 2 +- lib/shared/types.js | 2 +- package.json | 2 +- tests/lib/linter/linter.js | 2 +- 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 16f6513a3b22..2c8b99345be3 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [confi ### What ECMAScript versions does ESLint support? -ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2019, 2020, 2021 and 2022. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/latest/use/configure). +ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, and 2023. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/latest/use/configure). ### What about experimental features? diff --git a/conf/globals.js b/conf/globals.js index 6018b7af15c2..58710e05bc6d 100644 --- a/conf/globals.js +++ b/conf/globals.js @@ -128,6 +128,10 @@ const es2023 = { ...es2022 }; +const es2024 = { + ...es2023 +}; + //----------------------------------------------------------------------------- // Exports @@ -145,5 +149,6 @@ module.exports = { es2020, es2021, es2022, - es2023 + es2023, + es2024 }; diff --git a/docs/src/use/configure/language-options.md b/docs/src/use/configure/language-options.md index 4f8fa148bce8..90d7ca731311 100644 --- a/docs/src/use/configure/language-options.md +++ b/docs/src/use/configure/language-options.md @@ -193,7 +193,7 @@ By the same token, supporting ES6 syntax is not the same as supporting new ES6 g Parser options are set in your `.eslintrc.*` file with the `parserOptions` property. The available options are: -* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, 12, 13, or 14 to specify the version of ECMAScript syntax you want to use. You can also set it to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), 2018 (same as 9), 2019 (same as 10), 2020 (same as 11), 2021 (same as 12), 2022 (same as 13), or 2023 (same as 14) to use the year-based naming. You can also set `"latest"` to use the most recently supported version. +* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, 12, 13, 14, or 15 to specify the version of ECMAScript syntax you want to use. You can also set it to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), 2018 (same as 9), 2019 (same as 10), 2020 (same as 11), 2021 (same as 12), 2022 (same as 13), 2023 (same as 14), or 2024 (same as 15) to use the year-based naming. You can also set `"latest"` to use the most recently supported version. * `sourceType` - set to `"script"` (default) or `"module"` if your code is in ECMAScript modules. * `allowReserved` - allow the use of reserved words as identifiers (if `ecmaVersion` is 3). * `ecmaFeatures` - an object indicating which additional language features you'd like to use: diff --git a/lib/shared/types.js b/lib/shared/types.js index 5c10462587a5..e3a40bc986be 100644 --- a/lib/shared/types.js +++ b/lib/shared/types.js @@ -21,7 +21,7 @@ module.exports = {}; /** * @typedef {Object} ParserOptions * @property {EcmaFeatures} [ecmaFeatures] The optional features. - * @property {3|5|6|7|8|9|10|11|12|13|14|2015|2016|2017|2018|2019|2020|2021|2022|2023} [ecmaVersion] The ECMAScript version (or revision number). + * @property {3|5|6|7|8|9|10|11|12|13|14|15|2015|2016|2017|2018|2019|2020|2021|2022|2023|2024} [ecmaVersion] The ECMAScript version (or revision number). * @property {"script"|"module"} [sourceType] The source code type. * @property {boolean} [allowReserved] Allowing the use of reserved words as identifiers in ES3. */ diff --git a/package.json b/package.json index 5ee41b2b184e..cddc1e310622 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "espree": "^9.6.0", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index 3a8a8fe7e6cb..1b8f93d410f9 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -4938,7 +4938,7 @@ var a = "test2"; }); it("supports ECMAScript version 'latest'", () => { - const messages = linter.verify("let x = 5 ** 7;", { + const messages = linter.verify("let x = /[\\q{abc|d}&&[A--B]]/v;", { parserOptions: { ecmaVersion: "latest" } }); const suppressedMessages = linter.getSuppressedMessages(); From 176677180a4a1209fc192771521c9192e1f67578 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sat, 1 Jul 2023 00:17:38 +0200 Subject: [PATCH 068/248] feat: add `es2023` and `es2024` environments (#17328) * feat: add `es2023` and `es2024` environments Fixes #17298 * Update package.json --- docs/src/use/configure/language-options.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/src/use/configure/language-options.md b/docs/src/use/configure/language-options.md index 90d7ca731311..a89224780ac3 100644 --- a/docs/src/use/configure/language-options.md +++ b/docs/src/use/configure/language-options.md @@ -26,6 +26,8 @@ An environment provides predefined global variables. The available environments * `es2020` - adds all ECMAScript 2020 globals and automatically sets the `ecmaVersion` parser option to 11. * `es2021` - adds all ECMAScript 2021 globals and automatically sets the `ecmaVersion` parser option to 12. * `es2022` - adds all ECMAScript 2022 globals and automatically sets the `ecmaVersion` parser option to 13. +* `es2023` - adds all ECMAScript 2023 globals and automatically sets the `ecmaVersion` parser option to 14. +* `es2024` - adds all ECMAScript 2024 globals and automatically sets the `ecmaVersion` parser option to 15. * `worker` - web workers global variables. * `amd` - defines `require()` and `define()` as global variables as per the [amd](https://github.com/amdjs/amdjs-api/blob/master/AMD.md) spec. * `mocha` - adds all of the Mocha testing global variables. diff --git a/package.json b/package.json index cddc1e310622..e941ff3b1acc 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", + "@eslint/eslintrc": "^2.1.0", "@eslint/js": "8.43.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", From a1cb6421f9d185901cd99e5f696e912226ef6632 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 30 Jun 2023 18:31:49 -0400 Subject: [PATCH 069/248] 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 3af35a6915a5..9d9d9799f226 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "8.43.0", + "version": "8.44.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 49e46edf3c8dc71d691a97fc33b63ed80ae0db0c Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sat, 1 Jul 2023 00:53:23 +0200 Subject: [PATCH 070/248] chore: upgrade @eslint/js@8.44.0 (#17329) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e941ff3b1acc..8d20e743cfe2 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.1.0", - "@eslint/js": "8.43.0", + "@eslint/js": "8.44.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", From 849933b29d4bb188f36f458f5d9a138a57a441f0 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 30 Jun 2023 19:10:56 -0400 Subject: [PATCH 071/248] Build: changelog update for 8.44.0 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bb58d74514f..53d43c34d8aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +v8.44.0 - June 30, 2023 + +* [`49e46ed`](https://github.com/eslint/eslint/commit/49e46edf3c8dc71d691a97fc33b63ed80ae0db0c) chore: upgrade @eslint/js@8.44.0 (#17329) (Milos Djermanovic) +* [`a1cb642`](https://github.com/eslint/eslint/commit/a1cb6421f9d185901cd99e5f696e912226ef6632) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`1766771`](https://github.com/eslint/eslint/commit/176677180a4a1209fc192771521c9192e1f67578) feat: add `es2023` and `es2024` environments (#17328) (Milos Djermanovic) +* [`4c50400`](https://github.com/eslint/eslint/commit/4c5040022639ae804c15b366afc6e64982bd8ae3) feat: add `ecmaVersion: 2024`, regexp `v` flag parsing (#17324) (Milos Djermanovic) +* [`4d411e4`](https://github.com/eslint/eslint/commit/4d411e4c7063274d6d346f1b7ee46f7575d0bbd2) feat: add ternaryOperandBinaryExpressions option to no-extra-parens rule (#17270) (Percy Ma) +* [`840a264`](https://github.com/eslint/eslint/commit/840a26462bbf6c27c52c01b85ee2018062157951) test: More test cases for no-case-declarations (#17315) (Elian Cordoba) +* [`e6e74f9`](https://github.com/eslint/eslint/commit/e6e74f9eef0448129dd4775628aba554a2d8c8c9) chore: package.json update for eslint-config-eslint release (ESLint Jenkins) +* [`eb3d794`](https://github.com/eslint/eslint/commit/eb3d7946e1e9f70254008744dba2397aaa730114) chore: upgrade semver@7.5.3 (#17323) (Ziyad El Abid) +* [`a36bcb6`](https://github.com/eslint/eslint/commit/a36bcb67f26be42c794797d0cc9948b9cfd4ff71) fix: no-unused-vars false positive with logical assignment operators (#17320) (Gweesin Chan) +* [`c8b1f4d`](https://github.com/eslint/eslint/commit/c8b1f4d61a256727755d561bf53f889b6cd712e0) feat: Move `parserServices` to `SourceCode` (#17311) (Milos Djermanovic) +* [`cf88439`](https://github.com/eslint/eslint/commit/cf884390ad8071d88eae05df9321100f1770363d) chore: upgrade optionator@0.9.3 (#17319) (Milos Djermanovic) +* [`7620b89`](https://github.com/eslint/eslint/commit/7620b891e81c234f30f9dbcceb64a05fd0dde65e) fix: Remove `no-unused-labels` autofix before potential directives (#17314) (Francesco Trotta) +* [`ef6e24e`](https://github.com/eslint/eslint/commit/ef6e24e42670f321d996948623846d9caaedac99) feat: treat unknown nodes as having the lowest precedence (#17302) (Brad Zacher) +* [`9718a97`](https://github.com/eslint/eslint/commit/9718a9781d69d2c40b68c631aed97700b32c0082) refactor: remove unnecessary code in `flat-eslint.js` (#17308) (Milos Djermanovic) +* [`1866e1d`](https://github.com/eslint/eslint/commit/1866e1df6175e4ba0ae4a0d88dc3c956bb310035) feat: allow flat config files to export a Promise (#17301) (Milos Djermanovic) +* [`f82e56e`](https://github.com/eslint/eslint/commit/f82e56e9acfb9562ece76441472d5657d7d5e296) perf: various performance improvements (#17135) (moonlightaria) +* [`da81e66`](https://github.com/eslint/eslint/commit/da81e66e22b4f3d3fe292cf70c388753304deaad) chore: update eslint-plugin-jsdoc to 46.2.5 (#17245) (ๅ”ฏ็„ถ) +* [`526e911`](https://github.com/eslint/eslint/commit/526e91106e6fe101578e9478a9d7f4844d4f72ac) docs: resubmit pr 17115 doc changes (#17291) (ๅ”ฏ็„ถ) +* [`b991640`](https://github.com/eslint/eslint/commit/b991640176d5dce4750f7cc71c56cd6f284c882f) chore: switch eslint-config-eslint to the flat format (#17247) (ๅ”ฏ็„ถ) +* [`391ed38`](https://github.com/eslint/eslint/commit/391ed38b09bd1a3abe85db65b8fcda980ab3d6f4) fix: Remove `no-extra-semi` autofix before potential directives (#17297) (Francesco Trotta) +* [`e1314bf`](https://github.com/eslint/eslint/commit/e1314bf85a52bb0d05b1c9ca3b4c1732bae22172) docs: Integration section and tutorial (#17132) (Ben Perlmutter) +* [`19a8c5d`](https://github.com/eslint/eslint/commit/19a8c5d84596a9f7f2aa428c1696ba86daf854e6) docs: Update README (GitHub Actions Bot) + v8.43.0 - June 16, 2023 * [`78350f6`](https://github.com/eslint/eslint/commit/78350f63045c82b7990bb7bfe5080c5ad5e1c3f5) chore: upgrade @eslint/js@8.43.0 (#17295) (Milos Djermanovic) From 8c1ec65fa3d355d072f2c9c66e91aeada8e7ba14 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 30 Jun 2023 19:10:57 -0400 Subject: [PATCH 072/248] 8.44.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 d9eb49fb518e..74b5418d8a79 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.43.0", + "version": "8.44.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 5075035b903e..606fd49e0bd1 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

- 9 problems (5 errors, 4 warnings) - Generated on Fri Jun 16 2023 17:52:22 GMT-0400 (Eastern Daylight Time) + 9 problems (5 errors, 4 warnings) - Generated on Fri Jun 30 2023 19:10:58 GMT-0400 (Eastern Daylight Time)
diff --git a/package.json b/package.json index 8d20e743cfe2..6dd1d142c828 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.43.0", + "version": "8.44.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 1fc50a89753346f4f4c786ffd20ac4cf185bb036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro?= Date: Sun, 2 Jul 2023 13:37:18 +0200 Subject: [PATCH 073/248] docs: `max-len` rule `code` and `tabWidth` as positional arguments (#17331) --- docs/src/rules/max-len.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/rules/max-len.md b/docs/src/rules/max-len.md index be883a8ca0e1..eabd3efc4c6c 100644 --- a/docs/src/rules/max-len.md +++ b/docs/src/rules/max-len.md @@ -22,7 +22,7 @@ This rule enforces a maximum line length to increase code readability and mainta ## Options -This rule has a number or object option: +This rule can have up to two numbers as positional arguments (for `code` and `tabWidth` options), followed by an object option (provided positional arguments have priority): * `"code"` (default `80`) enforces a maximum line length * `"tabWidth"` (default `4`) specifies the character width for tab characters From d34abe59eb23932dcbc79757d7932d08ee8b20e5 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 3 Jul 2023 23:20:08 +0200 Subject: [PATCH 074/248] feat: fix indent rule for else-if (#17318) * feat: fix indent rule for else-if Fixes #17316 * add more test cases --- lib/rules/indent.js | 2 +- tests/lib/rules/indent.js | 577 +++++++++++++++++++++++++++++++++++++- 2 files changed, 577 insertions(+), 2 deletions(-) diff --git a/lib/rules/indent.js b/lib/rules/indent.js index 9068006d4972..7ea4b3f86c33 100644 --- a/lib/rules/indent.js +++ b/lib/rules/indent.js @@ -1250,7 +1250,7 @@ module.exports = { IfStatement(node) { addBlocklessNodeIndent(node.consequent); - if (node.alternate && node.alternate.type !== "IfStatement") { + if (node.alternate) { addBlocklessNodeIndent(node.alternate); } }, diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js index 0785083d0aca..4880fa5ec029 100644 --- a/tests/lib/rules/indent.js +++ b/tests/lib/rules/indent.js @@ -6361,7 +6361,156 @@ ruleTester.run("indent", rule, { ;[1, 2, 3].forEach(x=>console.log(x)) `, options: [4] - } + }, + + // https://github.com/eslint/eslint/issues/17316 + { + code: unIndent` + if (foo) + \tif (bar) doSomething(); + \telse doSomething(); + else + \tif (bar) doSomething(); + \telse doSomething(); + `, + options: ["tab"] + }, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) doSomething(); + else doSomething(); + `, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) + doSomething(); + else doSomething(); + `, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) doSomething(); + else + doSomething(); + `, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) + doSomething(); + else + doSomething(); + `, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (bar) doSomething(); + else doSomething(); + `, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (bar) + doSomething(); + else doSomething(); + `, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (bar) doSomething(); + else + doSomething(); + `, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (bar) + doSomething(); + else + doSomething(); + `, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) doSomething(); + else doSomething(); + + `, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (foo) + if (bar) doSomething(); + else + if (bar) doSomething(); + else doSomething(); + else doSomething(); + `, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (foo) doSomething(); + else doSomething(); + `, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (foo) { + doSomething(); + } + `, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (foo) + { + doSomething(); + } + `, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (foo) { + doSomething(); + } + `, + unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (foo) + { + doSomething(); + } + ` ], invalid: [ @@ -13381,6 +13530,432 @@ ruleTester.run("indent", rule, { `, options: [4], errors: expectedErrors([4, 0, 4, "Punctuator"]) + }, + + // https://github.com/eslint/eslint/issues/17316 + { + code: unIndent` + if (foo) + \tif (bar) doSomething(); + \telse doSomething(); + else + if (bar) doSomething(); + else doSomething(); + `, + output: unIndent` + if (foo) + \tif (bar) doSomething(); + \telse doSomething(); + else + \tif (bar) doSomething(); + \telse doSomething(); + `, + options: ["tab"], + errors: expectedErrors("tab", [ + [5, 1, 0, "Keyword"], + [6, 1, 0, "Keyword"] + ]) + }, + { + code: unIndent` + if (foo) + \tif (bar) doSomething(); + \telse doSomething(); + else + \t\tif (bar) doSomething(); + \t\telse doSomething(); + `, + output: unIndent` + if (foo) + \tif (bar) doSomething(); + \telse doSomething(); + else + \tif (bar) doSomething(); + \telse doSomething(); + `, + options: ["tab"], + errors: expectedErrors("tab", [ + [5, 1, 2, "Keyword"], + [6, 1, 2, "Keyword"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) doSomething(); + else doSomething(); + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) doSomething(); + else doSomething(); + `, + errors: expectedErrors([ + [5, 4, 0, "Keyword"], + [6, 4, 0, "Keyword"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) + doSomething(); + else doSomething(); + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) + doSomething(); + else doSomething(); + `, + errors: expectedErrors([ + [5, 4, 0, "Keyword"], + [6, 8, 0, "Identifier"], + [7, 4, 0, "Keyword"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) doSomething(); + else + doSomething(); + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) doSomething(); + else + doSomething(); + `, + errors: expectedErrors([ + [5, 4, 0, "Keyword"], + [6, 4, 0, "Keyword"], + [7, 8, 0, "Identifier"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) + doSomething(); + else + doSomething(); + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) + doSomething(); + else + doSomething(); + `, + errors: expectedErrors([ + [5, 4, 0, "Keyword"], + [6, 8, 4, "Identifier"], + [7, 4, 0, "Keyword"], + [8, 8, 0, "Identifier"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (bar) doSomething(); + else doSomething(); + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (bar) doSomething(); + else doSomething(); + `, + errors: expectedErrors([ + [5, 0, 4, "Keyword"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (bar) + doSomething(); + else doSomething(); + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (bar) + doSomething(); + else doSomething(); + `, + errors: expectedErrors([ + [4, 0, 4, "Keyword"], + [5, 4, 8, "Identifier"], + [6, 0, 4, "Keyword"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (bar) doSomething(); + else + doSomething(); + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (bar) doSomething(); + else + doSomething(); + `, + errors: expectedErrors([ + [5, 0, 5, "Keyword"], + [6, 4, 9, "Identifier"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (bar) + doSomething(); + else + doSomething(); + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (bar) + doSomething(); + else + doSomething(); + `, + errors: expectedErrors([ + [5, 4, 0, "Identifier"], + [7, 4, 0, "Identifier"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) doSomething(); + else doSomething(); + + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (bar) doSomething(); + else doSomething(); + + `, + errors: expectedErrors([ + [5, 4, 0, "Keyword"], + [6, 8, 4, "Keyword"], + [7, 8, 4, "Keyword"], + [8, 4, 0, "Keyword"], + [9, 8, 4, "Keyword"], + [10, 8, 4, "Keyword"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (foo) + if (bar) doSomething(); + else + if (bar) doSomething(); + else doSomething(); + else doSomething(); + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (foo) + if (bar) doSomething(); + else + if (bar) doSomething(); + else doSomething(); + else doSomething(); + `, + errors: expectedErrors([ + [5, 4, 0, "Keyword"], + [6, 8, 0, "Keyword"], + [7, 8, 0, "Keyword"], + [8, 12, 0, "Keyword"], + [9, 12, 0, "Keyword"], + [10, 4, 0, "Keyword"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (foo) doSomething(); + else doSomething(); + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (foo) doSomething(); + else doSomething(); + `, + errors: expectedErrors([ + [2, 4, 0, "Keyword"], + [3, 4, 0, "Keyword"], + [5, 0, 4, "Keyword"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (foo) { + doSomething(); + } + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (foo) { + doSomething(); + } + `, + errors: expectedErrors([ + [5, 4, 0, "Identifier"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (foo) + { + doSomething(); + } + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else if (foo) + { + doSomething(); + } + `, + errors: expectedErrors([ + [5, 0, 4, "Punctuator"], + [6, 4, 8, "Identifier"], + [7, 0, 4, "Punctuator"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (foo) { + doSomething(); + } + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (foo) { + doSomething(); + } + `, + errors: expectedErrors([ + [5, 4, 0, "Keyword"], + [6, 8, 4, "Identifier"], + [7, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (foo) + { + doSomething(); + } + `, + output: unIndent` + if (foo) + if (bar) doSomething(); + else doSomething(); + else + if (foo) + { + doSomething(); + } + `, + errors: expectedErrors([ + [5, 4, 0, "Keyword"], + [6, 4, 0, "Punctuator"], + [7, 8, 4, "Identifier"], + [8, 4, 0, "Punctuator"] + ]) } ] }); From 0052374035672efe9129343fc00ee51a4c288ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Tue, 4 Jul 2023 21:49:58 +0800 Subject: [PATCH 075/248] chore: move jsdoc settings to eslint-config-eslint (#17338) --- .eslintrc.js | 10 ---------- eslint.config.js | 10 ---------- packages/eslint-config-eslint/eslintrc.js | 1 + packages/eslint-config-eslint/index.js | 1 + 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 79812915fbfc..7757f21bd2b5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -67,16 +67,6 @@ module.exports = { parserOptions: { ecmaVersion: 2021 }, - - /* - * it fixes eslint-plugin-jsdoc's reports: "Invalid JSDoc tag name "template" jsdoc/check-tag-names" - * refs: https://github.com/gajus/eslint-plugin-jsdoc#check-tag-names - */ - settings: { - jsdoc: { - mode: "typescript" - } - }, rules: { "internal-rules/multiline-comment-style": "error" }, diff --git a/eslint.config.js b/eslint.config.js index 95612e901ca7..09497a915b64 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -100,16 +100,6 @@ module.exports = [ languageOptions: { ecmaVersion: "latest" }, - - /* - * it fixes eslint-plugin-jsdoc's reports: "Invalid JSDoc tag name "template" jsdoc/check-tag-names" - * refs: https://github.com/gajus/eslint-plugin-jsdoc#check-tag-names - */ - settings: { - jsdoc: { - mode: "typescript" - } - }, rules: { "internal-rules/multiline-comment-style": "error" } diff --git a/packages/eslint-config-eslint/eslintrc.js b/packages/eslint-config-eslint/eslintrc.js index a821ab0a07c3..31ce7f252c03 100644 --- a/packages/eslint-config-eslint/eslintrc.js +++ b/packages/eslint-config-eslint/eslintrc.js @@ -18,6 +18,7 @@ module.exports = { plugins: ["unicorn"], settings: { jsdoc: { + mode: "typescript", tagNamePreference: { file: "fileoverview", augments: "extends", diff --git a/packages/eslint-config-eslint/index.js b/packages/eslint-config-eslint/index.js index 1f629c5cd837..4057d6d944cf 100644 --- a/packages/eslint-config-eslint/index.js +++ b/packages/eslint-config-eslint/index.js @@ -278,6 +278,7 @@ const jsConfigs = [js.configs.recommended, { const jsdocConfigs = [jsdoc.configs.recommended, { settings: { jsdoc: { + mode: "typescript", tagNamePreference: { file: "fileoverview", augments: "extends", From 138c096bc9468b553dbafc0e573c6522a17a7922 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 4 Jul 2023 16:28:27 +0200 Subject: [PATCH 076/248] docs: add more prefer-destructuring examples with array destructuring (#17330) Fixes #17327 --- docs/src/rules/prefer-destructuring.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/src/rules/prefer-destructuring.md b/docs/src/rules/prefer-destructuring.md index c98abdc0c906..edb72967b540 100644 --- a/docs/src/rules/prefer-destructuring.md +++ b/docs/src/rules/prefer-destructuring.md @@ -38,6 +38,7 @@ Examples of **incorrect** code for this rule: ```javascript // With `array` enabled var foo = array[0]; +bar.baz = array[0]; // With `object` enabled var foo = object.foo; @@ -54,6 +55,8 @@ Examples of **correct** code for this rule: // With `array` enabled var [ foo ] = array; var foo = array[someIndex]; +[bar.baz] = array; + // With `object` enabled var { foo } = object; From c667055fb9da8ebac3a99f6e5a8b5565cc86af8e Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 4 Jul 2023 19:38:06 +0200 Subject: [PATCH 077/248] fix: provide unique `fix` and `fix.range` objects in lint messages (#17332) * fix: provide unique `fix` and `fix.range` objects in lint messages Fixes #16716 * Update lib/linter/report-translator.js Co-authored-by: Nicholas C. Zakas --------- Co-authored-by: Nicholas C. Zakas --- lib/linter/report-translator.js | 20 ++- tests/lib/linter/linter.js | 165 ++++++++++++++++++++++++ tests/lib/linter/report-translator.js | 179 ++++++++++++++++++++++++++ 3 files changed, 362 insertions(+), 2 deletions(-) diff --git a/lib/linter/report-translator.js b/lib/linter/report-translator.js index 7d2705206cdb..41a43eadc3be 100644 --- a/lib/linter/report-translator.js +++ b/lib/linter/report-translator.js @@ -100,6 +100,22 @@ function normalizeReportLoc(descriptor) { return descriptor.node.loc; } +/** + * Clones the given fix object. + * @param {Fix|null} fix The fix to clone. + * @returns {Fix|null} Deep cloned fix object or `null` if `null` or `undefined` was passed in. + */ +function cloneFix(fix) { + if (!fix) { + return null; + } + + return { + range: [fix.range[0], fix.range[1]], + text: fix.text + }; +} + /** * Check that a fix has a valid range. * @param {Fix|null} fix The fix to validate. @@ -137,7 +153,7 @@ function mergeFixes(fixes, sourceCode) { return null; } if (fixes.length === 1) { - return fixes[0]; + return cloneFix(fixes[0]); } fixes.sort(compareFixesByRange); @@ -183,7 +199,7 @@ function normalizeFixes(descriptor, sourceCode) { } assertValidFix(fix); - return fix; + return cloneFix(fix); } /** diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index 1b8f93d410f9..f86e98ba8e0d 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -15917,6 +15917,171 @@ var a = "test2"; assert.strictEqual(suppressedMessages.length, 0); }); + + // https://github.com/eslint/eslint/issues/16716 + it("should receive unique range arrays in suggestions", () => { + const configs = [ + { + plugins: { + "test-processors": { + processors: { + "line-processor": (() => { + const blocksMap = new Map(); + + return { + preprocess(text, fileName) { + const lines = text.split("\n"); + + blocksMap.set(fileName, lines); + + return lines.map((line, index) => ({ + text: line, + filename: `${index}.js` + })); + }, + + postprocess(messageLists, fileName) { + const lines = blocksMap.get(fileName); + let rangeOffset = 0; + + // intentionaly mutates objects and arrays + messageLists.forEach((messages, index) => { + messages.forEach(message => { + message.line += index; + if (typeof message.endLine === "number") { + message.endLine += index; + } + if (message.fix) { + message.fix.range[0] += rangeOffset; + message.fix.range[1] += rangeOffset; + } + if (message.suggestions) { + message.suggestions.forEach(suggestion => { + suggestion.fix.range[0] += rangeOffset; + suggestion.fix.range[1] += rangeOffset; + }); + } + }); + rangeOffset += lines[index].length + 1; + }); + + return messageLists.flat(); + }, + + supportsAutofix: true + }; + })() + } + }, + + "test-rules": { + rules: { + "no-foo": { + meta: { + hasSuggestions: true, + messages: { + unexpected: "Don't use 'foo'.", + replaceWithBar: "Replace with 'bar'", + replaceWithBaz: "Replace with 'baz'" + } + + }, + create(context) { + return { + Identifier(node) { + const { range } = node; + + if (node.name === "foo") { + context.report({ + node, + messageId: "unexpected", + suggest: [ + { + messageId: "replaceWithBar", + fix: () => ({ range, text: "bar" }) + }, + { + messageId: "replaceWithBaz", + fix: () => ({ range, text: "baz" }) + } + ] + }); + } + } + }; + } + } + } + } + } + }, + { + files: ["**/*.txt"], + processor: "test-processors/line-processor" + }, + { + files: ["**/*.js"], + rules: { + "test-rules/no-foo": 2 + } + } + ]; + + const result = linter.verifyAndFix( + "var a = 5;\nvar foo;\nfoo = a;", + configs, + { filename: "a.txt" } + ); + + assert.deepStrictEqual(result.messages, [ + { + ruleId: "test-rules/no-foo", + severity: 2, + message: "Don't use 'foo'.", + line: 2, + column: 5, + nodeType: "Identifier", + messageId: "unexpected", + endLine: 2, + endColumn: 8, + suggestions: [ + { + messageId: "replaceWithBar", + fix: { range: [15, 18], text: "bar" }, + desc: "Replace with 'bar'" + }, + { + messageId: "replaceWithBaz", + fix: { range: [15, 18], text: "baz" }, + desc: "Replace with 'baz'" + } + ] + }, + { + ruleId: "test-rules/no-foo", + severity: 2, + message: "Don't use 'foo'.", + line: 3, + column: 1, + nodeType: "Identifier", + messageId: "unexpected", + endLine: 3, + endColumn: 4, + suggestions: [ + { + messageId: "replaceWithBar", + fix: { range: [20, 23], text: "bar" }, + desc: "Replace with 'bar'" + }, + { + messageId: "replaceWithBaz", + fix: { range: [20, 23], text: "baz" }, + desc: "Replace with 'baz'" + } + ] + } + ]); + }); }); }); diff --git a/tests/lib/linter/report-translator.js b/tests/lib/linter/report-translator.js index 6feabb31b966..18c62f20729e 100644 --- a/tests/lib/linter/report-translator.js +++ b/tests/lib/linter/report-translator.js @@ -1091,4 +1091,183 @@ describe("createReportTranslator", () => { } }); }); + + // https://github.com/eslint/eslint/issues/16716 + describe("unique `fix` and `fix.range` objects", () => { + const range = [0, 3]; + const fix = { range, text: "baz" }; + const additionalRange = [4, 7]; + const additionalFix = { range: additionalRange, text: "qux" }; + + it("should deep clone returned fix object", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + fix: () => fix + }); + + assert.deepStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix.range, fix.range); + }); + + it("should create a new fix object with a new range array when `fix()` returns an array with a single item", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + fix: () => [fix] + }); + + assert.deepStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix.range, fix.range); + }); + + it("should create a new fix object with a new range array when `fix()` returns an array with multiple items", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + fix: () => [fix, additionalFix] + }); + + assert.notStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix.range, fix.range); + assert.notStrictEqual(translatedReport.fix, additionalFix); + assert.notStrictEqual(translatedReport.fix.range, additionalFix.range); + }); + + it("should create a new fix object with a new range array when `fix()` generator yields a single item", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + *fix() { + yield fix; + } + }); + + assert.deepStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix.range, fix.range); + }); + + it("should create a new fix object with a new range array when `fix()` generator yields multiple items", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + *fix() { + yield fix; + yield additionalFix; + } + }); + + assert.notStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix.range, fix.range); + assert.notStrictEqual(translatedReport.fix, additionalFix); + assert.notStrictEqual(translatedReport.fix.range, additionalFix.range); + }); + + it("should deep clone returned suggestion fix object", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + suggest: [{ + messageId: "suggestion1", + fix: () => fix + }] + }); + + assert.deepStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual(translatedReport.suggestions[0].fix.range, fix.range); + }); + + it("should create a new fix object with a new range array when suggestion `fix()` returns an array with a single item", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + suggest: [{ + messageId: "suggestion1", + fix: () => [fix] + }] + }); + + assert.deepStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual(translatedReport.suggestions[0].fix.range, fix.range); + }); + + it("should create a new fix object with a new range array when suggestion `fix()` returns an array with multiple items", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + suggest: [{ + messageId: "suggestion1", + fix: () => [fix, additionalFix] + }] + }); + + assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual(translatedReport.suggestions[0].fix.range, fix.range); + assert.notStrictEqual(translatedReport.suggestions[0].fix, additionalFix); + assert.notStrictEqual(translatedReport.suggestions[0].fix.range, additionalFix.range); + }); + + it("should create a new fix object with a new range array when suggestion `fix()` generator yields a single item", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + suggest: [{ + messageId: "suggestion1", + *fix() { + yield fix; + } + }] + }); + + assert.deepStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual(translatedReport.suggestions[0].fix.range, fix.range); + }); + + it("should create a new fix object with a new range array when suggestion `fix()` generator yields multiple items", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + suggest: [{ + messageId: "suggestion1", + *fix() { + yield fix; + yield additionalFix; + } + }] + }); + + assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual(translatedReport.suggestions[0].fix.range, fix.range); + assert.notStrictEqual(translatedReport.suggestions[0].fix, additionalFix); + assert.notStrictEqual(translatedReport.suggestions[0].fix.range, additionalFix.range); + }); + + it("should create different instances of range arrays when suggestions reuse the same instance", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + suggest: [ + { + messageId: "suggestion1", + fix: () => ({ range, text: "baz" }) + }, + { + messageId: "suggestion2", + data: { interpolated: "'interpolated value'" }, + fix: () => ({ range, text: "qux" }) + } + ] + }); + + assert.deepStrictEqual(translatedReport.suggestions[0].fix.range, range); + assert.deepStrictEqual(translatedReport.suggestions[1].fix.range, range); + assert.notStrictEqual(translatedReport.suggestions[0].fix.range, translatedReport.suggestions[1].fix.range); + }); + }); }); From cdd063c388bbfe1781d7a864a832f03a2c1cc277 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 4 Jul 2023 17:29:53 -0400 Subject: [PATCH 078/248] feat: Expose LegacyESLint in unsupported API (#17341) fixes #17340 --- lib/unsupported-api.js | 4 +++- tests/lib/unsupported-api.js | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/unsupported-api.js b/lib/unsupported-api.js index b688608ca884..8a2e147aabec 100644 --- a/lib/unsupported-api.js +++ b/lib/unsupported-api.js @@ -14,6 +14,7 @@ const { FileEnumerator } = require("./cli-engine/file-enumerator"); const { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint"); const FlatRuleTester = require("./rule-tester/flat-rule-tester"); +const { ESLint } = require("./eslint/eslint"); //----------------------------------------------------------------------------- // Exports @@ -24,5 +25,6 @@ module.exports = { FlatESLint, shouldUseFlatConfig, FlatRuleTester, - FileEnumerator + FileEnumerator, + LegacyESLint: ESLint }; diff --git a/tests/lib/unsupported-api.js b/tests/lib/unsupported-api.js index 3a65ba230f80..4ad46c3d2176 100644 --- a/tests/lib/unsupported-api.js +++ b/tests/lib/unsupported-api.js @@ -27,6 +27,14 @@ describe("unsupported-api", () => { assert.isFunction(api.FlatESLint); }); + it("should have LegacyESLint exposed", () => { + assert.isFunction(api.LegacyESLint); + }); + + it("should not have ESLint exposed", () => { + assert.isUndefined(api.ESLint); + }); + it("should have shouldUseFlatConfig exposed", () => { assert.isFunction(api.shouldUseFlatConfig); }); From c6f8cd0d62e4a3c314c6860ff367490bbd05325a Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 7 Jul 2023 11:21:16 +0200 Subject: [PATCH 079/248] chore: Remove `defaultIgnores` from FlatESLint private members (#17349) --- lib/eslint/flat-eslint.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/eslint/flat-eslint.js b/lib/eslint/flat-eslint.js index 9d511aab2e84..f2d6082eb3c6 100644 --- a/lib/eslint/flat-eslint.js +++ b/lib/eslint/flat-eslint.js @@ -576,7 +576,6 @@ class FlatESLint { cacheFilePath, lintResultCache, defaultConfigs, - defaultIgnores: () => false, configs: null }); From 7bf2e86022c9e95db4ca1472fddfa2ea4edd1870 Mon Sep 17 00:00:00 2001 From: Percy Ma Date: Sat, 8 Jul 2023 19:05:46 +0800 Subject: [PATCH 080/248] chore: remove unused dependencies (#17352) --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 6dd1d142c828..9f5c2a8a4ba3 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,6 @@ "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", @@ -97,7 +96,6 @@ "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "devDependencies": { @@ -156,7 +154,6 @@ "semver": "^7.5.3", "shelljs": "^0.8.2", "sinon": "^11.0.0", - "temp": "^0.9.0", "webpack": "^5.23.0", "webpack-cli": "^4.5.0", "yorkie": "^2.0.0" From b762632298f20c4f81e7d01ab850c3f5e3874637 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Mon, 10 Jul 2023 08:07:15 +0000 Subject: [PATCH 081/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c8b99345be3..cd7d6f8f3b81 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition Mercedes-Benz Group HeroCoders QuickBooks Tool hub

+

Incognito ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition Mercedes-Benz Group HeroCoders QuickBooks Tool hub

## Technology Sponsors From 5ca9b4d29f747e9cf5c9055e85c93b3b605d57fc Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 10 Jul 2023 17:38:50 +0200 Subject: [PATCH 082/248] chore: update eslint-config-eslint exports (#17336) * chore: update eslint-config-eslint export * update naming in README * main export for ESM, additional for CJS * update usage in README * update package.exports * rename file * add eslintrc export * add `files` to simplify usage --- eslint.config.js | 4 +- packages/eslint-config-eslint/README.md | 26 +- packages/eslint-config-eslint/base.js | 387 ++++++++++++++++++++ packages/eslint-config-eslint/cjs.js | 9 + packages/eslint-config-eslint/index.js | 407 +-------------------- packages/eslint-config-eslint/nodejs.js | 33 ++ packages/eslint-config-eslint/package.json | 14 +- 7 files changed, 477 insertions(+), 403 deletions(-) create mode 100644 packages/eslint-config-eslint/base.js create mode 100644 packages/eslint-config-eslint/cjs.js create mode 100644 packages/eslint-config-eslint/nodejs.js diff --git a/eslint.config.js b/eslint.config.js index 09497a915b64..214be713d4c9 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -31,7 +31,7 @@ const eslintPluginRulesRecommendedConfig = require("eslint-plugin-eslint-plugin/ const eslintPluginTestsRecommendedConfig = require("eslint-plugin-eslint-plugin/configs/tests-recommended"); const globals = require("globals"); const merge = require("lodash.merge"); -const baseConfig = require("eslint-config-eslint"); +const eslintConfigESLintCJS = require("eslint-config-eslint/cjs"); //----------------------------------------------------------------------------- // Helpers @@ -75,7 +75,7 @@ function createInternalFilesPatterns(pattern = null) { } module.exports = [ - ...baseConfig, + ...eslintConfigESLintCJS, { ignores: [ "build/**", diff --git a/packages/eslint-config-eslint/README.md b/packages/eslint-config-eslint/README.md index 6be0d1c91f10..645a1938260a 100644 --- a/packages/eslint-config-eslint/README.md +++ b/packages/eslint-config-eslint/README.md @@ -22,11 +22,33 @@ npm install eslint-config-eslint --save-dev ## Usage +### ESM (`"type":"module"`) projects + In your `eslint.config.js` file, add: ```js -const eslintConfig = require("eslint-config-eslint"); -module.exports = eslintConfig; +import eslintConfigESLint from "eslint-config-eslint"; + +export default [ + ...eslintConfigESLint +]; +``` + +**Note**: This configuration array contains configuration objects with the `files` property. + +* `files: ["**/*.js"]`: ESM-specific configurations. +* `files: ["**/*.cjs"]`: CommonJS-specific configurations. + +### CommonJS projects + +In your `eslint.config.js` file, add: + +```js +const eslintConfigESLintCJS = require("eslint-config-eslint/cjs"); + +module.exports = [ + ...eslintConfigESLintCJS +]; ``` ### Where to ask for help? diff --git a/packages/eslint-config-eslint/base.js b/packages/eslint-config-eslint/base.js new file mode 100644 index 000000000000..1365108390d5 --- /dev/null +++ b/packages/eslint-config-eslint/base.js @@ -0,0 +1,387 @@ +"use strict"; + +const js = require("@eslint/js"); +const jsdoc = require("eslint-plugin-jsdoc"); +const eslintComments = require("eslint-plugin-eslint-comments"); +const unicorn = require("eslint-plugin-unicorn"); + +/* + * the plugins' configs are not updated to support the flat config, + * need to manually update the `plugins` property + */ +jsdoc.configs.recommended.plugins = { jsdoc }; +eslintComments.configs.recommended.plugins = { "eslint-comments": eslintComments }; + +// extends eslint recommended config +const jsConfigs = [js.configs.recommended, { + 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", + "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", + "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-floating-decimal": "error", + "no-implied-eval": "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-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", + "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-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", + "wrap-iife": "error", + "yield-star-spacing": "error", + yoda: ["error", "never", { exceptRange: true }] + } +}]; + +// extends eslint-plugin-jsdoc's recommended config +const jsdocConfigs = [jsdoc.configs.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" + } + } + }, + rules: { + "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", + "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" + } +}]; + +// extends eslint-plugin-unicorn's config +const unicornConfigs = [{ + plugins: { unicorn }, + rules: { + "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-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" + } +}]; + +// extends eslint-plugin-eslint-comments's recommended config +const eslintCommentsConfigs = [eslintComments.configs.recommended, { + rules: { + "eslint-comments/disable-enable-pair": ["error"], + "eslint-comments/no-unused-disable": "error", + "eslint-comments/require-description": "error" + } +}]; + +module.exports = [ + { linterOptions: { reportUnusedDisableDirectives: true } }, + ...jsConfigs, + ...unicornConfigs, + ...jsdocConfigs, + ...eslintCommentsConfigs +]; diff --git a/packages/eslint-config-eslint/cjs.js b/packages/eslint-config-eslint/cjs.js new file mode 100644 index 000000000000..61963a39bef8 --- /dev/null +++ b/packages/eslint-config-eslint/cjs.js @@ -0,0 +1,9 @@ +"use strict"; + +const baseConfigs = require("./base"); +const { cjsConfigs } = require("./nodejs"); + +module.exports = [ + ...baseConfigs, + ...cjsConfigs +]; diff --git a/packages/eslint-config-eslint/index.js b/packages/eslint-config-eslint/index.js index 4057d6d944cf..8717218efe7d 100644 --- a/packages/eslint-config-eslint/index.js +++ b/packages/eslint-config-eslint/index.js @@ -1,401 +1,16 @@ "use strict"; -const nodeRecommendedConfig = require("eslint-plugin-n/configs/recommended-script"); -const js = require("@eslint/js"); -const jsdoc = require("eslint-plugin-jsdoc"); -const eslintComments = require("eslint-plugin-eslint-comments"); -const unicorn = require("eslint-plugin-unicorn"); - -/* - * the plugins' configs are not updated to support the flat config, - * need to manually update the `plugins` property - */ -jsdoc.configs.recommended.plugins = { jsdoc }; -eslintComments.configs.recommended.plugins = { "eslint-comments": eslintComments }; - -// extends eslint-plugin-n's recommended config -const nodeConfigs = [nodeRecommendedConfig, { - rules: { - "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" - } -}]; - -// extends eslint recommended config -const jsConfigs = [js.configs.recommended, { - 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", - "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", - "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-floating-decimal": "error", - "no-implied-eval": "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-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", - "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-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", - "wrap-iife": "error", - "yield-star-spacing": "error", - yoda: ["error", "never", { exceptRange: true }] - } -}]; - -// extends eslint-plugin-jsdoc's recommended config -const jsdocConfigs = [jsdoc.configs.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" - } - } - }, - rules: { - "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", - "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" - } -}]; - -// extends eslint-plugin-unicorn's config -const unicornConfigs = [{ - plugins: { unicorn }, - rules: { - "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-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" - } -}]; - -// extends eslint-plugin-eslint-comments's recommended config -const eslintCommentsConfigs = [eslintComments.configs.recommended, { - rules: { - "eslint-comments/disable-enable-pair": ["error"], - "eslint-comments/no-unused-disable": "error", - "eslint-comments/require-description": "error" - } -}]; +const baseConfigs = require("./base"); +const { esmConfigs, cjsConfigs } = require("./nodejs"); module.exports = [ - { linterOptions: { reportUnusedDisableDirectives: true } }, - ...jsConfigs, - ...nodeConfigs, - ...unicornConfigs, - ...jsdocConfigs, - ...eslintCommentsConfigs + ...baseConfigs, + ...esmConfigs.map(config => ({ + files: ["**/*.js"], + ...config + })), + ...cjsConfigs.map(config => ({ + files: ["**/*.cjs"], + ...config + })) ]; diff --git a/packages/eslint-config-eslint/nodejs.js b/packages/eslint-config-eslint/nodejs.js new file mode 100644 index 000000000000..368c67dc86b4 --- /dev/null +++ b/packages/eslint-config-eslint/nodejs.js @@ -0,0 +1,33 @@ +"use strict"; + +const recommendedScriptConfig = require("eslint-plugin-n/configs/recommended-script"); +const recommendedModuleConfig = require("eslint-plugin-n/configs/recommended-module"); + +const sharedRules = { + "n/callback-return": ["error", ["cb", "callback", "next"]], + "n/handle-callback-err": ["error", "err"] +}; + +const cjsConfigs = [ + recommendedScriptConfig, + { + rules: { + ...sharedRules, + "n/no-mixed-requires": "error", + "n/no-new-require": "error", + "n/no-path-concat": "error" + } + } +]; + +const esmConfigs = [ + recommendedModuleConfig, + { + rules: sharedRules + } +]; + +module.exports = { + cjsConfigs, + esmConfigs +}; diff --git a/packages/eslint-config-eslint/package.json b/packages/eslint-config-eslint/package.json index 1a779c1a1ef4..ffd1fbceb93a 100644 --- a/packages/eslint-config-eslint/package.json +++ b/packages/eslint-config-eslint/package.json @@ -3,15 +3,23 @@ "version": "8.0.0", "author": "Nicholas C. Zakas ", "description": "Default ESLint configuration for ESLint projects.", + "exports": { + "./package.json": "./package.json", + ".": "./index.js", + "./cjs": "./cjs.js", + "./eslintrc": "./eslintrc.js" + }, "scripts": { - "test": "node ./index.js", + "test": "node ./index.js && node ./cjs.js", "prepublish": "npm test" }, "files": [ "LICENSE", "README.md", + "base.js", + "cjs.js", "index.js", - "default.yml" + "nodejs.js" ], "repository": { "type": "git", @@ -33,6 +41,6 @@ ], "license": "MIT", "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } } From 84d243b245b01b667f0752b592e8bda02a9aa2b1 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Tue, 11 Jul 2023 08:07:09 +0000 Subject: [PATCH 083/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cd7d6f8f3b81..5335d77adfc7 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

Incognito ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition Mercedes-Benz Group HeroCoders QuickBooks Tool hub

+

Charles ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition Mercedes-Benz Group HeroCoders QuickBooks Tool hub

## Technology Sponsors From b79b6fb64473969b426d086b484d2e29594a5e9a Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Tue, 11 Jul 2023 17:56:55 +0200 Subject: [PATCH 084/248] fix: Fix suggestion message in `no-useless-escape` (#17339) * fix: Fix suggestion message in `no-useless-escape` * Fixes * Fix for nodes with null `directive` * Empty commit * Remove fallback for `isDirective` * Remove duplicate tests --- lib/rules/no-useless-escape.js | 6 ++- lib/rules/padding-line-between-statements.js | 46 ++------------------ lib/rules/utils/ast-utils.js | 12 ++++- tests/lib/rules/no-useless-escape.js | 37 ++++++++++++++++ tests/lib/rules/utils/ast-utils.js | 45 +++++++++++++++++++ 5 files changed, 102 insertions(+), 44 deletions(-) diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js index 8304d915f9e5..71d006b2323e 100644 --- a/lib/rules/no-useless-escape.js +++ b/lib/rules/no-useless-escape.js @@ -94,6 +94,7 @@ module.exports = { messages: { unnecessaryEscape: "Unnecessary escape character: \\{{character}}.", removeEscape: "Remove the `\\`. This maintains the current functionality.", + removeEscapeDoNotKeepSemantics: "Remove the `\\` if it was inserted by mistake.", escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character." }, @@ -125,7 +126,10 @@ module.exports = { data: { character }, suggest: [ { - messageId: "removeEscape", + + // Removing unnecessary `\` characters in a directive is not guaranteed to maintain functionality. + messageId: astUtils.isDirective(node.parent) + ? "removeEscapeDoNotKeepSemantics" : "removeEscape", fix(fixer) { return fixer.removeRange(range); } diff --git a/lib/rules/padding-line-between-statements.js b/lib/rules/padding-line-between-statements.js index 6b165c07f273..95e08736a9c5 100644 --- a/lib/rules/padding-line-between-statements.js +++ b/lib/rules/padding-line-between-statements.js @@ -130,42 +130,6 @@ function isBlockLikeStatement(sourceCode, node) { ); } -/** - * Check whether the given node is a directive or not. - * @param {ASTNode} node The node to check. - * @param {SourceCode} sourceCode The source code object to get tokens. - * @returns {boolean} `true` if the node is a directive. - */ -function isDirective(node, sourceCode) { - return ( - astUtils.isTopLevelExpressionStatement(node) && - node.expression.type === "Literal" && - typeof node.expression.value === "string" && - !astUtils.isParenthesised(sourceCode, node.expression) - ); -} - -/** - * Check whether the given node is a part of directive prologue or not. - * @param {ASTNode} node The node to check. - * @param {SourceCode} sourceCode The source code object to get tokens. - * @returns {boolean} `true` if the node is a part of directive prologue. - */ -function isDirectivePrologue(node, sourceCode) { - if (isDirective(node, sourceCode)) { - for (const sibling of node.parent.body) { - if (sibling === node) { - break; - } - if (!isDirective(sibling, sourceCode)) { - return false; - } - } - return true; - } - return false; -} - /** * Gets the actual last token. * @@ -359,12 +323,10 @@ const StatementTypes = { CJS_IMPORT.test(sourceCode.getText(node.declarations[0].init)) }, directive: { - test: isDirectivePrologue + test: astUtils.isDirective }, expression: { - test: (node, sourceCode) => - node.type === "ExpressionStatement" && - !isDirectivePrologue(node, sourceCode) + test: node => node.type === "ExpressionStatement" && !astUtils.isDirective(node) }, iife: { test: isIIFEStatement @@ -375,10 +337,10 @@ const StatementTypes = { isBlockLikeStatement(sourceCode, node) }, "multiline-expression": { - test: (node, sourceCode) => + test: node => node.loc.start.line !== node.loc.end.line && node.type === "ExpressionStatement" && - !isDirectivePrologue(node, sourceCode) + !astUtils.isDirective(node) }, "multiline-const": newMultilineKeywordTester("const"), diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index e8ed3edd85c8..08a23c888786 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -1006,6 +1006,15 @@ function isTopLevelExpressionStatement(node) { } +/** + * Check whether the given node is a part of a directive prologue or not. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is a part of directive prologue. + */ +function isDirective(node) { + return node.type === "ExpressionStatement" && typeof node.directive === "string"; +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -2158,5 +2167,6 @@ module.exports = { getSwitchCaseColonToken, getModuleExportName, isConstant, - isTopLevelExpressionStatement + isTopLevelExpressionStatement, + isDirective }; diff --git a/tests/lib/rules/no-useless-escape.js b/tests/lib/rules/no-useless-escape.js index 09f146ef8c66..77a3c57807bf 100644 --- a/tests/lib/rules/no-useless-escape.js +++ b/tests/lib/rules/no-useless-escape.js @@ -1066,6 +1066,43 @@ ruleTester.run("no-useless-escape", rule, { output: "`\\\\a```" }] }] + }, + + // https://github.com/eslint/eslint/issues/16988 + { + code: String.raw`"use\ strict";`, + errors: [{ + line: 1, + column: 5, + endColumn: 6, + message: "Unnecessary escape character: \\ .", + type: "Literal", + suggestions: [{ + messageId: "removeEscapeDoNotKeepSemantics", + output: String.raw`"use strict";` + }, { + messageId: "escapeBackslash", + output: String.raw`"use\\ strict";` + }] + }] + }, + { + code: String.raw`({ foo() { "foo"; "bar"; "ba\z" } })`, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + line: 1, + column: 29, + endColumn: 30, + message: "Unnecessary escape character: \\z.", + type: "Literal", + suggestions: [{ + messageId: "removeEscapeDoNotKeepSemantics", + output: String.raw`({ foo() { "foo"; "bar"; "baz" } })` + }, { + messageId: "escapeBackslash", + output: String.raw`({ foo() { "foo"; "bar"; "ba\\z" } })` + }] + }] } ] }); diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js index 59486b237ef4..bc03c7e47cdd 100644 --- a/tests/lib/rules/utils/ast-utils.js +++ b/tests/lib/rules/utils/ast-utils.js @@ -1875,4 +1875,49 @@ describe("ast-utils", () => { }); }); }); + + describe("isDirective", () => { + const expectedResults = [ + { code: '"use strict";', expectedRetVal: true }, + { code: '"use strict"; "use asm";', nodeText: '"use asm";', expectedRetVal: true }, + { code: 'const a = () => { "foo"; }', nodeText: '"foo";', expectedRetVal: true }, + { code: '"";', expectedRetVal: true }, + { code: '{ "foo"; }', nodeText: '"foo";', expectedRetVal: false }, + { code: "foo();", expectedRetVal: false }, + { code: '"foo" + "bar";', expectedRetVal: false }, + { code: "12345;", expectedRetVal: false }, + { code: "`foo`;", expectedRetVal: false }, + { code: "('foo');", expectedRetVal: false }, + { code: 'foo(); "use strict";', nodeText: '"use strict";', expectedRetVal: false } + ]; + + expectedResults.forEach(({ code, nodeText = code, expectedRetVal }) => { + it(`should return ${expectedRetVal} for \`${nodeText}\` in \`${code}\``, () => { + linter.defineRule("checker", { + create: mustCall(({ sourceCode }) => { + const assertForNode = mustCall( + node => assert.strictEqual(astUtils.isDirective(node), expectedRetVal) + ); + + return ({ + ExpressionStatement(node) { + if (sourceCode.getText(node) === nodeText) { + assertForNode(node); + + if (!expectedRetVal) { + + // The flow parser sets `directive` to null on non-directive ExpressionStatement nodes. + node.directive = null; + assertForNode(node); + } + } + } + }); + }) + }); + + linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 2022 } }); + }); + }); + }); }); From bb3090897166dbfd2931a43a70e2a5c1f3fa0a07 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 13 Jul 2023 08:07:05 +0000 Subject: [PATCH 085/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5335d77adfc7..78598b3a01c1 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

Charles ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition Mercedes-Benz Group HeroCoders QuickBooks Tool hub

+

iBoysoft ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition Mercedes-Benz Group HeroCoders QuickBooks Tool hub

## Technology Sponsors From 8bcbf11b6050418262ffa8e0ca37f365ae92e7ce Mon Sep 17 00:00:00 2001 From: Ben Perlmutter <57849986+bpmutter@users.noreply.github.com> Date: Thu, 13 Jul 2023 15:30:35 -0300 Subject: [PATCH 086/248] docs: Config Migration Guide (#17230) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: config migration guide * first full draft of page * Fix lint errs * Add set up section * add reviewer feedback to migration guide * copy edits * Apply suggestions from code review Co-authored-by: ๅ”ฏ็„ถ * implement feedback * Apply suggestions from NZ code review Co-authored-by: Nicholas C. Zakas * implement reviewer feedback * copy edit * Apply suggestions from code review Co-authored-by: Francesco Trotta Co-authored-by: Nicholas C. Zakas * remove example subheadings * Apply suggestions from code review Co-authored-by: Nicholas C. Zakas * linter options section * Apply suggestions from code review Co-authored-by: Milos Djermanovic * Update flat config import explanation * Apply suggestions from code review Co-authored-by: Francesco Trotta * Fix indentation * Apply suggestions from code review * Apply suggestions from code review Co-authored-by: Nicholas C. Zakas --------- Co-authored-by: ๅ”ฏ็„ถ Co-authored-by: Nicholas C. Zakas Co-authored-by: Francesco Trotta Co-authored-by: Milos Djermanovic --- docs/src/use/configure/migration-guide.md | 442 ++++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 docs/src/use/configure/migration-guide.md diff --git a/docs/src/use/configure/migration-guide.md b/docs/src/use/configure/migration-guide.md new file mode 100644 index 000000000000..4791dc7c5d0e --- /dev/null +++ b/docs/src/use/configure/migration-guide.md @@ -0,0 +1,442 @@ +--- +title: Configuration Migration Guide +eleventyNavigation: + key: migration guide + parent: configure + title: Configuration Migration Guide + order: 8 +--- + +This guide provides an overview of how you can migrate your ESLint configuration file from the eslintrc format (typically configured in `.eslintrc.js` or `.eslintrc.json` files) to the new flat config format (typically configured in an `eslint.config.js` file). + +To learn more about the flat config format, refer to [this blog post](https://eslint.org/blog/2022/08/new-config-system-part-2/). + +For reference information on these configuration formats, refer to the following documentation: + +* [eslintrc configuration files](configuration-files) +* [flat configuration files](configuration-files-new) + +## 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. + +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`. + +## Things That Havenโ€™t Changed between Configuration File Formats + +While the configuration file format has changed from eslintrc to flat config, the following has stayed the same: + +* Syntax for configuring rules +* Syntax for configuring processors +* The CLI, except for the flag changes noted in [CLI Flag Changes](#cli-flag-changes). +* Global variables are configured the same way, but on a different property (see [Configuring Language Options](#configuring-language-options)). + +## Key Differences between Configuration Formats + +A few of the most notable differences between the eslintrc and flat config formats are the following: + +### Importing Plugins and Custom Parsers + +Eslintrc files use string-based import system inside the `plugins` property to load plugins and inside the `extends` property to load external configurations. + +Flat config files represent plugins and parsers as JavaScript objects. This means you can use CommonJS `require()` or ES module `import` statements to load plugins and custom parsers from external files. + +For example, this eslintrc config file loads `eslint-plugin-jsdoc` and configures rules from that plugin: + +```javascript +// .eslintrc.js + +module.exports = { + // ...other config + plugins: ["jsdoc"], + rules: { + "jsdoc/require-description": "error", + "jsdoc/check-values": "error" + } + // ...other config +}; +``` + +In flat config, you would do the same thing like this: + +```javascript +// eslint.config.js + +import jsdoc from "eslint-plugin-jsdoc"; + +export default [ + { + files: ["**/*.js"], + plugins: { + jsdoc: jsdoc + }, + rules: { + "jsdoc/require-description": "error", + "jsdoc/check-values": "error" + } + } +]; +``` + +### 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. + +In flat config files, import a custom parser as a module, then assign it to the `languageOptions.parser` property of a configuration object. + +For example, this eslintrc config file uses the `@babel/eslint-parser` parser: + +```javascript +// .eslintrc.js + +module.exports = { + // ...other config + parser: "@babel/eslint-parser", + // ...other config +}; +``` + +In flat config, you would do the same thing like this: + +```javascript +// eslint.config.js + +import babelParser from "@babel/eslint-parser"; + +export default [ + { + // ...other config + languageOptions: { + parser: babelParser + } + // ...other config + } +]; +``` + +### Glob-Based Configs + +By default, eslintrc files lint all files (except those covered by `.eslintignore`) in the directory in which theyโ€™re placed and its child directories. If you want to have different configurations for different file glob patterns, you can specify them in the `overrides` property. + +By default, flat config files support different glob pattern-based configs in exported array. You can include the glob pattern in a config object's `files` property. If you don't specify a `files` property, the config defaults to the glob pattern `"**/*.{js,mjs,cjs}"`. Basically, all configuration in the flat config file is like the eslintrc `overrides` property. + +#### eslintrc Examples + +For example, this eslintrc file applies to all files in the directory where it is placed and its child directories: + +```javascript +// .eslintrc.js + +module.exports = { + // ...other config + rules: { + semi: ["warn", "always"] + } +}; +``` + +This eslintrc file supports multiple configs with overrides: + +```javascript +// .eslintrc.js + +module.exports = { + // ...other config + overrides: [ + { + files: ["src/**/*"], + rules: { + semi: ["warn", "always"] + } + }, + { + files:["test/**/*"], + rules: { + "no-console": "off" + } + } + ] +}; +``` + +For flat config, here is a configuration with the default glob pattern: + +```javascript +// eslint.config.js + +import js from "@eslint/js"; + +export default [ + js.configs.recommended, // Recommended config applied to all files + // Override the recommended config + { + rules: { + indent: ["error", 2], + "no-unused-vars": "warn" + } + // ...other configuration + } +]; +``` + +A flag config example configuration supporting multiple configs for different glob patterns: + +```javascript +// eslint.config.js + +import js from "@eslint/js"; + +export default [ + js.configs.recommended, // Recommended config applied to all files + // File-pattern specific overrides + { + files: ["src/**/*", "test/**/*"], + rules: { + semi: ["warn", "always"] + } + }, + { + files:["test/**/*"], + rules: { + "no-console": "off" + } + } + // ...other configurations +]; +``` + +### Configuring Language Options + +In eslintrc files, you configure various language options across the `env`, `globals` and `parserOptions` properties. Groups of global variables for specific runtimes (e.g. `document` and `window` for browser JavaScript; `process` and `require` for Node.js) are configured with the `env` property. + +In flat config files, the `globals`, and `parserOptions` are consolidated under the `languageOptions` key; the `env` property doesn't exist. Groups of global variables for specific runtimes are imported from the [globals](https://www.npmjs.com/package/globals) npm package and included in the `globals` property. You can use the spread operator (`...`) to import multiple globals at once. + +For example, here's a eslintrc file with language options: + +```javascript +// .eslintrc.js + +module.exports = { + env: { + browser: true, + }, + globals: { + myCustomGlobal: "readonly", + }, + parserOptions: { + ecmaVersion: 2022, + sourceType: "module" + } + // ...other config +} +``` + +Here's the same configuration in flat config: + +```javascript +// eslint.config.js + +import globals from "globals"; + +export default [ + { + languageOptions: { + globals: { + ...globals.browser, + myCustomGlobal: "readonly" + }, + parserOptions: { + ecmaVersion: 2022, + sourceType: "module" + } + } + // ...other config + } +]; +``` + +### Predefined Configs + +In eslintrc files, use the `extends` property to use predefined configs. ESLint comes with two predefined configs that you can access as strings: + +* `"eslint:recommended"`: the rules recommended by ESLint +* `"eslint:all"`: all rules shipped with ESLint + +You can also use the `extends` property to extend a custom config. Custom configs can either be paths to local config files or npm package names. + +In flat config files, predefined configs are imported from separate modules into flat config files. The `recommended` and `all` rules configs are located in the [`@eslint/js`](https://www.npmjs.com/package/@eslint/js) package. You must import this package to use these configs: + +```shell +npm install @eslint/js --save-dev +``` + +You can add each of these configs to the exported array or expose specific rules from them. You must import the modules for local config files and npm package configs with flat config. + +For example, here's an eslintrc file using the built-in `eslint:recommended` config: + +```javascript +// .eslintrc.js + +module.exports = { + // ...other config + extends: "eslint:recommended", + rules: { + semi: ["warn", "always"] + }, + // ...other config +} +``` + +This eslintrc file uses built-in config, local custom config, and custom config from an npm package: + +```javascript +// .eslintrc.js + +module.exports = { + // ...other config + extends: ["eslint:recommended", "./custom-config.js", "eslint-config-my-config"], + rules: { + semi: ["warn", "always"] + }, + // ...other config +} +``` + +To use the same configs in flat config, you would do the following: + +```javascript +// eslint.config.js + +import js from "@eslint/js"; +import customConfig from "./custom-config.js"; +import myConfig from "eslint-config-my-config"; + +export default [ + js.configs.recommended, + customConfig, + myConfig, + { + rules: { + semi: ["warn", "always"] + }, + // ...other config + } +]; +``` + +Note that because you are just importing JavaScript modules, you can mutate the config objects before ESLint uses them. For example, you might want to have a certain config object only apply to your test files: + +```javascript +// eslint.config.js + +import js from "@eslint/js"; +import customTestConfig from "./custom-test-config.js"; + +export default [ + js.configs.recommended, + { + ...customTestConfig, + files: ["**/*.test.js"], + }, +]; +``` + +### Ignoring Files + +With eslintrc, you can make ESLint ignore files by creating a separate `.eslintignore` file in the root of your project. The `.eslintignore` file uses the same glob pattern syntax as `.gitignore` files. Alternatively, you can use an `ignorePatterns` property in your eslintrc file. + +To ignore files with flat config, you can use the `ignores` property in a config object. The `ignores` property accepts an array of glob patterns. Note that flat config glob patterns do _not_ match dot files (e.g. `*.js` won't match `.dotfile.js`). Flat config does not support loading ignore patterns from `.eslintignore` files, so you'll need to migrate those patterns directly into flat config. + +For example, here's a `.eslintignore` example you can use with an eslintrc config: + +```shell +# .eslintignore +temp.js +.config/* +# ...other ignored files +``` + +`ignorePatterns` example: + +```javascript +// .eslintrc.js +module.exports = { + // ...other config + ignorePatterns: ["temp.js", ".config/*"], +}; +``` + +Here are the same files ignore patterns in flat config: + +```javascript +export default [ + { + // ...other config + ignores: ["temp.js", ".config/*"] + } +]; +``` + +### Linter Options + +ESlintrc files let you configure the linter itself with the `noInlineConfig` and `reportUnusedDisableDirectives` properties. + +The flat config system introduces a new top-level property `linterOptions` that you can use to configure the linter. In the `linterOptions` object, you can include `noInlineConfig` and `reportUnusedDisableDirectives`. + +For example, here's an eslintrc file with linter options enabled: + +```javascript +// .eslintrc.js + +module.exports = { + // ...other config + noInlineConfig: true, + reportUnusedDisableDirectives: true +} +``` + +Here's the same options in flat config: + +```javascript +// eslint.config.js + +export default [ + { + // ...other config + linterOptions: { + noInlineConfig: true, + reportUnusedDisableDirectives: true + } + } +]; +``` + +### CLI Flag Changes + +The following CLI flags are no longer supported with the flat config file format: + +* `--rulesdir` +* `--ext` +* `--resolve-plugins-relative-to` + +The flag `--no-eslintrc` has been replaced with `--no-config-lookup`. + +### Additional Changes + +The following changes have been made from the eslintrc to the flat config file format: + +* The `root` option no longer exists. (Flat config files act as if `root: true` is set.) +* The `files` option cannot be a single string anymore, it must be an array. +* The `sourceType` option now supports the new value `"commonjs"` (`.eslintrc` supports it too, but it was never documented). + +## TypeScript Types for Flat Config Files + +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). + +## Further Reading + +* [Overview of the flat config file format blog post](https://eslint.org/blog/2022/08/new-config-system-part-2/) +* [API usage of new configuration system blog post](https://eslint.org/blog/2022/08/new-config-system-part-3/) +* [Background to new configuration system blog post](https://eslint.org/blog/2022/08/new-config-system-part-1/) From f8892b52920b8967f9e7bec23c75b74e03977d6b Mon Sep 17 00:00:00 2001 From: Matt Wilkinson Date: Fri, 14 Jul 2023 15:58:44 +0100 Subject: [PATCH 087/248] docs: Expand rule option schema docs (#17198) --- docs/src/extend/custom-rules.md | 155 +++++++++++++++++++++++++++++--- 1 file changed, 142 insertions(+), 13 deletions(-) diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index 944d334c0f5d..35df20541b6d 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -613,39 +613,168 @@ You can also access comments through many of `sourceCode`'s methods using the `i ### Options Schemas -Rules may export a `schema` property, which is a [JSON Schema](https://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. +Rules may specify a `schema` property, which is a [JSON Schema](https://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. -There are two formats for a rule's exported `schema`: +Note: Prior to ESLint v9.0.0, rules without a schema are passed their options directly from the config without any validation. In ESLint v9.0.0 and later, rules without schemas will throw errors when options are passed. See the [Require schemas and object-style rules](https://github.com/eslint/rfcs/blob/main/designs/2021-schema-object-rules/README.md) RFC for further details. -1. A full JSON Schema object describing all possible options the rule accepts. -2. An array of JSON Schema objects for each optional positional argument. +When validating a rule's config, there are five steps: -In both cases, these should exclude the [severity](../use/configure/rules#rule-severities), as ESLint automatically validates this first. +1. If the rule config is not an array, then the value is wrapped into an array (e.g. `"off"` becomes `["off"]`); if the rule config is an array then it is used directly. +2. ESLint validates the first element of the rule config array as a severity (`"off"`, `"warn"`, `"error"`, `0`, `1`, `2`) +3. If the severity is `off` or `0`, then the rule is disabled and validation stops, ignoring any other elements of the rule config array. +4. If the rule is enabled, then any elements of the array after the severity are copied into the `context.options` array (e.g. a config of `["warn", "never", { someOption: 5 }]` results in `context.options = ["never", { someOption: 5 }]`) +5. The rule's schema validation is run on the `context.options` array. + +Note: this means that the rule schema cannot validate the severity. The rule schema only validates the array elements _after_ the severity in a rule config. There is no way for a rule to know what severity it is configured at. + +There are two formats for a rule's `schema`: + +* An array of JSON Schema objects + * Each element will be checked against the same position in the `context.options` array. + * If the `context.options` array has fewer elements than there are schemas, then the unmatched schemas are ignored + * If the `context.options` array has more elements than there are schemas, then the validation fails + * There are two important consequences to using this format: + * It is _always valid_ for a user to provide no options to your rule (beyond severity) + * If you specify an empty array, then it is _always an error_ for a user to provide any options to your rule (beyond severity) +* A full JSON Schema object that will validate the `context.options` array + * The schema should assume an array of options to validate even if your rule only accepts one option. + * The schema can be arbitrarily complex, so you can validate completely different sets of potential options via `oneOf`, `anyOf` etc. + * The supported version of JSON Schemas is [Draft-04](http://json-schema.org/draft-04/schema), so some newer features such as `if` or `$data` are unavailable. + * At present, it is explicitly planned to not update schema support beyond this level due to ecosystem compatibility concerns. See [this comment](https://github.com/eslint/eslint/issues/13888#issuecomment-872591875) for further context. For example, the `yoda` rule accepts a primary mode argument of `"always"` or `"never"`, as well as an extra options object with an optional property `exceptRange`: ```js +// Valid configuration: +// "yoda": "warn" +// "yoda": ["error"] +// "yoda": ["error", "always"] // "yoda": ["error", "never", { "exceptRange": true }] +// Invalid configuration: +// "yoda": ["warn", "never", { "exceptRange": true }, 5] +// "yoda": ["error", { "exceptRange": true }, "never"] module.exports = { meta: { schema: [ { - "enum": ["always", "never"] + enum: ["always", "never"] }, { - "type": "object", - "properties": { - "exceptRange": { - "type": "boolean" - } + type: "object", + properties: { + exceptRange: { type: "boolean" } }, - "additionalProperties": false + additionalProperties: false } ] - }, + } +}; +``` + +And here is the equivalent object-based schema: + +```js +// Valid configuration: +// "yoda": "warn" +// "yoda": ["error"] +// "yoda": ["error", "always"] +// "yoda": ["error", "never", { "exceptRange": true }] +// Invalid configuration: +// "yoda": ["warn", "never", { "exceptRange": true }, 5] +// "yoda": ["error", { "exceptRange": true }, "never"] +module.exports = { + meta: { + schema: { + type: "array", + minItems: 0, + maxItems: 2, + items: [ + { + enum: ["always", "never"] + }, + { + type: "object", + properties: { + exceptRange: { type: "boolean" } + }, + additionalProperties: false + } + ] + } + } }; ``` +Object schemas can be more precise and restrictive in what is permitted. For example, the below schema always requires the first option to be specified (a number between 0 and 10), but the second option is optional, and can either be an object with some options explicitly set, or `"off"` or `"strict"`. + +```js +// Valid configuration: +// "someRule": ["error", 6] +// "someRule": ["error", 5, "strict"] +// "someRule": ["warn", 10, { someNonOptionalProperty: true }] +// Invalid configuration: +// "someRule": "warn" +// "someRule": ["error"] +// "someRule": ["warn", 15] +// "someRule": ["warn", 7, { }] +// "someRule": ["error", 3, "on"] +// "someRule": ["warn", 7, { someOtherProperty: 5 }] +// "someRule": ["warn", 7, { someNonOptionalProperty: false, someOtherProperty: 5 }] +module.exports = { + meta: { + schema: { + type: "array", + minItems: 1, // Can't specify only severity! + maxItems: 2, + items: [ + { + type: "number", + minimum: 0, + maximum: 10 + }, + { + anyOf: [ + { + type: "object", + properties: { + someNonOptionalProperty: { type: "boolean" } + }, + required: ["someNonOptionalProperty"], + additionalProperties: false + }, + { + enum: ["off", "strict"] + } + ] + } + ] + } + } +} +``` + +Remember, rule options are always an array, so be careful not to specify a schema for a non-array type at the top level. If your schema does not specify an array at the top-level, users can _never_ enable your rule, as their configuration will always be invalid when the rule is enabled. + +Here's an example schema that will always fail validation: + +```js +// Possibly trying to validate ["error", { someOptionalProperty: true }] +// but when the rule is enabled, config will always fail validation because the options are an array which doesn't match "object" +module.exports = { + meta: { + schema: { + type: "object", + properties: { + someOptionalProperty: { + type: "boolean" + } + }, + additionalProperties: false + } + } +} +``` + **Note:** If your rule schema uses JSON schema [`$ref`](https://json-schema.org/understanding-json-schema/structuring.html#ref) properties, you must use the full JSON Schema object rather than the array of positional property schemas. This is because ESLint transforms the array shorthand into a single schema without updating references that makes them incorrect (they are ignored). To learn more about JSON Schema, we recommend looking at some examples on the [JSON Schema website](https://json-schema.org/learn/), or reading the free [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) ebook. From 89f3225108c66425e4132f76db6c1ab13aac98d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Fri, 14 Jul 2023 11:36:34 -0400 Subject: [PATCH 088/248] docs: add playground links to correct and incorrect code blocks (#17306) * docs: add playground links to correct and incorrect code blocks * Added info options and used existing styles * Switch options to parserOptions * Use context-based prefix for playground URLs * Tweaked styles for more padding, and overlaid vertically in thin screens * Reset md files to main * Removed icon and increased bottom padding --- docs/.eleventy.js | 53 ++++++++++++++++++++++++- docs/src/assets/js/main.js | 18 --------- docs/src/assets/scss/docs.scss | 9 +++-- docs/src/rules/no-implicit-globals.md | 22 +++++----- docs/src/rules/no-restricted-imports.md | 36 ++++++++--------- 5 files changed, 85 insertions(+), 53 deletions(-) diff --git a/docs/.eleventy.js b/docs/.eleventy.js index 1bd1de1af2f5..94a202112543 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -179,14 +179,63 @@ module.exports = function(eleventyConfig) { `.trim(); } + /** + * Encodes text in the base 64 format used in playground URL params. + * @param {string} text Text to be encoded to base 64. + * @see https://github.com/eslint/eslint.org/blob/1b2f2aabeac2955a076d61788da8a0008bca6fb6/src/playground/utils/unicode.js + * @returns {string} The base 64 encoded equivalent of the text. + */ + function encodeToBase64(text) { + /* global btoa -- It does exist, and is what the playground uses. */ + return btoa(unescape(encodeURIComponent(text))); + } + + /** + * Creates markdownItContainer settings for a playground-linked codeblock. + * @param {string} name Plugin name and class name to add to the code block. + * @returns {[string, object]} Plugin name and options for markdown-it. + */ + function withPlaygroundRender(name) { + return [ + name, + { + render(tokens, index) { + if (tokens[index].nesting !== 1) { + return ""; + } + + // See https://github.com/eslint/eslint.org/blob/ac38ab41f99b89a8798d374f74e2cce01171be8b/src/playground/App.js#L44 + const parserOptions = tokens[index].info?.split("correct ")[1]?.trim(); + const { content } = tokens[index + 1]; + const state = encodeToBase64( + JSON.stringify({ + ...(parserOptions && { options: { parserOptions: JSON.parse(parserOptions) } }), + text: content + }) + ); + const prefix = process.env.CONTEXT && process.env.CONTEXT !== "deploy-preview" + ? "" + : "https://eslint.org"; + + return ` +
+ + Open in Playground + + `.trim(); + } + } + ]; + } + const markdownIt = require("markdown-it"); const md = markdownIt({ html: true, linkify: true, typographer: true, highlight: (str, lang) => highlighter(md, str, lang) }) .use(markdownItAnchor, { slugify: s => slug(s) }) .use(markdownItContainer, "img-container", {}) - .use(markdownItContainer, "correct", {}) - .use(markdownItContainer, "incorrect", {}) + .use(markdownItContainer, ...withPlaygroundRender("correct")) + .use(markdownItContainer, ...withPlaygroundRender("incorrect")) .use(markdownItContainer, "warning", { render(tokens, idx) { return generateAlertMarkup("warning", tokens, idx); diff --git a/docs/src/assets/js/main.js b/docs/src/assets/js/main.js index bf3268266f40..80168a136c91 100644 --- a/docs/src/assets/js/main.js +++ b/docs/src/assets/js/main.js @@ -192,24 +192,6 @@ } })(); -// add "Open in Playground" button to code blocks -// (function() { -// let blocks = document.querySelectorAll('pre[class*="language-"]'); -// if (blocks) { -// blocks.forEach(function(block) { -// let button = document.createElement("a"); -// button.classList.add('c-btn--playground'); -// button.classList.add('c-btn'); -// button.classList.add('c-btn--secondary'); -// button.setAttribute("href", "#"); -// button.innerText = "Open in Playground"; -// block.appendChild(button); -// }); -// } -// })(); - - - // add utilities var util = { keyCodes: { diff --git a/docs/src/assets/scss/docs.scss b/docs/src/assets/scss/docs.scss index ee40123891d8..98d6de83d45c 100644 --- a/docs/src/assets/scss/docs.scss +++ b/docs/src/assets/scss/docs.scss @@ -142,13 +142,14 @@ pre[class*="language-"] { .c-btn.c-btn--playground { position: absolute; font-size: var(--step--1); - bottom: 0.5rem; - right: 0.5rem; + bottom: -1rem; + right: 1rem; offset-block-end: 0.5rem; offset-inline-end: 0.5rem; + z-index: 1; - @media all and (max-width: 768px) { - display: none; + @media all and (min-width: 768px) { + bottom: 1.5rem; } } diff --git a/docs/src/rules/no-implicit-globals.md b/docs/src/rules/no-implicit-globals.md index 1e5c5fad723f..6a479a0a765d 100644 --- a/docs/src/rules/no-implicit-globals.md +++ b/docs/src/rules/no-implicit-globals.md @@ -44,7 +44,7 @@ This rule disallows `var` and `function` declarations at the top-level script sc Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-implicit-globals: "error"*/ @@ -58,7 +58,7 @@ function bar() {} Examples of **correct** code for this rule: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-implicit-globals: "error"*/ @@ -79,7 +79,7 @@ window.bar = function() {}; Examples of **correct** code for this rule with `"parserOptions": { "sourceType": "module" }` in the ESLint configuration: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-implicit-globals: "error"*/ @@ -100,7 +100,7 @@ This does not apply to ES modules since the module code is implicitly in `strict Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-implicit-globals: "error"*/ @@ -127,7 +127,7 @@ or in a `/*global */` comment. Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-implicit-globals: "error"*/ @@ -155,7 +155,7 @@ If the variable is intended to be local to the script, wrap the code with a bloc Examples of **correct** code for this rule with `"lexicalBindings"` option set to `false` (default): -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-implicit-globals: ["error", {"lexicalBindings": false}]*/ @@ -171,7 +171,7 @@ class Bar {} Examples of **incorrect** code for this rule with `"lexicalBindings"` option set to `true`: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-implicit-globals: ["error", {"lexicalBindings": true}]*/ @@ -187,7 +187,7 @@ class Bar {} Examples of **correct** code for this rule with `"lexicalBindings"` option set to `true`: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-implicit-globals: ["error", {"lexicalBindings": true}]*/ @@ -221,7 +221,7 @@ Even the `typeof` check is not safe from TDZ reference exceptions. Examples of **incorrect** code for this rule with `"lexicalBindings"` option set to `true`: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-implicit-globals: ["error", {"lexicalBindings": true}]*/ @@ -239,7 +239,7 @@ const MyGlobalFunction = (function() { Examples of **correct** code for this rule with `"lexicalBindings"` option set to `true`: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-implicit-globals: ["error", {"lexicalBindings": true}]*/ @@ -261,7 +261,7 @@ You can use `/* exported variableName */` block comments in the same way as in [ Examples of **correct** code for `/* exported variableName */` operation: -::: correct +::: correct { "sourceType": "script" } ```js /* exported global_var */ diff --git a/docs/src/rules/no-restricted-imports.md b/docs/src/rules/no-restricted-imports.md index ece0ceb54902..9040170ed133 100644 --- a/docs/src/rules/no-restricted-imports.md +++ b/docs/src/rules/no-restricted-imports.md @@ -130,7 +130,7 @@ To restrict the use of all Node.js core imports (via Date: Fri, 14 Jul 2023 11:48:53 -0400 Subject: [PATCH 089/248] 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 9d9d9799f226..245ac45d3e59 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "8.44.0", + "version": "8.45.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 4c11517c528c7b987fa888796f0351cb8136793b Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 14 Jul 2023 11:53:17 -0400 Subject: [PATCH 090/248] Build: changelog update for 8.45.0 --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53d43c34d8aa..366782db95b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +v8.45.0 - July 14, 2023 + +* [`68f63d7`](https://github.com/eslint/eslint/commit/68f63d76ce785fab4f42b76f1599026eea379bf7) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`89f3225`](https://github.com/eslint/eslint/commit/89f3225108c66425e4132f76db6c1ab13aac98d7) docs: add playground links to correct and incorrect code blocks (#17306) (Josh Goldberg โœจ) +* [`f8892b5`](https://github.com/eslint/eslint/commit/f8892b52920b8967f9e7bec23c75b74e03977d6b) docs: Expand rule option schema docs (#17198) (Matt Wilkinson) +* [`8bcbf11`](https://github.com/eslint/eslint/commit/8bcbf11b6050418262ffa8e0ca37f365ae92e7ce) docs: Config Migration Guide (#17230) (Ben Perlmutter) +* [`bb30908`](https://github.com/eslint/eslint/commit/bb3090897166dbfd2931a43a70e2a5c1f3fa0a07) docs: Update README (GitHub Actions Bot) +* [`b79b6fb`](https://github.com/eslint/eslint/commit/b79b6fb64473969b426d086b484d2e29594a5e9a) fix: Fix suggestion message in `no-useless-escape` (#17339) (Francesco Trotta) +* [`84d243b`](https://github.com/eslint/eslint/commit/84d243b245b01b667f0752b592e8bda02a9aa2b1) docs: Update README (GitHub Actions Bot) +* [`5ca9b4d`](https://github.com/eslint/eslint/commit/5ca9b4d29f747e9cf5c9055e85c93b3b605d57fc) chore: update eslint-config-eslint exports (#17336) (Milos Djermanovic) +* [`b762632`](https://github.com/eslint/eslint/commit/b762632298f20c4f81e7d01ab850c3f5e3874637) docs: Update README (GitHub Actions Bot) +* [`7bf2e86`](https://github.com/eslint/eslint/commit/7bf2e86022c9e95db4ca1472fddfa2ea4edd1870) chore: remove unused dependencies (#17352) (Percy Ma) +* [`c6f8cd0`](https://github.com/eslint/eslint/commit/c6f8cd0d62e4a3c314c6860ff367490bbd05325a) chore: Remove `defaultIgnores` from FlatESLint private members (#17349) (Francesco Trotta) +* [`cdd063c`](https://github.com/eslint/eslint/commit/cdd063c388bbfe1781d7a864a832f03a2c1cc277) feat: Expose LegacyESLint in unsupported API (#17341) (Nicholas C. Zakas) +* [`c667055`](https://github.com/eslint/eslint/commit/c667055fb9da8ebac3a99f6e5a8b5565cc86af8e) fix: provide unique `fix` and `fix.range` objects in lint messages (#17332) (Milos Djermanovic) +* [`138c096`](https://github.com/eslint/eslint/commit/138c096bc9468b553dbafc0e573c6522a17a7922) docs: add more prefer-destructuring examples with array destructuring (#17330) (Milos Djermanovic) +* [`0052374`](https://github.com/eslint/eslint/commit/0052374035672efe9129343fc00ee51a4c288ff3) chore: move jsdoc settings to eslint-config-eslint (#17338) (ๅ”ฏ็„ถ) +* [`d34abe5`](https://github.com/eslint/eslint/commit/d34abe59eb23932dcbc79757d7932d08ee8b20e5) feat: fix indent rule for else-if (#17318) (Milos Djermanovic) +* [`1fc50a8`](https://github.com/eslint/eslint/commit/1fc50a89753346f4f4c786ffd20ac4cf185bb036) docs: `max-len` rule `code` and `tabWidth` as positional arguments (#17331) (Jesรบs Leganรฉs-Combarro) + v8.44.0 - June 30, 2023 * [`49e46ed`](https://github.com/eslint/eslint/commit/49e46edf3c8dc71d691a97fc33b63ed80ae0db0c) chore: upgrade @eslint/js@8.44.0 (#17329) (Milos Djermanovic) From 536cc343083ffd64172d4da13c7e043b140f1078 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 14 Jul 2023 11:53:18 -0400 Subject: [PATCH 091/248] 8.45.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 74b5418d8a79..3bb66bb434b1 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.44.0", + "version": "8.45.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 606fd49e0bd1..b4a942be584d 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

- 9 problems (5 errors, 4 warnings) - Generated on Fri Jun 30 2023 19:10:58 GMT-0400 (Eastern Daylight Time) + 9 problems (5 errors, 4 warnings) - Generated on Fri Jul 14 2023 11:53:18 GMT-0400 (Eastern Daylight Time)
diff --git a/package.json b/package.json index 9f5c2a8a4ba3..80545e2cdb23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.44.0", + "version": "8.45.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 0b0bbe07d4fb0870f3916e975b8ec6978f838077 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Sat, 15 Jul 2023 08:06:23 +0000 Subject: [PATCH 092/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 78598b3a01c1..978089a16722 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

iBoysoft ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition Mercedes-Benz Group HeroCoders QuickBooks Tool hub

+

iBoysoft ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord GitHub Transloadit Ignition Mercedes-Benz Group HeroCoders QuickBooks Tool hub

## Technology Sponsors From 7e9be4bd7331d0e8e8e0af0b075a2f6d28d1bea3 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Mon, 17 Jul 2023 08:07:16 +0000 Subject: [PATCH 093/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 978089a16722..2917009e2c99 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

iBoysoft ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord GitHub Transloadit Ignition Mercedes-Benz Group HeroCoders QuickBooks Tool hub

+

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord GitHub Transloadit Ignition Mercedes-Benz Group HeroCoders QuickBooks Tool hub

## Technology Sponsors From 42faa17b1c93f801b14bea2840d1d528e25c7211 Mon Sep 17 00:00:00 2001 From: Matt Wilkinson Date: Tue, 18 Jul 2023 13:46:20 +0100 Subject: [PATCH 094/248] fix: Update no-loop-func to not overlap with no-undef (#17358) --- lib/rules/no-loop-func.js | 2 +- tests/lib/rules/no-loop-func.js | 54 ++++++++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/lib/rules/no-loop-func.js b/lib/rules/no-loop-func.js index e1d65fdc92c1..48312fbf58a5 100644 --- a/lib/rules/no-loop-func.js +++ b/lib/rules/no-loop-func.js @@ -186,7 +186,7 @@ module.exports = { } const references = sourceCode.getScope(node).through; - const unsafeRefs = references.filter(r => !isSafe(loopNode, r)).map(r => r.identifier.name); + const unsafeRefs = references.filter(r => r.resolved && !isSafe(loopNode, r)).map(r => r.identifier.name); if (unsafeRefs.length > 0) { context.report({ diff --git a/tests/lib/rules/no-loop-func.js b/tests/lib/rules/no-loop-func.js index ba161ec49d61..f1311000f912 100644 --- a/tests/lib/rules/no-loop-func.js +++ b/tests/lib/rules/no-loop-func.js @@ -111,7 +111,53 @@ ruleTester.run("no-loop-func", rule, { "let a;" ].join("\n"), parserOptions: { ecmaVersion: 6 } + }, + + /* + * These loops _look_ like they might be unsafe, but because i is undeclared, they're fine + * at least as far as this rule is concerned - the loop doesn't declare/generate the variable. + */ + "while(i) { (function() { i; }) }", + "do { (function() { i; }) } while (i)", + + /** + * These loops _look_ like they might be unsafe, but because i is declared outside the loop + * and is not updated in or after the loop, they're fine as far as this rule is concerned. + * The variable that's captured is just the one variable shared by all the loops, but that's + * explicitly expected in these cases. + */ + "var i; while(i) { (function() { i; }) }", + "var i; do { (function() { i; }) } while (i)", + + /** + * These loops use an undeclared variable, and so shouldn't be flagged by this rule, + * they'll be picked up by no-undef. + */ + { + code: "for (var i=0; i x != undeclared)) { } }", + parserOptions: { ecmaVersion: 6 } } + ], invalid: [ { @@ -152,14 +198,6 @@ ruleTester.run("no-loop-func", rule, { code: "for (var i=0; i Date: Wed, 19 Jul 2023 17:35:17 -0400 Subject: [PATCH 095/248] feat: Improve config error messages (#17385) * feat: Improve config error messages Includes some keys to catch known eslintrc keys when they appear in flat config. This throws a specific error that the ESLint CLI can then output a more helpful message about. fixes #17370 * Update messages/eslintrc-incompat.js Co-authored-by: Milos Djermanovic * Update messages/eslintrc-incompat.js Co-authored-by: Milos Djermanovic * Apply feedback --------- Co-authored-by: Milos Djermanovic --- lib/config/flat-config-schema.js | 49 ++++++++++++ messages/eslintrc-incompat.js | 98 +++++++++++++++++++++++ tests/lib/config/flat-config-array.js | 28 +++++++ tests/lib/rule-tester/flat-rule-tester.js | 2 +- 4 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 messages/eslintrc-incompat.js diff --git a/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js index 10d6b50ef1ff..7325742adfcd 100644 --- a/lib/config/flat-config-schema.js +++ b/lib/config/flat-config-schema.js @@ -212,6 +212,22 @@ function assertIsObject(value) { } } +/** + * The error type when there's an eslintrc-style options in a flat config. + */ +class IncompatibleKeyError extends Error { + + /** + * @param {string} key The invalid key. + */ + constructor(key) { + super("This appears to be in eslintrc format rather than flat config format."); + this.messageTemplate = "eslintrc-incompat"; + this.messageData = { key }; + } +} + + //----------------------------------------------------------------------------- // Low-Level Schemas //----------------------------------------------------------------------------- @@ -438,11 +454,44 @@ const sourceTypeSchema = { } }; +/** + * Creates a schema that always throws an error. Useful for warning + * about eslintrc-style keys. + * @param {string} key The eslintrc key to create a schema for. + * @returns {ObjectPropertySchema} The schema. + */ +function createEslintrcErrorSchema(key) { + return { + merge: "replace", + validate() { + throw new IncompatibleKeyError(key); + } + }; +} + +const eslintrcKeys = [ + "env", + "extends", + "globals", + "ignorePatterns", + "noInlineConfig", + "overrides", + "parser", + "parserOptions", + "reportUnusedDisableDirectives", + "root" +]; + //----------------------------------------------------------------------------- // Full schema //----------------------------------------------------------------------------- exports.flatConfigSchema = { + + // eslintrc-style keys that should always error + ...Object.fromEntries(eslintrcKeys.map(key => [key, createEslintrcErrorSchema(key)])), + + // flat config keys settings: deepObjectAssignSchema, linterOptions: { schema: { diff --git a/messages/eslintrc-incompat.js b/messages/eslintrc-incompat.js new file mode 100644 index 000000000000..deffca57c595 --- /dev/null +++ b/messages/eslintrc-incompat.js @@ -0,0 +1,98 @@ +"use strict"; + +/* eslint consistent-return: 0 -- no default case */ + +const messages = { + + env: ` +A config object is using the "env" key, which is not supported in flat config system. + +Flat config uses "languageOptions.globals" to define global variables for your files. + +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 +`, + + extends: ` +A config object is using the "extends" key, which is not supported in flat config system. + +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: +https://eslint.org/docs/latest/use/configure/migration-guide#predefined-configs +`, + + globals: ` +A config object is using the "globals" key, which is not supported in flat config system. + +Flat config uses "languageOptions.globals" to define global variables for your files. + +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 +`, + + ignorePatterns: ` +A config object is using the "ignorePatterns" key, which is not supported in flat config system. + +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 +`, + + noInlineConfig: ` +A config object is using the "noInlineConfig" key, which is not supported in flat config system. + +Flat config uses "linterOptions.noInlineConfig" 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#linter-options +`, + + overrides: ` +A config object is using the "overrides" key, which is not supported in flat config system. + +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 +`, + + parser: ` +A config object is using the "parser" key, which is not supported in flat config system. + +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 +`, + + parserOptions: ` +A config object is using the "parserOptions" key, which is not supported in flat config system. + +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 +`, + + reportUnusedDisableDirectives: ` +A config object is using the "reportUnusedDisableDirectives" key, which is not supported in flat config system. + +Flat config uses "linterOptions.reportUnusedDisableDirectives" 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#linter-options +`, + + root: ` +A config object is using the "root" key, which is not supported in flat config system. + +Flat configs always act as if they are the root config file, so this key can be safely removed. +` +}; + +module.exports = function({ key }) { + + return messages[key].trim(); +}; diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index 65d3a729f130..10f9c31c3d24 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -1938,5 +1938,33 @@ describe("FlatConfigArray", () => { }); + describe("Invalid Keys", () => { + + [ + "env", + "extends", + "globals", + "ignorePatterns", + "noInlineConfig", + "overrides", + "parser", + "parserOptions", + "reportUnusedDisableDirectives", + "root" + ].forEach(key => { + + it(`should error when a ${key} key is found`, async () => { + await assertInvalidConfig([ + { + [key]: "foo" + } + ], `Key "${key}": This appears to be in eslintrc format rather than flat config format.`); + + }); + }); + + + }); + }); }); diff --git a/tests/lib/rule-tester/flat-rule-tester.js b/tests/lib/rule-tester/flat-rule-tester.js index 6c6188aee502..f5cdba967ecd 100644 --- a/tests/lib/rule-tester/flat-rule-tester.js +++ b/tests/lib/rule-tester/flat-rule-tester.js @@ -1336,7 +1336,7 @@ describe("FlatRuleTester", () => { ], invalid: [] }); - }, /Unexpected key "env" found./u); + }, /Key "env": This appears to be in eslintrc format rather than flat config format/u); }); it("should pass-through the tester config to the rule", () => { From 8a9abb7cf424bd49d45c09345dc45ae95f29cc9d Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 20 Jul 2023 08:06:21 +0000 Subject: [PATCH 096/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2917009e2c99..cf948825abfd 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord GitHub Transloadit Ignition Mercedes-Benz Group HeroCoders QuickBooks Tool hub

+

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord GitHub Transloadit Ignition HeroCoders QuickBooks Tool hub

## Technology Sponsors From 80dffed4c81dcc71fb72bc187aff2f87d141a6ed Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Thu, 20 Jul 2023 17:00:31 +0200 Subject: [PATCH 097/248] docs: fix Ignoring Files section in config migration guide (#17392) * docs: fix Ignoring Files section in config migration guide * remove extra blank line --- docs/src/use/configure/migration-guide.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/src/use/configure/migration-guide.md b/docs/src/use/configure/migration-guide.md index 4791dc7c5d0e..6a054246a9b3 100644 --- a/docs/src/use/configure/migration-guide.md +++ b/docs/src/use/configure/migration-guide.md @@ -345,14 +345,14 @@ export default [ With eslintrc, you can make ESLint ignore files by creating a separate `.eslintignore` file in the root of your project. The `.eslintignore` file uses the same glob pattern syntax as `.gitignore` files. Alternatively, you can use an `ignorePatterns` property in your eslintrc file. -To ignore files with flat config, you can use the `ignores` property in a config object. The `ignores` property accepts an array of glob patterns. Note that flat config glob patterns do _not_ match dot files (e.g. `*.js` won't match `.dotfile.js`). Flat config does not support loading ignore patterns from `.eslintignore` files, so you'll need to migrate those patterns directly into flat config. +To ignore files with flat config, you can use the `ignores` property in a config object. The `ignores` property accepts an array of glob patterns. Flat config does not support loading ignore patterns from `.eslintignore` files, so you'll need to migrate those patterns directly into flat config. For example, here's a `.eslintignore` example you can use with an eslintrc config: ```shell # .eslintignore temp.js -.config/* +config/* # ...other ignored files ``` @@ -362,7 +362,7 @@ temp.js // .eslintrc.js module.exports = { // ...other config - ignorePatterns: ["temp.js", ".config/*"], + ignorePatterns: ["temp.js", "config/*"], }; ``` @@ -370,13 +370,15 @@ Here are the same files ignore patterns in flat config: ```javascript export default [ + // ...other config { - // ...other config - ignores: ["temp.js", ".config/*"] + ignores: ["**/temp.js", "config/*"] } ]; ``` +Also, with flat config, dotfiles (e.g. `.dotfile.js`) are no longer ignored by default. If you want to ignore dotfiles, add files ignore pattern `"**/.*"`. + ### Linter Options ESlintrc files let you configure the linter itself with the `noInlineConfig` and `reportUnusedDisableDirectives` properties. From 9803c7c04078f0672d8a480fd39cf3bbef8017e6 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 21 Jul 2023 17:09:22 +0200 Subject: [PATCH 098/248] fix: FlatESLint#getRulesMetaForResults shouldn't throw on unknown rules (#17393) * fix: FlatESLint#getRulesMetaForResults shouldn't throw on unknown rules Fixes #17343 * refactor test check Co-authored-by: Nicholas C. Zakas --------- Co-authored-by: Nicholas C. Zakas --- lib/eslint/flat-eslint.js | 8 +++----- tests/lib/eslint/flat-eslint.js | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/lib/eslint/flat-eslint.js b/lib/eslint/flat-eslint.js index f2d6082eb3c6..4ef386113616 100644 --- a/lib/eslint/flat-eslint.js +++ b/lib/eslint/flat-eslint.js @@ -714,12 +714,10 @@ class FlatESLint { } const rule = getRuleFromConfig(ruleId, config); - // ensure the rule exists - if (!rule) { - throw new TypeError(`Could not find the rule "${ruleId}".`); + // ignore unknown rules + if (rule) { + resultRules.set(ruleId, rule); } - - resultRules.set(ruleId, rule); } } diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index c3a8240e7b24..9e0ca12458f5 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -4478,6 +4478,42 @@ describe("FlatESLint", () => { assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); }); + + it("should return empty object if all messages are related to unknown rules", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true + }); + + const results = await engine.lintText("// eslint-disable-line foo, bar/baz, bar/baz/qux"); + + assert.strictEqual(results[0].messages.length, 3); + assert.strictEqual(results[0].messages[0].ruleId, "foo"); + assert.strictEqual(results[0].messages[1].ruleId, "bar/baz"); + assert.strictEqual(results[0].messages[2].ruleId, "bar/baz/qux"); + + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(Object.keys(rulesMeta).length, 0); + }); + + it("should return object with meta of known rules if some messages are related to unknown rules", async () => { + const engine = new FlatESLint({ + overrideConfigFile: true, + overrideConfig: { rules: { "no-var": "warn" } } + }); + + const results = await engine.lintText("// eslint-disable-line foo, bar/baz, bar/baz/qux\nvar x;"); + + assert.strictEqual(results[0].messages.length, 4); + assert.strictEqual(results[0].messages[0].ruleId, "foo"); + assert.strictEqual(results[0].messages[1].ruleId, "bar/baz"); + assert.strictEqual(results[0].messages[2].ruleId, "bar/baz/qux"); + assert.strictEqual(results[0].messages[3].ruleId, "no-var"); + + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); + }); }); describe("outputFixes()", () => { From 1af6eac5727080c809e37c07dc729b44ef24483c Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Fri, 21 Jul 2023 21:00:32 +0530 Subject: [PATCH 099/248] feat: adds option for allowing empty object patterns as parameter (#17365) * feat: adds option for allowing empty object patterns as parameter * feat: refactor previous code * docs: adds option details for no-empty-pattern rule * feat: refactor previous code * feat: update no-empty-pattern rule file --- docs/src/rules/no-empty-pattern.md | 44 ++++++++++++ lib/rules/no-empty-pattern.js | 41 ++++++++++- tests/lib/rules/no-empty-pattern.js | 108 +++++++++++++++++++++++++++- 3 files changed, 189 insertions(+), 4 deletions(-) diff --git a/docs/src/rules/no-empty-pattern.md b/docs/src/rules/no-empty-pattern.md index d510f592d558..7edd933731a9 100644 --- a/docs/src/rules/no-empty-pattern.md +++ b/docs/src/rules/no-empty-pattern.md @@ -65,3 +65,47 @@ function foo({a = []}) {} ``` ::: + +## Options + +This rule has an object option for exceptions: + +### allowObjectPatternsAsParameters + +Set to `false` by default. Setting this option to `true` allows empty object patterns as function parameters. + +**Note:** This rule doesn't allow empty array patterns as function parameters. + +Examples of **incorrect** code for this rule with the `{"allowObjectPatternsAsParameters": true}` option: + +::: incorrect + +```js +/*eslint no-empty-pattern: ["error", { "allowObjectPatternsAsParameters": true }]*/ + +function foo({a: {}}) {} +var foo = function({a: {}}) {}; +var foo = ({a: {}}) => {}; +var foo = ({} = bar) => {}; +var foo = ({} = { bar: 1 }) => {}; + +function foo([]) {} +``` + +::: + +Examples of **correct** code for this rule with the `{"allowObjectPatternsAsParameters": true}` option: + +::: correct + +```js +/*eslint no-empty-pattern: ["error", { "allowObjectPatternsAsParameters": true }]*/ + +function foo({}) {} +var foo = function({}) {}; +var foo = ({}) => {}; + +function foo({} = {}) {} +``` + +::: diff --git a/lib/rules/no-empty-pattern.js b/lib/rules/no-empty-pattern.js index abb1a7c6ddbc..fb75f6d25b34 100644 --- a/lib/rules/no-empty-pattern.js +++ b/lib/rules/no-empty-pattern.js @@ -4,6 +4,8 @@ */ "use strict"; +const astUtils = require("./utils/ast-utils"); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -19,7 +21,18 @@ module.exports = { url: "https://eslint.org/docs/latest/rules/no-empty-pattern" }, - schema: [], + schema: [ + { + type: "object", + properties: { + allowObjectPatternsAsParameters: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ], messages: { unexpected: "Unexpected empty {{type}} pattern." @@ -27,11 +40,33 @@ module.exports = { }, create(context) { + const options = context.options[0] || {}, + allowObjectPatternsAsParameters = options.allowObjectPatternsAsParameters || false; + return { ObjectPattern(node) { - if (node.properties.length === 0) { - context.report({ node, messageId: "unexpected", data: { type: "object" } }); + + if (node.properties.length > 0) { + return; } + + // Allow {} and {} = {} empty object patterns as parameters when allowObjectPatternsAsParameters is true + if ( + allowObjectPatternsAsParameters && + ( + astUtils.isFunction(node.parent) || + ( + node.parent.type === "AssignmentPattern" && + astUtils.isFunction(node.parent.parent) && + node.parent.right.type === "ObjectExpression" && + node.parent.right.properties.length === 0 + ) + ) + ) { + return; + } + + context.report({ node, messageId: "unexpected", data: { type: "object" } }); }, ArrayPattern(node) { if (node.elements.length === 0) { diff --git a/tests/lib/rules/no-empty-pattern.js b/tests/lib/rules/no-empty-pattern.js index 0d2d7ea80aa1..2cd06c4be373 100644 --- a/tests/lib/rules/no-empty-pattern.js +++ b/tests/lib/rules/no-empty-pattern.js @@ -26,7 +26,13 @@ ruleTester.run("no-empty-pattern", rule, { { code: "var {a = []} = foo;", parserOptions: { ecmaVersion: 6 } }, { code: "function foo({a = {}}) {}", parserOptions: { ecmaVersion: 6 } }, { code: "function foo({a = []}) {}", parserOptions: { ecmaVersion: 6 } }, - { code: "var [a] = foo", parserOptions: { ecmaVersion: 6 } } + { code: "var [a] = foo", parserOptions: { ecmaVersion: 6 } }, + { code: "function foo({}) {}", options: [{ allowObjectPatternsAsParameters: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "var foo = function({}) {}", options: [{ allowObjectPatternsAsParameters: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "var foo = ({}) => {}", options: [{ allowObjectPatternsAsParameters: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "function foo({} = {}) {}", options: [{ allowObjectPatternsAsParameters: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "var foo = function({} = {}) {}", options: [{ allowObjectPatternsAsParameters: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "var foo = ({} = {}) => {}", options: [{ allowObjectPatternsAsParameters: true }], parserOptions: { ecmaVersion: 6 } } ], // Examples of code that should trigger the rule @@ -111,6 +117,106 @@ ruleTester.run("no-empty-pattern", rule, { data: { type: "array" }, type: "ArrayPattern" }] + }, + { + code: "function foo({}) {}", + options: [{}], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unexpected", + data: { type: "object" }, + type: "ObjectPattern" + }] + }, + { + code: "var foo = function({}) {}", + options: [{}], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unexpected", + data: { type: "object" }, + type: "ObjectPattern" + }] + }, + { + code: "var foo = ({}) => {}", + options: [{}], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unexpected", + data: { type: "object" }, + type: "ObjectPattern" + }] + }, + { + code: "function foo({} = {}) {}", + options: [{}], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unexpected", + data: { type: "object" }, + type: "ObjectPattern" + }] + }, + { + code: "var foo = function({} = {}) {}", + options: [{}], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unexpected", + data: { type: "object" }, + type: "ObjectPattern" + }] + }, + { + code: "var foo = ({} = {}) => {}", + options: [{}], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unexpected", + data: { type: "object" }, + type: "ObjectPattern" + }] + }, + { + code: "var foo = ({a: {}}) => {}", + options: [{ allowObjectPatternsAsParameters: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unexpected", + data: { type: "object" }, + type: "ObjectPattern" + }] + }, + { + code: "var foo = ({} = bar) => {}", + options: [{ allowObjectPatternsAsParameters: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unexpected", + data: { type: "object" }, + type: "ObjectPattern" + }] + }, + { + code: "var foo = ({} = { bar: 1 }) => {}", + options: [{ allowObjectPatternsAsParameters: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unexpected", + data: { type: "object" }, + type: "ObjectPattern" + }] + }, + { + code: "var foo = ([]) => {}", + options: [{ allowObjectPatternsAsParameters: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "unexpected", + data: { type: "array" }, + type: "ArrayPattern" + }] } ] }); From 10e9cfa01ac043961f2c476198848f0ca5e8bbb0 Mon Sep 17 00:00:00 2001 From: leo-centurion <89160080+leo-centurion@users.noreply.github.com> Date: Fri, 21 Jul 2023 12:58:16 -0300 Subject: [PATCH 100/248] Merge pull request from GHSA-qwh7-v8hg-w8rh --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 80545e2cdb23..d17f2b201694 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", From da73e583e1703a420551d8fa8f7c70b56dc88dd5 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Sat, 22 Jul 2023 12:01:48 +0200 Subject: [PATCH 101/248] docs: Migrating `eslint-env` configuration comments (#17390) * docs: Migrating `eslint-env` configuration comments * "must" -> "should" Co-authored-by: Nicholas C. Zakas * Add heading --------- Co-authored-by: Nicholas C. Zakas --- docs/src/use/configure/migration-guide.md | 59 +++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/docs/src/use/configure/migration-guide.md b/docs/src/use/configure/migration-guide.md index 6a054246a9b3..14680d365e52 100644 --- a/docs/src/use/configure/migration-guide.md +++ b/docs/src/use/configure/migration-guide.md @@ -255,6 +255,65 @@ export default [ ]; ``` +### `eslint-env` Configuration Comments + +In the eslintrc config system it was possible to use `eslint-env` configuration comments to define globals for a file. +These comments are no longer recognized when linting with flat config: in a future version of ESLint, `eslint-env` comments will be reported as errors. +For this reason, when migrating from eslintrc to flat config, `eslint-env` configuration comments should be removed from all files. +They can be either replaced with equivalent but more verbose `global` configuration comments, or dropped in favor of `globals` definitions in the config file. + +For example, when using eslintrc, a file to be linted could look like this: + +```javascript +// tests/my-file.js + +/* eslint-env mocha */ + +describe("unit tests", () => { + it("should pass", () => { + // ... + }); +}); +``` + +In the above example, `describe` and `it` would be recognized as global identifiers because of the `/* eslint-env mocha */` comment. + +The same effect can be achieved with flat config with a `global` configuration comment, e.g.: + +```javascript +// tests/my-file.js + +/* global describe, it -- Globals defined by Mocha */ + +describe("unit tests", () => { + it("should pass", () => { + // ... + }); +}); +``` + +Another option is to remove the comment from the file being linted and define the globals in the configuration, for example: + +```javascript +// eslint.config.js + +import globals from "globals"; + +export default [ + // ...other config + { + files: [ + "tests/**" + ], + languageOptions: { + globals: { + ...globals.mocha + } + } + } +]; +``` + ### Predefined Configs In eslintrc files, use the `extends` property to use predefined configs. ESLint comes with two predefined configs that you can access as strings: From 7fc3a2ce68979a2c2a6fc779e647b3004ab6f4ac Mon Sep 17 00:00:00 2001 From: Matt Wilkinson Date: Mon, 24 Jul 2023 16:28:38 +0100 Subject: [PATCH 102/248] docs: Add private class features info to no-underscore-dangle (#17386) --- docs/src/rules/no-underscore-dangle.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/src/rules/no-underscore-dangle.md b/docs/src/rules/no-underscore-dangle.md index d35aefeb72d1..c5e7923c2bfb 100644 --- a/docs/src/rules/no-underscore-dangle.md +++ b/docs/src/rules/no-underscore-dangle.md @@ -3,16 +3,17 @@ title: no-underscore-dangle rule_type: suggestion --- - As far as naming conventions for identifiers go, dangling underscores may be the most polarizing in JavaScript. Dangling underscores are underscores at either the beginning or end of an identifier, such as: ```js var _foo; ``` -There is actually a long history of using dangling underscores to indicate "private" members of objects in JavaScript (though JavaScript doesn't have truly private members, this convention served as a warning). This began with SpiderMonkey adding nonstandard methods such as `__defineGetter__()`. The intent with the underscores was to make it obvious that this method was special in some way. Since that time, using a single underscore prefix has become popular as a way to indicate "private" members of objects. +There is a long history of marking "private" members with dangling underscores in JavaScript, beginning with SpiderMonkey adding nonstandard methods such as `__defineGetter__()`. Since that time, using a single underscore prefix has become the most popular convention for indicating a member is not part of the public interface of an object. + +It is recommended to use the formal [private class features](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields) introduced in ECMAScript 2022 for encapsulating private data and methods rather than relying on naming conventions. -Whether or not you choose to allow dangling underscores in identifiers is purely a convention and has no effect on performance, readability, or complexity. It's purely a preference. +Allowing dangling underscores in identifiers is purely a convention and has no effect on performance, readability, or complexity. They do not have the same encapsulation benefits as private class features, even with this rule enabled. ## Rule Details From 94954a715448d5794f2892bf212fe986b43228ed Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Tue, 25 Jul 2023 17:11:47 +0900 Subject: [PATCH 103/248] feat: `no-invalid-regexp` support `v` flag (#17404) * feat: `no-invalid-regexp` support `v` flag * fix: uv flags * feat: update regexpp * test: add test cases * fix: error message and update regexpp to ^4.6.1 --- lib/rules/no-invalid-regexp.js | 29 ++++++++++++---- package.json | 2 +- tests/lib/rules/no-invalid-regexp.js | 50 ++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 8 deletions(-) diff --git a/lib/rules/no-invalid-regexp.js b/lib/rules/no-invalid-regexp.js index 9a35743d122f..3c42a68e8a3a 100644 --- a/lib/rules/no-invalid-regexp.js +++ b/lib/rules/no-invalid-regexp.js @@ -10,7 +10,7 @@ const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator; const validator = new RegExpValidator(); -const validFlags = /[dgimsuy]/gu; +const validFlags = /[dgimsuvy]/gu; const undefined1 = void 0; //------------------------------------------------------------------------------ @@ -108,12 +108,14 @@ module.exports = { /** * Check syntax error in a given pattern. * @param {string} pattern The RegExp pattern to validate. - * @param {boolean} uFlag The Unicode flag. + * @param {Object} flags The RegExp flags to validate. + * @param {boolean} [flags.unicode] The Unicode flag. + * @param {boolean} [flags.unicodeSets] The UnicodeSets flag. * @returns {string|null} The syntax error. */ - function validateRegExpPattern(pattern, uFlag) { + function validateRegExpPattern(pattern, flags) { try { - validator.validatePattern(pattern, undefined1, undefined1, uFlag); + validator.validatePattern(pattern, undefined1, undefined1, flags); return null; } catch (err) { return err.message; @@ -131,10 +133,19 @@ module.exports = { } try { validator.validateFlags(flags); - return null; } catch { return `Invalid flags supplied to RegExp constructor '${flags}'`; } + + /* + * `regexpp` checks the combination of `u` and `v` flags when parsing `Pattern` according to `ecma262`, + * but this rule may check only the flag when the pattern is unidentifiable, so check it here. + * https://tc39.es/ecma262/multipage/text-processing.html#sec-parsepattern + */ + if (flags.includes("u") && flags.includes("v")) { + return "Regex 'u' and 'v' flags cannot be used together"; + } + return null; } return { @@ -166,8 +177,12 @@ module.exports = { // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag flags === null - ? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false) - : validateRegExpPattern(pattern, flags.includes("u")) + ? ( + validateRegExpPattern(pattern, { unicode: true, unicodeSets: false }) && + validateRegExpPattern(pattern, { unicode: false, unicodeSets: true }) && + validateRegExpPattern(pattern, { unicode: false, unicodeSets: false }) + ) + : validateRegExpPattern(pattern, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") }) ); if (message) { diff --git a/package.json b/package.json index d17f2b201694..3833ed0a2b8b 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "bugs": "https://github.com/eslint/eslint/issues/", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", + "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.0", "@eslint/js": "8.44.0", "@humanwhocodes/config-array": "^0.11.10", diff --git a/tests/lib/rules/no-invalid-regexp.js b/tests/lib/rules/no-invalid-regexp.js index ceaa8f13a44d..dfd662bad273 100644 --- a/tests/lib/rules/no-invalid-regexp.js +++ b/tests/lib/rules/no-invalid-regexp.js @@ -81,6 +81,14 @@ ruleTester.run("no-invalid-regexp", rule, { "new RegExp('\\\\p{Script=Vith}', 'u')", "new RegExp('\\\\p{Script=Vithkuqi}', 'u')", + // ES2024 + "new RegExp('[A--B]', 'v')", + "new RegExp('[A&&B]', 'v')", + "new RegExp('[A--[0-9]]', 'v')", + "new RegExp('[\\\\p{Basic_Emoji}--\\\\q{a|bc|def}]', 'v')", + "new RegExp('[A--B]', flags)", // valid only with `v` flag + "new RegExp('[[]\\\\u{0}*', flags)", // valid only with `u` flag + // allowConstructorFlags { code: "new RegExp('.', 'g')", @@ -288,6 +296,48 @@ ruleTester.run("no-invalid-regexp", rule, { data: { message: "Invalid flags supplied to RegExp constructor 'z'" }, type: "NewExpression" }] + }, + + // ES2024 + { + code: "new RegExp('[[]', 'v');", + errors: [{ + messageId: "regexMessage", + data: { message: "Invalid regular expression: /[[]/v: Unterminated character class" }, + type: "NewExpression" + }] + }, + { + code: "new RegExp('.', 'uv');", + errors: [{ + messageId: "regexMessage", + data: { message: "Regex 'u' and 'v' flags cannot be used together" }, + type: "NewExpression" + }] + }, + { + code: "new RegExp(pattern, 'uv');", + errors: [{ + messageId: "regexMessage", + data: { message: "Regex 'u' and 'v' flags cannot be used together" }, + type: "NewExpression" + }] + }, + { + code: "new RegExp('[A--B]' /* valid only with `v` flag */, 'u')", + errors: [{ + messageId: "regexMessage", + data: { message: "Invalid regular expression: /[A--B]/u: Range out of order in character class" }, + type: "NewExpression" + }] + }, + { + code: "new RegExp('[[]\\\\u{0}*' /* valid only with `u` flag */, 'v')", + errors: [{ + messageId: "regexMessage", + data: { message: "Invalid regular expression: /[[]\\u{0}*/v: Unterminated character class" }, + type: "NewExpression" + }] } ] }); From a6a3ad4ae438ea7fc3a1d97cd2555f6534b565f1 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Tue, 25 Jul 2023 17:54:40 +0900 Subject: [PATCH 104/248] feat: `no-useless-backreference` support `v` flag (#17408) * feat: `no-useless-backreference` support `v` flag * test: change test case --- lib/rules/no-useless-backreference.js | 2 +- tests/lib/rules/no-useless-backreference.js | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/rules/no-useless-backreference.js b/lib/rules/no-useless-backreference.js index c99ac4114958..7ca43c8b2607 100644 --- a/lib/rules/no-useless-backreference.js +++ b/lib/rules/no-useless-backreference.js @@ -95,7 +95,7 @@ module.exports = { let regExpAST; try { - regExpAST = parser.parsePattern(pattern, 0, pattern.length, flags.includes("u")); + regExpAST = parser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") }); } catch { // Ignore regular expressions with syntax errors diff --git a/tests/lib/rules/no-useless-backreference.js b/tests/lib/rules/no-useless-backreference.js index d51d5bf76e75..3db83c65a4ef 100644 --- a/tests/lib/rules/no-useless-backreference.js +++ b/tests/lib/rules/no-useless-backreference.js @@ -142,7 +142,11 @@ ruleTester.run("no-useless-backreference", rule, { String.raw`new RegExp('\\1(a)\\2', 'ug')`, // \1 would be an error, but \2 is syntax error because of the 'u' flag String.raw`const flags = 'gus'; RegExp('\\1(a){', flags);`, // \1 would be an error, but the rule is aware of the 'u' flag so this is a syntax error String.raw`RegExp('\\1(a)\\k', 'u')`, // \1 would be an error, but \k produces syntax error because of the u flag - String.raw`new RegExp('\\k(?a)\\k')` // \k would be an error, but \k produces syntax error because group doesn't exist + String.raw`new RegExp('\\k(?a)\\k')`, // \k would be an error, but \k produces syntax error because group doesn't exist + + // ES2024 + String.raw`new RegExp('([[A--B]])\\1', 'v')`, + String.raw`new RegExp('[[]\\1](a)', 'v')` // SyntaxError ], invalid: [ @@ -508,6 +512,13 @@ ruleTester.run("no-useless-backreference", rule, { { code: String.raw`const r = RegExp, p = '\\1', s = '(a)'; new r(p + s);`, errors: [{ messageId: "forward", data: { bref: String.raw`\1`, group: String.raw`(a)` }, type: "NewExpression" }] + }, + + + // ES2024 + { + code: String.raw`new RegExp('\\1([[A--B]])', 'v')`, + errors: [{ messageId: "forward", data: { bref: String.raw`\1`, group: String.raw`([[A--B]])` }, type: "NewExpression" }] } ] }); From b7fad2b52f23667628cf209663795a721c88d0ba Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Tue, 25 Jul 2023 18:44:36 +0900 Subject: [PATCH 105/248] feat: `prefer-regex-literals` support `v` flag (#17410) * feat: `prefer-regex-literals` support `v` flag * Update tests/lib/rules/prefer-regex-literals.js Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- lib/rules/prefer-regex-literals.js | 12 +- lib/rules/utils/regular-expressions.js | 2 +- tests/lib/rules/prefer-regex-literals.js | 182 ++++++++++++++++++++++- 3 files changed, 191 insertions(+), 5 deletions(-) diff --git a/lib/rules/prefer-regex-literals.js b/lib/rules/prefer-regex-literals.js index eca805483f4a..ffaaeac3f279 100644 --- a/lib/rules/prefer-regex-literals.js +++ b/lib/rules/prefer-regex-literals.js @@ -241,7 +241,7 @@ module.exports = { /** * Returns a ecmaVersion compatible for regexpp. * @param {number} ecmaVersion The ecmaVersion to convert. - * @returns {import("regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp. + * @returns {import("@eslint-community/regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp. */ function getRegexppEcmaVersion(ecmaVersion) { if (ecmaVersion <= 5) { @@ -297,7 +297,10 @@ module.exports = { const validator = new RegExpValidator({ ecmaVersion: regexppEcmaVersion }); try { - validator.validatePattern(pattern, 0, pattern.length, flags ? flags.includes("u") : false); + validator.validatePattern(pattern, 0, pattern.length, { + unicode: flags ? flags.includes("u") : false, + unicodeSets: flags ? flags.includes("v") : false + }); if (flags) { validator.validateFlags(flags); } @@ -461,7 +464,10 @@ module.exports = { if (regexContent && !noFix) { let charIncrease = 0; - const ast = new RegExpParser({ ecmaVersion: regexppEcmaVersion }).parsePattern(regexContent, 0, regexContent.length, flags ? flags.includes("u") : false); + const ast = new RegExpParser({ ecmaVersion: regexppEcmaVersion }).parsePattern(regexContent, 0, regexContent.length, { + unicode: flags ? flags.includes("u") : false, + unicodeSets: flags ? flags.includes("v") : false + }); visitRegExpAST(ast, { onCharacterEnter(characterNode) { diff --git a/lib/rules/utils/regular-expressions.js b/lib/rules/utils/regular-expressions.js index 234a1cb8b114..92da774c96c7 100644 --- a/lib/rules/utils/regular-expressions.js +++ b/lib/rules/utils/regular-expressions.js @@ -8,7 +8,7 @@ const { RegExpValidator } = require("@eslint-community/regexpp"); -const REGEXPP_LATEST_ECMA_VERSION = 2022; +const REGEXPP_LATEST_ECMA_VERSION = 2024; /** * Checks if the given regular expression pattern would be valid with the `u` flag. diff --git a/tests/lib/rules/prefer-regex-literals.js b/tests/lib/rules/prefer-regex-literals.js index 054d89be1d75..11f23cac3d5a 100644 --- a/tests/lib/rules/prefer-regex-literals.js +++ b/tests/lib/rules/prefer-regex-literals.js @@ -134,7 +134,10 @@ ruleTester.run("prefer-regex-literals", rule, { { code: "class C { #RegExp; foo() { globalThis.#RegExp('a'); } }", env: { es2020: true } - } + }, + + // ES2024 + "new RegExp('[[A--B]]' + a, 'v')" ], invalid: [ @@ -2808,6 +2811,183 @@ ruleTester.run("prefer-regex-literals", rule, { suggestions: null } ] + }, + + // ES2024 + { + code: "new RegExp('[[A--B]]', 'v')", + parserOptions: { ecmaVersion: 2024 }, + errors: [ + { + messageId: "unexpectedRegExp", + suggestions: [ + { + messageId: "replaceWithLiteral", + output: "/[[A--B]]/v" + } + ] + } + ] + }, + { + code: "new RegExp('[[A--B]]', 'v')", + parserOptions: { ecmaVersion: 2023 }, + errors: [ + { + messageId: "unexpectedRegExp", + suggestions: null + } + ] + }, + { + code: "new RegExp('[[A&&&]]', 'v')", + parserOptions: { ecmaVersion: 2024 }, + errors: [ + { + messageId: "unexpectedRegExp", + suggestions: null + } + ] + }, + { + code: "new RegExp('a', 'uv')", + parserOptions: { ecmaVersion: 2024 }, + errors: [ + { + messageId: "unexpectedRegExp", + suggestions: null + } + ] + }, + { + code: "new RegExp(/a/, 'v')", + options: [{ disallowRedundantWrapping: true }], + parserOptions: { ecmaVersion: 2024 }, + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/v", + data: { + flags: "v" + } + } + ] + } + ] + }, + { + code: "new RegExp(/a/, 'v')", + options: [{ disallowRedundantWrapping: true }], + parserOptions: { ecmaVersion: 2023 }, + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + suggestions: null + } + ] + }, + { + code: "new RegExp(/a/g, 'v')", + options: [{ disallowRedundantWrapping: true }], + parserOptions: { ecmaVersion: 2024 }, + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/v", + data: { + flags: "v" + } + }, + { + messageId: "replaceWithIntendedLiteralAndFlags", + output: "/a/gv", + data: { + flags: "gv" + } + } + ] + } + ] + }, + { + code: "new RegExp(/[[A--B]]/v, 'g')", + options: [{ disallowRedundantWrapping: true }], + parserOptions: { ecmaVersion: 2024 }, + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + suggestions: [ + { + messageId: "replaceWithIntendedLiteralAndFlags", + output: "/[[A--B]]/vg", + data: { + flags: "vg" + } + } + + // suggestion with flags `g` would be invalid + ] + } + ] + }, + { + code: "new RegExp(/a/u, 'v')", + options: [{ disallowRedundantWrapping: true }], + parserOptions: { ecmaVersion: 2024 }, + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/v", + data: { + flags: "v" + } + } + + // suggestion with merged flags `uv` would be invalid + ] + } + ] + }, + { + code: "new RegExp(/a/v, 'u')", + options: [{ disallowRedundantWrapping: true }], + parserOptions: { ecmaVersion: 2024 }, + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + suggestions: [ + { + messageId: "replaceWithLiteralAndFlags", + output: "/a/u", + data: { + flags: "u" + } + } + + // suggestion with merged flags `vu` would be invalid + ] + } + ] + }, + { + code: "new RegExp(/[[A--B]]/v, 'u')", + options: [{ disallowRedundantWrapping: true }], + parserOptions: { ecmaVersion: 2024 }, + errors: [ + { + messageId: "unexpectedRedundantRegExpWithFlags", + suggestions: null + } + ] } ] }); From 3caf51487decdf93a4b17765a2af2a51c337e974 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Tue, 25 Jul 2023 20:48:58 +0900 Subject: [PATCH 106/248] feat: `no-regex-spaces` support `v` flag (#17407) * feat: `no-regex-spaces` support `v` flag * fix: to ignore from check if flag cannot be determined * Apply suggestions from code review Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- lib/rules/no-regex-spaces.js | 21 ++++++++++++++--- tests/lib/rules/no-regex-spaces.js | 38 +++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/lib/rules/no-regex-spaces.js b/lib/rules/no-regex-spaces.js index e7fae6d40551..cb250107289a 100644 --- a/lib/rules/no-regex-spaces.js +++ b/lib/rules/no-regex-spaces.js @@ -77,7 +77,7 @@ module.exports = { let regExpAST; try { - regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, flags.includes("u")); + regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") }); } catch { // Ignore regular expressions with syntax errors @@ -155,13 +155,28 @@ module.exports = { const regExpVar = astUtils.getVariableByName(scope, "RegExp"); const shadowed = regExpVar && regExpVar.defs.length > 0; const patternNode = node.arguments[0]; - const flagsNode = node.arguments[1]; if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(patternNode) && !shadowed) { const pattern = patternNode.value; const rawPattern = patternNode.raw.slice(1, -1); const rawPatternStartRange = patternNode.range[0] + 1; - const flags = isString(flagsNode) ? flagsNode.value : ""; + let flags; + + if (node.arguments.length < 2) { + + // It has no flags. + flags = ""; + } else { + const flagsNode = node.arguments[1]; + + if (isString(flagsNode)) { + flags = flagsNode.value; + } else { + + // The flags cannot be determined. + return; + } + } checkRegex( node, diff --git a/tests/lib/rules/no-regex-spaces.js b/tests/lib/rules/no-regex-spaces.js index 899522975496..fdc3eaa1817e 100644 --- a/tests/lib/rules/no-regex-spaces.js +++ b/tests/lib/rules/no-regex-spaces.js @@ -62,9 +62,18 @@ ruleTester.run("no-regex-spaces", rule, { "var foo = new RegExp(' \\[ ');", "var foo = new RegExp(' \\[ \\] ');", + // ES2024 + { code: "var foo = / {2}/v;", parserOptions: { ecmaVersion: 2024 } }, + { code: "var foo = /[\\q{ }]/v;", parserOptions: { ecmaVersion: 2024 } }, + // don't report invalid regex "var foo = new RegExp('[ ');", - "var foo = new RegExp('{ ', 'u');" + "var foo = new RegExp('{ ', 'u');", + + // don't report if flags cannot be determined + "new RegExp(' ', flags)", + "new RegExp('[[abc] ]', flags + 'v')", + "new RegExp('[[abc]\\\\q{ }]', flags + 'v')" ], invalid: [ @@ -371,6 +380,33 @@ ruleTester.run("no-regex-spaces", rule, { type: "NewExpression" } ] + }, + + // ES2024 + { + code: "var foo = /[[ ] ] /v;", + output: "var foo = /[[ ] ] {4}/v;", + parserOptions: { + ecmaVersion: 2024 + }, + errors: [ + { + messageId: "multipleSpaces", + data: { length: "4" }, + type: "Literal" + } + ] + }, + { + code: "var foo = new RegExp('[[ ] ] ', 'v');", + output: "var foo = new RegExp('[[ ] ] {4}', 'v');", + errors: [ + { + messageId: "multipleSpaces", + data: { length: "4" }, + type: "NewExpression" + } + ] } ] }); From 509f75395035822280245772e2a95732a0dde0e1 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Tue, 25 Jul 2023 21:08:50 +0900 Subject: [PATCH 107/248] feat: `no-misleading-character-class` support `v` flag (#17406) * feat: `no-misleading-character-class` support `v` flag * fix: lib/rules/utils/regular-expressions.js * fix: iterateCharacterSequence --- .../rules/no-misleading-character-class.md | 1 + lib/rules/no-misleading-character-class.js | 10 ++++++++-- lib/rules/utils/regular-expressions.js | 2 +- .../rules/no-misleading-character-class.js | 20 ++++++++++++++++++- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/docs/src/rules/no-misleading-character-class.md b/docs/src/rules/no-misleading-character-class.md index a0d9e6d06931..e1b182c2ef50 100644 --- a/docs/src/rules/no-misleading-character-class.md +++ b/docs/src/rules/no-misleading-character-class.md @@ -79,6 +79,7 @@ Examples of **correct** code for this rule: /^[abc]$/ /^[๐Ÿ‘]$/u +/^[\q{๐Ÿ‘ถ๐Ÿป}]$/v ``` ::: diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index 47ee84ec857d..99aa9404868f 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -18,7 +18,7 @@ const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); * * CharacterClassRange syntax can steal a part of character sequence, * so this function reverts CharacterClassRange syntax and restore the sequence. - * @param {regexpp.AST.CharacterClassElement[]} nodes The node list to iterate character sequences. + * @param {import('@eslint-community/regexpp').AST.CharacterClassElement[]} nodes The node list to iterate character sequences. * @returns {IterableIterator} The list of character sequences. */ function *iterateCharacterSequence(nodes) { @@ -37,6 +37,9 @@ function *iterateCharacterSequence(nodes) { break; case "CharacterSet": + case "CharacterClass": // [[]] nesting character class + case "ClassStringDisjunction": // \q{...} + case "ExpressionCharacterClass": // [A--B] if (seq.length > 0) { yield seq; seq = []; @@ -144,7 +147,10 @@ module.exports = { pattern, 0, pattern.length, - flags.includes("u") + { + unicode: flags.includes("u"), + unicodeSets: flags.includes("v") + } ); } catch { diff --git a/lib/rules/utils/regular-expressions.js b/lib/rules/utils/regular-expressions.js index 92da774c96c7..12e544e379de 100644 --- a/lib/rules/utils/regular-expressions.js +++ b/lib/rules/utils/regular-expressions.js @@ -28,7 +28,7 @@ function isValidWithUnicodeFlag(ecmaVersion, pattern) { }); try { - validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true); + validator.validatePattern(pattern, void 0, void 0, { unicode: /* uFlag = */ true }); } catch { return false; } diff --git a/tests/lib/rules/no-misleading-character-class.js b/tests/lib/rules/no-misleading-character-class.js index 5cc5cba5261b..54c971493131 100644 --- a/tests/lib/rules/no-misleading-character-class.js +++ b/tests/lib/rules/no-misleading-character-class.js @@ -70,7 +70,14 @@ ruleTester.run("no-misleading-character-class", rule, { "var r = new RegExp('[Aฬ] [ ');", "var r = RegExp('{ [Aฬ]', 'u');", { code: "var r = new globalThis.RegExp('[Aฬ] [ ');", env: { es2020: true } }, - { code: "var r = globalThis.RegExp('{ [Aฬ]', 'u');", env: { es2020: true } } + { code: "var r = globalThis.RegExp('{ [Aฬ]', 'u');", env: { es2020: true } }, + + // ES2024 + { code: "var r = /[๐Ÿ‘]/v", parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`var r = /^[\q{๐Ÿ‘ถ๐Ÿป}]$/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`var r = /[๐Ÿ‡ฏ\q{abc}๐Ÿ‡ต]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: "var r = /[๐Ÿ‡ฏ[A]๐Ÿ‡ต]/v", parserOptions: { ecmaVersion: 2024 } }, + { code: "var r = /[๐Ÿ‡ฏ[A--B]๐Ÿ‡ต]/v", parserOptions: { ecmaVersion: 2024 } } ], invalid: [ @@ -620,6 +627,17 @@ ruleTester.run("no-misleading-character-class", rule, { messageId: "zwj", suggestions: null }] + }, + + + // ES2024 + { + code: "var r = /[[๐Ÿ‘ถ๐Ÿป]]/v", + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + messageId: "emojiModifier", + suggestions: null + }] } ] }); From 6d6dc5141f535728029eef8735854a421bc08eba Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Wed, 26 Jul 2023 00:30:07 +0530 Subject: [PATCH 108/248] docs: fix overlapping of `open in playground` button (#17403) * docs: fix overlapping of open play button * docs: update px to rem * docs: open play btn style change for mobile size --- docs/src/assets/scss/docs.scss | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/src/assets/scss/docs.scss b/docs/src/assets/scss/docs.scss index 98d6de83d45c..3709a9cebf13 100644 --- a/docs/src/assets/scss/docs.scss +++ b/docs/src/assets/scss/docs.scss @@ -112,6 +112,10 @@ div.incorrect { offset-inline-end: -22px; offset-block-start: -22px; } + + pre.line-numbers-mode { + padding-bottom: 4.5rem; + } } div.correct { @@ -142,10 +146,8 @@ pre[class*="language-"] { .c-btn.c-btn--playground { position: absolute; font-size: var(--step--1); - bottom: -1rem; + bottom: 1rem; right: 1rem; - offset-block-end: 0.5rem; - offset-inline-end: 0.5rem; z-index: 1; @media all and (min-width: 768px) { @@ -166,6 +168,7 @@ pre[class*="language-"] { position: fixed; right: 50px; bottom: 35px; + z-index: 1; font-size: 1.5rem; border-radius: 50%; color: var(--body-background-color); From 8ca8b50b0425b3bad34a9505bc3095168e2f59d8 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 25 Jul 2023 17:25:36 -0400 Subject: [PATCH 109/248] feat: Better error message for flat config plugins (#17399) * feat: Better error message for flat config plugins Refs #17370 * Update docs/src/use/configure/migration-guide.md Co-authored-by: Milos Djermanovic * Update extended error message * Update docs/src/use/configure/migration-guide.md Co-authored-by: Francesco Trotta --------- Co-authored-by: Milos Djermanovic Co-authored-by: Francesco Trotta --- docs/src/use/configure/migration-guide.md | 44 ++++++++++++++++++++--- lib/config/flat-config-schema.js | 21 +++++++++++ messages/eslintrc-plugins.js | 24 +++++++++++++ tests/lib/config/flat-config-array.js | 9 +++++ 4 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 messages/eslintrc-plugins.js diff --git a/docs/src/use/configure/migration-guide.md b/docs/src/use/configure/migration-guide.md index 14680d365e52..b931244e144b 100644 --- a/docs/src/use/configure/migration-guide.md +++ b/docs/src/use/configure/migration-guide.md @@ -211,7 +211,7 @@ In eslintrc files, you configure various language options across the `env`, `glo In flat config files, the `globals`, and `parserOptions` are consolidated under the `languageOptions` key; the `env` property doesn't exist. Groups of global variables for specific runtimes are imported from the [globals](https://www.npmjs.com/package/globals) npm package and included in the `globals` property. You can use the spread operator (`...`) to import multiple globals at once. -For example, here's a eslintrc file with language options: +For example, here's an eslintrc file with language options: ```javascript // .eslintrc.js @@ -314,14 +314,14 @@ export default [ ]; ``` -### Predefined Configs +### Predefined and Shareable Configs -In eslintrc files, use the `extends` property to use predefined configs. ESLint comes with two predefined configs that you can access as strings: +In eslintrc files, use the `extends` property to use predefined and shareable configs. ESLint comes with two predefined configs that you can access as strings: * `"eslint:recommended"`: the rules recommended by ESLint * `"eslint:all"`: all rules shipped with ESLint -You can also use the `extends` property to extend a custom config. Custom configs can either be paths to local config files or npm package names. +You can also use the `extends` property to extend a shareable config. Shareable configs can either be paths to local config files or npm package names. In flat config files, predefined configs are imported from separate modules into flat config files. The `recommended` and `all` rules configs are located in the [`@eslint/js`](https://www.npmjs.com/package/@eslint/js) package. You must import this package to use these configs: @@ -346,7 +346,7 @@ module.exports = { } ``` -This eslintrc file uses built-in config, local custom config, and custom config from an npm package: +This eslintrc file uses built-in config, local custom config, and shareable config from an npm package: ```javascript // .eslintrc.js @@ -400,6 +400,40 @@ export default [ ]; ``` +#### Using eslintrc Configs in Flat Config + +You may find that there's a shareable config you rely on that hasn't yet been updated to flat config format. In that case, you can use the `FlatCompat` utility to translate the eslintrc format into flat config format. First, install the `@eslint/eslintrc` package: + +```shell +npm install @eslint/eslintrc --save-dev +``` + +Then, import `FlatCompat` and create a new instance to convert an existing eslintrc config. For example, if the npm package `eslint-config-my-config` is in eslintrc format, you can write this: + +```js +import { FlatCompat } from "@eslint/eslintrc"; +import path from "path"; +import { fileURLToPath } from "url"; + +// mimic CommonJS variables -- not needed if using CommonJS +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname +}); + +export default [ + + // mimic ESLintRC-style extends + ...compat.extends("eslint-config-my-config"), +]; +``` + +This example uses the `FlatCompat#extends()` method to insert the `eslint-config-my-config` into the flat config array. + +For more information about the `FlatCompat` class, please see the [package README](https://github.com/eslint/eslintrc#usage). + ### Ignoring Files With eslintrc, you can make ESLint ignore files by creating a separate `.eslintignore` file in the root of your project. The `.eslintignore` file uses the same glob pattern syntax as `.gitignore` files. Alternatively, you can use an `ignorePatterns` property in your eslintrc file. diff --git a/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js index 7325742adfcd..3922e8a94fe7 100644 --- a/lib/config/flat-config-schema.js +++ b/lib/config/flat-config-schema.js @@ -227,6 +227,22 @@ class IncompatibleKeyError extends Error { } } +/** + * The error type when there's an eslintrc-style plugins array found. + */ +class IncompatiblePluginsError extends Error { + + /** + * Creates a new instance. + * @param {Array} plugins The plugins array. + */ + constructor(plugins) { + super("This appears to be in eslintrc format (array of strings) rather than flat config format (object)."); + this.messageTemplate = "eslintrc-plugins"; + this.messageData = { plugins }; + } +} + //----------------------------------------------------------------------------- // Low-Level Schemas @@ -319,6 +335,11 @@ const pluginsSchema = { throw new TypeError("Expected an object."); } + // make sure it's not an array, which would mean eslintrc-style is used + if (Array.isArray(value)) { + throw new IncompatiblePluginsError(value); + } + // second check the keys to make sure they are objects for (const key of Object.keys(value)) { diff --git a/messages/eslintrc-plugins.js b/messages/eslintrc-plugins.js new file mode 100644 index 000000000000..bb708c95b053 --- /dev/null +++ b/messages/eslintrc-plugins.js @@ -0,0 +1,24 @@ +"use strict"; + +module.exports = function({ plugins }) { + + const isArrayOfStrings = typeof plugins[0] === "string"; + + return ` +A config object has a "plugins" key defined as an array${isArrayOfStrings ? " of strings" : ""}. + +Flat config requires "plugins" to be an object in this form: + + { + plugins: { + ${isArrayOfStrings && plugins[0] ? plugins[0] : "namespace"}: pluginObject + } + } + +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#importing-plugins-and-custom-parsers + +If you're using a shareable config that you cannot rewrite in flat config format, then use the compatibility utility: +https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config +`; +}; diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index 10f9c31c3d24..358882374831 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -1963,6 +1963,15 @@ describe("FlatConfigArray", () => { }); }); + it("should error when plugins is an array", async () => { + await assertInvalidConfig([ + { + plugins: ["foo"] + } + ], "Key \"plugins\": This appears to be in eslintrc format (array of strings) rather than flat config format (object)."); + + }); + }); From 2a35f3e6ed27deafbebba48b6aec570d3abf9974 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Wed, 26 Jul 2023 08:45:44 +0900 Subject: [PATCH 110/248] feat: `prefer-named-capture-group` support `v` flag (#17409) * feat: `prefer-named-capture-group` support `v` flag * chore: add comment * Update lib/rules/prefer-named-capture-group.js Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- lib/rules/prefer-named-capture-group.js | 13 +++++--- tests/lib/rules/prefer-named-capture-group.js | 33 ++++++++++++++++++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/lib/rules/prefer-named-capture-group.js b/lib/rules/prefer-named-capture-group.js index 8fb68de1794e..a82ee1f78356 100644 --- a/lib/rules/prefer-named-capture-group.js +++ b/lib/rules/prefer-named-capture-group.js @@ -112,14 +112,17 @@ module.exports = { * @param {string} pattern The regular expression pattern to be checked. * @param {ASTNode} node AST node which contains the regular expression or a call/new expression. * @param {ASTNode} regexNode AST node which contains the regular expression. - * @param {boolean} uFlag Flag indicates whether unicode mode is enabled or not. + * @param {string|null} flags The regular expression flags to be checked. * @returns {void} */ - function checkRegex(pattern, node, regexNode, uFlag) { + function checkRegex(pattern, node, regexNode, flags) { let ast; try { - ast = parser.parsePattern(pattern, 0, pattern.length, uFlag); + ast = parser.parsePattern(pattern, 0, pattern.length, { + unicode: Boolean(flags && flags.includes("u")), + unicodeSets: Boolean(flags && flags.includes("v")) + }); } catch { // ignore regex syntax errors @@ -148,7 +151,7 @@ module.exports = { return { Literal(node) { if (node.regex) { - checkRegex(node.regex.pattern, node, node, node.regex.flags.includes("u")); + checkRegex(node.regex.pattern, node, node, node.regex.flags); } }, Program(node) { @@ -166,7 +169,7 @@ module.exports = { const flags = getStringIfConstant(refNode.arguments[1]); if (regex) { - checkRegex(regex, refNode, refNode.arguments[0], flags && flags.includes("u")); + checkRegex(regex, refNode, refNode.arguments[0], flags); } } } diff --git a/tests/lib/rules/prefer-named-capture-group.js b/tests/lib/rules/prefer-named-capture-group.js index dad3d7c02901..31dcc70f3324 100644 --- a/tests/lib/rules/prefer-named-capture-group.js +++ b/tests/lib/rules/prefer-named-capture-group.js @@ -70,7 +70,17 @@ ruleTester.run("prefer-named-capture-group", rule, { } `, env: { es2020: true } - } + }, + + // ES2024 + "new RegExp('(?[[A--B]])', 'v')", + + /* + * This testcase checks if the rule understands the v flag correctly. + * Without the v flag, `([\q])` is considered a valid regex and the rule reports, + * but if the v flag is understood correctly the rule does not because of a syntax error. + */ + String.raw`new RegExp('([\\q])', 'v')` // SyntaxError ], invalid: [ @@ -591,6 +601,27 @@ ruleTester.run("prefer-named-capture-group", rule, { } ] }] + }, + + // ES2024 + { + code: "new RegExp('([[A--B]])', 'v')", + errors: [{ + messageId: "required", + type: "NewExpression", + data: { group: "([[A--B]])" }, + line: 1, + column: 1, + suggestions: [ + { + messageId: "addGroupName", + output: "new RegExp('(?[[A--B]])', 'v')" + }, + { + messageId: "addNonCapture", + output: "new RegExp('(?:[[A--B]])', 'v')" + }] + }] } ] }); From 9254a6cea845dfaf2f3f52f718cb9b071853aa09 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Wed, 26 Jul 2023 08:06:42 +0000 Subject: [PATCH 111/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf948825abfd..e16c09bb9867 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord GitHub Transloadit Ignition HeroCoders QuickBooks Tool hub

+

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord iBoysoft BairesDev GitHub Transloadit Ignition HeroCoders QuickBooks Tool hub

## Technology Sponsors From d4f02e4bf1b9ae4e1fc8f2bc4e4851ae3c36a127 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Wed, 26 Jul 2023 20:52:17 +0900 Subject: [PATCH 112/248] feat: `no-control-regex` support `v` flag (#17405) * feat: `no-control-regex` support v flag * test: add testcases * fix: false positives for `uv` flags --- lib/rules/no-control-regex.js | 17 +++++++++++++++-- tests/lib/rules/no-control-regex.js | 19 ++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/lib/rules/no-control-regex.js b/lib/rules/no-control-regex.js index 086747f37460..dc412fcabd52 100644 --- a/lib/rules/no-control-regex.js +++ b/lib/rules/no-control-regex.js @@ -14,6 +14,16 @@ const collector = new (class { } onPatternEnter() { + + /* + * `RegExpValidator` may parse the pattern twice in one `validatePattern`. + * So `this._controlChars` should be cleared here as well. + * + * For example, the `/(?\x1f)/` regex will parse the pattern twice. + * This is based on the content described in Annex B. + * If the regex contains a `GroupName` and the `u` flag is not used, `ParseText` will be called twice. + * See https://tc39.es/ecma262/2023/multipage/additional-ecmascript-features-for-web-browsers.html#sec-parsepattern-annexb + */ this._controlChars = []; } @@ -32,10 +42,13 @@ const collector = new (class { collectControlChars(regexpStr, flags) { const uFlag = typeof flags === "string" && flags.includes("u"); + const vFlag = typeof flags === "string" && flags.includes("v"); + + this._controlChars = []; + this._source = regexpStr; try { - this._source = regexpStr; - this._validator.validatePattern(regexpStr, void 0, void 0, uFlag); // Call onCharacter hook + this._validator.validatePattern(regexpStr, void 0, void 0, { unicode: uFlag, unicodeSets: vFlag }); // Call onCharacter hook } catch { // Ignore syntax errors in RegExp. diff --git a/tests/lib/rules/no-control-regex.js b/tests/lib/rules/no-control-regex.js index 14abfbce4505..3bfc87bace1f 100644 --- a/tests/lib/rules/no-control-regex.js +++ b/tests/lib/rules/no-control-regex.js @@ -33,7 +33,10 @@ ruleTester.run("no-control-regex", rule, { String.raw`new RegExp("\\u{20}", "u")`, String.raw`new RegExp("\\u{1F}")`, String.raw`new RegExp("\\u{1F}", "g")`, - String.raw`new RegExp("\\u{1F}", flags)` // when flags are unknown, this rule assumes there's no `u` flag + String.raw`new RegExp("\\u{1F}", flags)`, // when flags are unknown, this rule assumes there's no `u` flag + String.raw`new RegExp("[\\q{\\u{20}}]", "v")`, + { code: String.raw`/[\u{20}--B]/v`, parserOptions: { ecmaVersion: 2024 } } + ], invalid: [ { code: String.raw`var regex = /\x1f/`, errors: [{ messageId: "unexpected", data: { controlChars: "\\x1f" }, type: "Literal" }] }, @@ -85,6 +88,20 @@ ruleTester.run("no-control-regex", rule, { { code: String.raw`new RegExp("\\u{1F}", "gui")`, errors: [{ messageId: "unexpected", data: { controlChars: "\\x1f" }, type: "Literal" }] + }, + { + code: String.raw`new RegExp("[\\q{\\u{1F}}]", "v")`, + errors: [{ messageId: "unexpected", data: { controlChars: "\\x1f" }, type: "Literal" }] + }, + { + code: String.raw`/[\u{1F}--B]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ messageId: "unexpected", data: { controlChars: "\\x1f" }, type: "Literal" }] + }, + { + code: String.raw`/\x11/; RegExp("foo", "uv");`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ messageId: "unexpected", data: { controlChars: "\\x11" }, type: "Literal", column: 1 }] } ] }); From 853d32baa8934c08b59a738470b72522e1505f6f Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Wed, 26 Jul 2023 11:48:22 -0400 Subject: [PATCH 113/248] feat: deprecate no-return-await (#17417) * fix: deprecate no-return-await The original intent of this rule no longer applies due to the fact Javascript now handles native `Promises` differently. It can no be slower to remove `await` rather than keeping it. Fixes #17345 * Update docs/src/rules/no-return-await.md Co-authored-by: Milos Djermanovic * Update lib/rules/no-return-await.js Co-authored-by: Milos Djermanovic * Update docs/src/rules/no-return-await.md --------- Co-authored-by: Milos Djermanovic Co-authored-by: Nicholas C. Zakas --- docs/src/_data/further_reading_links.json | 7 +++++++ docs/src/_data/rules.json | 11 ++++------- docs/src/_data/rules_meta.json | 2 ++ docs/src/rules/no-return-await.md | 2 ++ lib/rules/no-return-await.js | 5 +++++ packages/js/src/configs/eslint-all.js | 1 - 6 files changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/src/_data/further_reading_links.json b/docs/src/_data/further_reading_links.json index 120dd37032fd..e66ef76ce696 100644 --- a/docs/src/_data/further_reading_links.json +++ b/docs/src/_data/further_reading_links.json @@ -719,5 +719,12 @@ "logo": "https://tc39.es/ecma262/img/favicon.ico", "title": "ECMAScriptยฎ 2023 Languageย Specification", "description": null + }, + "https://v8.dev/blog/fast-async": { + "domain": "v8.dev", + "url": "https://v8.dev/blog/fast-async", + "logo": "https://v8.dev/favicon.ico", + "title": "Faster async functions and promises ยท V8", + "description": "Faster and easier-to-debug async functions and promises are coming to V8 v7.2 / Chrome 72." } } \ No newline at end of file diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 45caaa9f031e..15fc4227d5f5 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -1062,13 +1062,6 @@ "fixable": false, "hasSuggestions": false }, - { - "name": "no-return-await", - "description": "Disallow unnecessary `return await`", - "recommended": false, - "fixable": false, - "hasSuggestions": true - }, { "name": "no-script-url", "description": "Disallow `javascript:` urls", @@ -1983,6 +1976,10 @@ "name": "no-restricted-modules", "replacedBy": [] }, + { + "name": "no-return-await", + "replacedBy": [] + }, { "name": "no-spaced-func", "replacedBy": [ diff --git a/docs/src/_data/rules_meta.json b/docs/src/_data/rules_meta.json index 6d9fb3d5a224..450c073c54dc 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -1578,6 +1578,8 @@ "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-return-await" }, + "deprecated": true, + "replacedBy": [], "fixable": null }, "no-script-url": { diff --git a/docs/src/rules/no-return-await.md b/docs/src/rules/no-return-await.md index 8f42100cffff..80328c09d8d6 100644 --- a/docs/src/rules/no-return-await.md +++ b/docs/src/rules/no-return-await.md @@ -2,10 +2,12 @@ title: no-return-await rule_type: suggestion further_reading: +- https://v8.dev/blog/fast-async - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function - 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). 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. diff --git a/lib/rules/no-return-await.js b/lib/rules/no-return-await.js index b5abf14c69b8..77abda0cadfd 100644 --- a/lib/rules/no-return-await.js +++ b/lib/rules/no-return-await.js @@ -1,6 +1,7 @@ /** * @fileoverview Disallows unnecessary `return await` * @author Jordan Harband + * @deprecated in ESLint v8.46.0 */ "use strict"; @@ -26,6 +27,10 @@ module.exports = { fixable: null, + deprecated: true, + + replacedBy: [], + schema: [ ], diff --git a/packages/js/src/configs/eslint-all.js b/packages/js/src/configs/eslint-all.js index f3a415cce5c2..177ff92f3164 100644 --- a/packages/js/src/configs/eslint-all.js +++ b/packages/js/src/configs/eslint-all.js @@ -172,7 +172,6 @@ module.exports = Object.freeze({ "no-restricted-properties": "error", "no-restricted-syntax": "error", "no-return-assign": "error", - "no-return-await": "error", "no-script-url": "error", "no-self-assign": "error", "no-self-compare": "error", From ee68d1d9630892d99ae0d8dabe2f9f8d3b1338be Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Thu, 27 Jul 2023 19:17:42 +0200 Subject: [PATCH 114/248] feat: `no-empty-character-class` support `v` flag (#17419) Reports nested character classes Refs #17223 --- docs/src/rules/no-empty-character-class.md | 20 +++++++++ lib/rules/no-empty-character-class.js | 45 +++++++++++++++------ tests/lib/rules/no-empty-character-class.js | 24 ++++++++++- 3 files changed, 75 insertions(+), 14 deletions(-) diff --git a/docs/src/rules/no-empty-character-class.md b/docs/src/rules/no-empty-character-class.md index 984f1e183306..2ab0679b394c 100644 --- a/docs/src/rules/no-empty-character-class.md +++ b/docs/src/rules/no-empty-character-class.md @@ -24,6 +24,23 @@ Examples of **incorrect** code for this rule: /^abc[]/.test("abcdefg"); // false "abcdefg".match(/^abc[]/); // null + +/^abc[[]]/v.test("abcdefg"); // false +"abcdefg".match(/^abc[[]]/v); // null + +/^abc[[]--[x]]/v.test("abcdefg"); // false +"abcdefg".match(/^abc[[]--[x]]/v); // null + +/^abc[[d]&&[]]/v.test("abcdefg"); // false +"abcdefg".match(/^abc[[d]&&[]]/v); // null + +const regex = /^abc[d[]]/v; +regex.test("abcdefg"); // true, the nested `[]` has no effect +"abcdefg".match(regex); // ["abcd"] +regex.test("abcefg"); // false, the nested `[]` has no effect +"abcefg".match(regex); // null +regex.test("abc"); // false, the nested `[]` has no effect +"abc".match(regex); // null ``` ::: @@ -40,6 +57,9 @@ Examples of **correct** code for this rule: /^abc[a-z]/.test("abcdefg"); // true "abcdefg".match(/^abc[a-z]/); // ["abcd"] + +/^abc[^]/.test("abcdefg"); // true +"abcdefg".match(/^abc[^]/); // ["abcd"] ``` ::: diff --git a/lib/rules/no-empty-character-class.js b/lib/rules/no-empty-character-class.js index da29bbe92704..5c8410235bcc 100644 --- a/lib/rules/no-empty-character-class.js +++ b/lib/rules/no-empty-character-class.js @@ -5,20 +5,18 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); + //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -/* - * plain-English description of the following regexp: - * 0. `^` fix the match at the beginning of the string - * 1. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following - * 1.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes) - * 1.1. `\\.`: an escape sequence - * 1.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty - * 2. `$`: fix the match at the end of the string - */ -const regex = /^([^\\[]|\\.|\[([^\\\]]|\\.)+\])*$/u; +const parser = new RegExpParser(); +const QUICK_TEST_REGEX = /\[\]/u; //------------------------------------------------------------------------------ // Rule Definition @@ -45,9 +43,32 @@ module.exports = { create(context) { return { "Literal[regex]"(node) { - if (!regex.test(node.regex.pattern)) { - context.report({ node, messageId: "unexpected" }); + const { pattern, flags } = node.regex; + + if (!QUICK_TEST_REGEX.test(pattern)) { + return; } + + let regExpAST; + + try { + regExpAST = parser.parsePattern(pattern, 0, pattern.length, { + unicode: flags.includes("u"), + unicodeSets: flags.includes("v") + }); + } catch { + + // Ignore regular expressions that regexpp cannot parse + return; + } + + visitRegExpAST(regExpAST, { + onCharacterClassEnter(characterClass) { + if (!characterClass.negate && characterClass.elements.length === 0) { + context.report({ node, messageId: "unexpected" }); + } + } + }); } }; diff --git a/tests/lib/rules/no-empty-character-class.js b/tests/lib/rules/no-empty-character-class.js index fd4cef8ed77b..81b66c4b3002 100644 --- a/tests/lib/rules/no-empty-character-class.js +++ b/tests/lib/rules/no-empty-character-class.js @@ -25,15 +25,26 @@ ruleTester.run("no-empty-character-class", rule, { "var foo = /^abc/;", "var foo = /[\\[]/;", "var foo = /[\\]]/;", + "var foo = /\\[][\\]]/;", "var foo = /[a-zA-Z\\[]/;", "var foo = /[[]/;", "var foo = /[\\[a-z[]]/;", "var foo = /[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\^\\$\\|]/g;", "var foo = /\\s*:\\s*/gim;", + "var foo = /[^]/;", // this rule allows negated empty character classes + "var foo = /\\[][^]/;", { code: "var foo = /[\\]]/uy;", parserOptions: { ecmaVersion: 6 } }, { code: "var foo = /[\\]]/s;", parserOptions: { ecmaVersion: 2018 } }, { code: "var foo = /[\\]]/d;", parserOptions: { ecmaVersion: 2022 } }, - "var foo = /\\[]/" + "var foo = /\\[]/", + { code: "var foo = /[[^]]/v;", parserOptions: { ecmaVersion: 2024 } }, + { code: "var foo = /[[\\]]]/v;", parserOptions: { ecmaVersion: 2024 } }, + { code: "var foo = /[[\\[]]/v;", parserOptions: { ecmaVersion: 2024 } }, + { code: "var foo = /[a--b]/v;", parserOptions: { ecmaVersion: 2024 } }, + { code: "var foo = /[a&&b]/v;", parserOptions: { ecmaVersion: 2024 } }, + { code: "var foo = /[[a][b]]/v;", parserOptions: { ecmaVersion: 2024 } }, + { code: "var foo = /[\\q{}]/v;", parserOptions: { ecmaVersion: 2024 } }, + { code: "var foo = /[[^]--\\p{ASCII}]/v;", parserOptions: { ecmaVersion: 2024 } } ], invalid: [ { code: "var foo = /^abc[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] }, @@ -43,6 +54,15 @@ ruleTester.run("no-empty-character-class", rule, { { code: "var foo = /[]]/;", errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "var foo = /\\[[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "var foo = /\\[\\[\\]a-z[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] }, - { code: "var foo = /[]]/d;", parserOptions: { ecmaVersion: 2022 }, errors: [{ messageId: "unexpected", type: "Literal" }] } + { code: "var foo = /[]]/d;", parserOptions: { ecmaVersion: 2022 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "var foo = /[(]\\u{0}*[]/u;", parserOptions: { ecmaVersion: 2015 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "var foo = /[]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "var foo = /[[]]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "var foo = /[[a][]]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "var foo = /[a[[b[]c]]d]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "var foo = /[a--[]]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "var foo = /[[]--b]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "var foo = /[a&&[]]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "var foo = /[[]&&b]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] } ] }); From 0aa0bc365a5425440c8e86c96104d0053a51b602 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 27 Jul 2023 21:54:34 -0400 Subject: [PATCH 115/248] chore: Add PRs to triage project (#17421) * chore: Add PRs to triage project * Update docs/src/maintain/manage-issues.md Co-authored-by: Amaresh S M * Update docs/src/maintain/manage-issues.md Co-authored-by: Nitin Kumar * Fix broken links --------- Co-authored-by: Amaresh S M Co-authored-by: Nitin Kumar --- .github/workflows/add-to-triage.yml | 3 ++ docs/src/contribute/work-on-issue.md | 4 +-- docs/src/maintain/manage-issues.md | 46 +++++++++++++++------------- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/.github/workflows/add-to-triage.yml b/.github/workflows/add-to-triage.yml index 83297dcab333..64ef0397e17c 100644 --- a/.github/workflows/add-to-triage.yml +++ b/.github/workflows/add-to-triage.yml @@ -4,6 +4,9 @@ on: issues: types: - opened + pull_request: + types: + - opened jobs: add-to-project: diff --git a/docs/src/contribute/work-on-issue.md b/docs/src/contribute/work-on-issue.md index 6205a37fd7e1..a141d59a8e61 100644 --- a/docs/src/contribute/work-on-issue.md +++ b/docs/src/contribute/work-on-issue.md @@ -11,10 +11,10 @@ Our public [issues tracker](https://github.com/eslint/eslint/issues) lists all o ## Issue Labels -We use labels to indicate the status of issues. The most complete documentation on the labels is found in the [Maintain ESLint documentation](../maintain/manage-issues#when-an-issue-is-opened), but most contributors should find the information on this page sufficient. The most important questions that labels can help you, as a contributor, answer are: +We use labels to indicate the status of issues. The most complete documentation on the labels is found in the [Maintain ESLint documentation](../maintain/manage-issues#when-an-issue-or-pull-request-is-opened), but most contributors should find the information on this page sufficient. The most important questions that labels can help you, as a contributor, answer are: 1. Is this issue available for me to work on? If you have little or no experience contributing to ESLint, the [`good first issue`](https://github.com/eslint/eslint/labels/good%20first%20issue) label marks appropriate issues. Otherwise, the [`help wanted`](https://github.com/eslint/eslint/labels/help%20wanted) label is an invitation to work on the issue. If you have more experience, you can try working on other issues labeled [`accepted`](https://github.com/eslint/eslint/labels/accepted). Conversely, issues not yet ready to work on are labeled `triage`, `evaluating`, and/or `needs bikeshedding`, and issues that cannot currently be worked on because of something else, such as a bug in a dependency, are labeled `blocked`. -1. What is this issue about? Labels describing the nature of issues include `bug`, `enhancement`, `feature`, `question`, `rule`, `documentation`, `core`, `build`, `cli`, `infrastructure`, `breaking`, and `chore`. These are documented in [Maintain ESLint](../maintain/manage-issues#types-of-issues). +1. What is this issue about? Labels describing the nature of issues include `bug`, `enhancement`, `feature`, `question`, `rule`, `documentation`, `core`, `build`, `cli`, `infrastructure`, `breaking`, and `chore`. These are documented in [Maintain ESLint](../maintain/manage-issues#types-of-issues-and-pull-requests). 1. What is the priority of this issue? Because we have a lot of issues, we prioritize certain issues above others. The following is the list of priorities, from highest to lowest: 1. **Bugs** - problems with the project are actively affecting users. We want to get these resolved as quickly as possible. diff --git a/docs/src/maintain/manage-issues.md b/docs/src/maintain/manage-issues.md index 778ad15ddbfa..9e243c12e720 100644 --- a/docs/src/maintain/manage-issues.md +++ b/docs/src/maintain/manage-issues.md @@ -1,14 +1,14 @@ --- -title: Manage Issues +title: Manage Issues and Pull Requests eleventyNavigation: key: manage issues parent: maintain eslint - title: Manage Issues + title: Manage Issues and Pull Requests order: 2 --- -New issues are filed frequently, and how we respond to those issues directly affects the success of the project. Being part of the project team means helping to triage and address issues as they come in so the project can continue to run smoothly. +New issues and pull requests are filed frequently, and how we respond to those issues directly affects the success of the project. Being part of the project team means helping to triage and address issues as they come in so the project can continue to run smoothly. ## Things to Keep in Mind @@ -17,44 +17,45 @@ New issues are filed frequently, and how we respond to those issues directly aff 1. **Not all requests are equal.** It's unlikely we'll be able to accommodate every request, so don't be afraid to say that something doesn't fit into the scope of the project or isn't practical. It's better to give such feedback if that's the case. 1. **Close when appropriate.** Don't be afraid to close issues that you don't think will be done, or when it's become clear from the conversation that there's no further work to do. Issues can always be reopened if they are closed incorrectly, so feel free to close issues when appropriate. Just be sure to leave a comment explaining why the issue is being closed (if not closed by a commit). -## Types of Issues +## Types of Issues and Pull Requests -There are four primary issue categories: +There are five primary categories: 1. **Bug**: Something isn't working the way it's expected to work. 1. **Enhancement**: A change to something that already exists. For instance, adding a new option to an existing rule or fixing a bug in a rule where fixing it will result in the rule reporting more problems (in this case, use both "Bug" and "Enhancement"). 1. **Feature**: Adding something that doesn't already exist. For example, adding a new rule, new formatter, or new command line flag. +1. **Documentation**: Adding, updating, or removing project documentation. 1. **Question**: An inquiry about how something works that won't result in a code change. We prefer if people use GitHub Discussions or Discord for questions, but sometimes they'll open an issue. -The first goal when evaluating an issue is to determine which category the issue falls into. +The first goal when evaluating an issue or pull request is to determine which category the issue falls into. ## Triaging Process -All of ESLint's issues, across all GitHub repositories, are managed on our [Triage Project](https://github.com/orgs/eslint/projects/3). Please use the Triage project instead of the issues list when reviewing issues to determine what to work on. The Triage project has several columns: +All of ESLint's issues and pull requests, across all GitHub repositories, are managed on our [Triage Project](https://github.com/orgs/eslint/projects/3). Please use the Triage project instead of the issues list when reviewing issues to determine what to work on. The Triage project has several columns: -* **Needs Triage**: Issues that have not yet been reviewed by anyone -* **Triaging**: Issues that someone has reviewed but has not been able to fully triage yet -* **Ready for Dev Team**: Issues that have been triaged and have all the information necessary for the dev team to take a look -* **Evaluating**: The dev team is evaluating these issues to determine whether to move forward or not +* **Needs Triage**: Issues and pull requests that have not yet been reviewed by anyone +* **Triaging**: Issues and pull requests that someone has reviewed but has not been able to fully triage yet +* **Ready for Dev Team**: Issues and pull requests that have been triaged and have all the information necessary for the dev team to take a look +* **Evaluating**: The dev team is evaluating these issues and pull requests to determine whether to move forward or not * **Feedback Needed**: A team member is requesting more input from the rest of the team before proceeding * **Waiting for RFC**: The next step in the process is for an RFC to be written * **RFC Opened**: An RFC is opened to address these issues * **Blocked**: The issue can't move forward due to some dependency * **Ready to Implement**: These issues have all the details necessary to start implementation -* **Implementing**: There is an open pull request for each of these issues +* **Implementing**: There is an open pull request for each of these issues or this is a pull request that has been approved * **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. -### When an Issue is Opened +### When an Issue or Pull Request is Opened -When an issue is opened, it is automatically added to the "Needs Triage" column in the Triage project. These issues need to be evaluated to determine next steps. Anyone on the support team or dev team can follow these steps to properly triage issues. +When an issue or pull request is opened, it is automatically added to the "Needs Triage" column in the Triage project. These issues and pull requests need to be evaluated to determine the next steps. Anyone on the support team or dev team can follow these steps to properly triage issues. -**Note:** If an issue is in the "Triaging" column, that means someone is already triaging it, and you should let them finish. There's no need to comment on issues in the "Triaging" column unless someone asks for help. +**Note:** If an issue or pull request is in the "Triaging" column, that means someone is already triaging it, and you should let them finish. There's no need to comment on issues or pull requests in the "Triaging" column unless someone asks for help. -The steps for triaging an issue are: +The steps for triaging an issue or pull request are: -1. Move the issue from "Needs Triage" to "Triaging" in the Triage project. +1. Move the issue or pull request from "Needs Triage" to "Triaging" in the Triage project. 1. Check: Has all the information in the issue template been provided? * **No:** If information is missing from the issue template, or you can't tell what is being requested, please ask the author to provide the missing information: * Add the "needs info" label to the issue so we know that this issue is stalled due to lack of information. @@ -62,8 +63,8 @@ The steps for triaging an issue are: * If the issue author hasn't provided the necessary information after 7 days, please close the issue. The bot will add a comment stating that the issue was closed because there was information missing. * **Yes:** * If the issue is actually a question (rather than something the dev team needs to change), please [convert it to a discussion](https://docs.github.com/en/free-pro-team@latest/discussions/managing-discussions-for-your-community/moderating-discussions#converting-an-issue-to-a-discussion). You can continue the conversation as a discussion. - * If the issue is reporting a bug, try to reproduce the issue following the instructions in the issue. If you can reproduce the bug, please add the "repro:yes" label. (The bot will automatically remove the "repro:needed" label.) If you can't reproduce the bug, ask the author for more information about their environment or to clarify reproduction steps. - * If the issue is reporting something that works as intended, please add the "works as intended" label and close the issue. + * If the issue is reporting a bug, or if a pull request is fixing a bug, try to reproduce the issue following the instructions in the issue. If you can reproduce the bug, please add the "repro:yes" label. (The bot will automatically remove the "repro:needed" label.) If you can't reproduce the bug, ask the author for more information about their environment or to clarify reproduction steps. + * If the issue or pull request is reporting something that works as intended, please add the "works as intended" label and close the issue. * Please add labels describing the part of ESLint affected: * **3rd party plugin**: Related to third-party functionality (plugins, parsers, rules, etc.) * **build**: Related to commands run during a build (testing, linting, release scripts, etc.) @@ -72,7 +73,7 @@ The steps for triaging an issue are: * **documentation**: Related to content on eslint.org * **infrastructure**: Related to resources needed for builds or deployment (VMs, CI tools, bots, etc.) * **rule**: Related to core rules - * Please assign an initial priority based on the importance of the issue. If you're not sure, use your best judgment. We can always change the priority later. + * Please assign an initial priority based on the importance of the issue or pull request. If you're not sure, use your best judgment. We can always change the priority later. * **P1**: Urgent and important, we need to address this immediately. * **P2**: Important but not urgent. Should be handled by a TSC member or reviewer. * **P3**: Nice to have but not important. Can be handled by any team member. @@ -82,7 +83,8 @@ The steps for triaging an issue are: * **Low**: Doesn't affect many users. * **Medium**: Affects most users or has a noticeable effect on user experience. * **High**: Affects a lot of users, is a breaking change, or otherwise will be very noticeable to users. - * If you can't properly triage the issue, move the issue back to the "Needs Triage" column in the Triage project so someone else can triage it. + * If you can't properly triage the issue or pull request, move the issue back to the "Needs Triage" column in the Triage project so someone else can triage it. + * If a pull request references an already accepted issue, move it to the "Implementing" column in the Triage project. * If you have triaged the issue, move the issue to the "Ready for Dev Team" column in the Triage project. ## Evaluation Process @@ -102,7 +104,7 @@ When an issue has been moved to the "Ready for Dev Team" column, any dev team me **Note:** "Good first issue" issues are intended to help new contributors feel welcome and empowered to make a contribution to ESLint. To ensure that new contributors are given a chance to work on these issues, issues labeled "good first issue" must be open for 30 days *from the day the issue was labeled* before a team member is permitted to work on them. -## Accepting Issues +## Accepting Issues and Pull Requests Issues may be labeled as "accepted" when the issue is: From 1a2f966fabe35103141d2f936180d2f1a72154db Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Fri, 28 Jul 2023 19:42:10 +0900 Subject: [PATCH 116/248] feat: `no-useless-escape` support `v` flag (#17420) * feat: `no-useless-escape` support `v` flag * fix: false positives for `/[\^]/v` * fix: remove unnecessary `if` statement * fix: incorrect suggestion --- lib/rules/no-useless-escape.js | 235 ++++++--- tests/lib/rules/no-useless-escape.js | 734 ++++++++++++++++++++++++++- 2 files changed, 888 insertions(+), 81 deletions(-) diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js index 71d006b2323e..0e0f6f09f2c3 100644 --- a/lib/rules/no-useless-escape.js +++ b/lib/rules/no-useless-escape.js @@ -6,7 +6,12 @@ "use strict"; const astUtils = require("./utils/ast-utils"); +const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); +/** + * @typedef {import('@eslint-community/regexpp').AST.CharacterClass} CharacterClass + * @typedef {import('@eslint-community/regexpp').AST.ExpressionCharacterClass} ExpressionCharacterClass + */ //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -28,55 +33,17 @@ const VALID_STRING_ESCAPES = union(new Set("\\nrvtbfux"), astUtils.LINEBREAKS); const REGEX_GENERAL_ESCAPES = new Set("\\bcdDfnpPrsStvwWxu0123456789]"); const REGEX_NON_CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("^/.$*+?[{}|()Bk")); -/** - * Parses a regular expression into a list of characters with character class info. - * @param {string} regExpText The raw text used to create the regular expression - * @returns {Object[]} A list of characters, each with info on escaping and whether they're in a character class. - * @example - * - * parseRegExp("a\\b[cd-]"); - * - * // returns: - * [ - * { text: "a", index: 0, escaped: false, inCharClass: false, startsCharClass: false, endsCharClass: false }, - * { text: "b", index: 2, escaped: true, inCharClass: false, startsCharClass: false, endsCharClass: false }, - * { text: "c", index: 4, escaped: false, inCharClass: true, startsCharClass: true, endsCharClass: false }, - * { text: "d", index: 5, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false }, - * { text: "-", index: 6, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false } - * ]; - * +/* + * Set of characters that require escaping in character classes in `unicodeSets` mode. + * ( ) [ ] { } / - \ | are ClassSetSyntaxCharacter */ -function parseRegExp(regExpText) { - const charList = []; +const REGEX_CLASSSET_CHARACTER_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("q/[{}|()-")); - regExpText.split("").reduce((state, char, index) => { - if (!state.escapeNextChar) { - if (char === "\\") { - return Object.assign(state, { escapeNextChar: true }); - } - if (char === "[" && !state.inCharClass) { - return Object.assign(state, { inCharClass: true, startingCharClass: true }); - } - if (char === "]" && state.inCharClass) { - if (charList.length && charList[charList.length - 1].inCharClass) { - charList[charList.length - 1].endsCharClass = true; - } - return Object.assign(state, { inCharClass: false, startingCharClass: false }); - } - } - charList.push({ - text: char, - index, - escaped: state.escapeNextChar, - inCharClass: state.inCharClass, - startsCharClass: state.startingCharClass, - endsCharClass: false - }); - return Object.assign(state, { escapeNextChar: false, startingCharClass: false }); - }, { escapeNextChar: false, inCharClass: false, startingCharClass: false }); - - return charList; -} +/* + * A single character set of ClassSetReservedDoublePunctuator. + * && !! ## $$ %% ** ++ ,, .. :: ;; << == >> ?? @@ ^^ `` ~~ are ClassSetReservedDoublePunctuator + */ +const REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR = new Set("!#$%&*+,.:;<=>?@^`~"); /** @type {import('../shared/types').Rule} */ module.exports = { @@ -103,15 +70,17 @@ module.exports = { create(context) { const sourceCode = context.sourceCode; + const parser = new RegExpParser(); /** * Reports a node * @param {ASTNode} node The node to report * @param {number} startOffset The backslash's offset from the start of the node * @param {string} character The uselessly escaped character (not including the backslash) + * @param {boolean} [disableEscapeBackslashSuggest] `true` if escapeBackslash suggestion should be turned off. * @returns {void} */ - function report(node, startOffset, character) { + function report(node, startOffset, character, disableEscapeBackslashSuggest) { const rangeStart = node.range[0] + startOffset; const range = [rangeStart, rangeStart + 1]; const start = sourceCode.getLocFromIndex(rangeStart); @@ -134,12 +103,16 @@ module.exports = { return fixer.removeRange(range); } }, - { - messageId: "escapeBackslash", - fix(fixer) { - return fixer.insertTextBeforeRange(range, "\\"); - } - } + ...disableEscapeBackslashSuggest + ? [] + : [ + { + messageId: "escapeBackslash", + fix(fixer) { + return fixer.insertTextBeforeRange(range, "\\"); + } + } + ] ] }); } @@ -182,6 +155,133 @@ module.exports = { } } + /** + * Checks if the escape character in given regexp is unnecessary. + * @private + * @param {ASTNode} node node to validate. + * @returns {void} + */ + function validateRegExp(node) { + const { pattern, flags } = node.regex; + let patternNode; + const unicode = flags.includes("u"); + const unicodeSets = flags.includes("v"); + + try { + patternNode = parser.parsePattern(pattern, 0, pattern.length, { unicode, unicodeSets }); + } catch { + + // Ignore regular expressions with syntax errors + return; + } + + /** @type {(CharacterClass | ExpressionCharacterClass)[]} */ + const characterClassStack = []; + + visitRegExpAST(patternNode, { + onCharacterClassEnter: characterClassNode => characterClassStack.unshift(characterClassNode), + onCharacterClassLeave: () => characterClassStack.shift(), + onExpressionCharacterClassEnter: characterClassNode => characterClassStack.unshift(characterClassNode), + onExpressionCharacterClassLeave: () => characterClassStack.shift(), + onCharacterEnter(characterNode) { + if (!characterNode.raw.startsWith("\\")) { + + // It's not an escaped character. + return; + } + + const escapedChar = characterNode.raw.slice(1); + + if (escapedChar !== String.fromCodePoint(characterNode.value)) { + + // It's a valid escape. + return; + } + let allowedEscapes; + + if (characterClassStack.length) { + allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : REGEX_GENERAL_ESCAPES; + } else { + allowedEscapes = REGEX_NON_CHARCLASS_ESCAPES; + } + if (allowedEscapes.has(escapedChar)) { + return; + } + + const reportedIndex = characterNode.start + 1; + let disableEscapeBackslashSuggest = false; + + if (characterClassStack.length) { + const characterClassNode = characterClassStack[0]; + + if (escapedChar === "^") { + + /* + * The '^' character is also a special case; it must always be escaped outside of character classes, but + * it only needs to be escaped in character classes if it's at the beginning of the character class. To + * account for this, consider it to be a valid escape character outside of character classes, and filter + * out '^' characters that appear at the start of a character class. + */ + if (characterClassNode.start + 1 === characterNode.start) { + + return; + } + } + if (!unicodeSets) { + if (escapedChar === "-") { + + /* + * The '-' character is a special case, because it's only valid to escape it if it's in a character + * class, and is not at either edge of the character class. To account for this, don't consider '-' + * characters to be valid in general, and filter out '-' characters that appear in the middle of a + * character class. + */ + if (characterClassNode.start + 1 !== characterNode.start && characterNode.end !== characterClassNode.end - 1) { + + return; + } + } + } else { // unicodeSets mode + if (REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR.has(escapedChar)) { + + // Escaping is valid if it is a ClassSetReservedDoublePunctuator. + if (pattern[characterNode.end] === escapedChar) { + return; + } + if (pattern[characterNode.start - 1] === escapedChar) { + if (escapedChar !== "^") { + return; + } + + // If the previous character is a `negate` caret(`^`), escape to caret is unnecessary. + + if (!characterClassNode.negate) { + return; + } + const negateCaretIndex = characterClassNode.start + 1; + + if (negateCaretIndex < characterNode.start - 1) { + return; + } + } + } + + if (characterNode.parent.type === "ClassIntersection" || characterNode.parent.type === "ClassSubtraction") { + disableEscapeBackslashSuggest = true; + } + } + } + + report( + node, + reportedIndex, + escapedChar, + disableEscapeBackslashSuggest + ); + } + }); + } + /** * Checks if a node has an escape. * @param {ASTNode} node node to check. @@ -220,32 +320,7 @@ module.exports = { validateString(node, match); } } else if (node.regex) { - parseRegExp(node.regex.pattern) - - /* - * The '-' character is a special case, because it's only valid to escape it if it's in a character - * class, and is not at either edge of the character class. To account for this, don't consider '-' - * characters to be valid in general, and filter out '-' characters that appear in the middle of a - * character class. - */ - .filter(charInfo => !(charInfo.text === "-" && charInfo.inCharClass && !charInfo.startsCharClass && !charInfo.endsCharClass)) - - /* - * The '^' character is also a special case; it must always be escaped outside of character classes, but - * it only needs to be escaped in character classes if it's at the beginning of the character class. To - * account for this, consider it to be a valid escape character outside of character classes, and filter - * out '^' characters that appear at the start of a character class. - */ - .filter(charInfo => !(charInfo.text === "^" && charInfo.startsCharClass)) - - // Filter out characters that aren't escaped. - .filter(charInfo => charInfo.escaped) - - // Filter out characters that are valid to escape, based on their position in the regular expression. - .filter(charInfo => !(charInfo.inCharClass ? REGEX_GENERAL_ESCAPES : REGEX_NON_CHARCLASS_ESCAPES).has(charInfo.text)) - - // Report all the remaining characters. - .forEach(charInfo => report(node, charInfo.index, charInfo.text)); + validateRegExp(node); } } diff --git a/tests/lib/rules/no-useless-escape.js b/tests/lib/rules/no-useless-escape.js index 77a3c57807bf..3130d52a0aae 100644 --- a/tests/lib/rules/no-useless-escape.js +++ b/tests/lib/rules/no-useless-escape.js @@ -127,7 +127,65 @@ ruleTester.run("no-useless-escape", rule, { { code: String.raw`var foo = /\p{ASCII}/u`, parserOptions: { ecmaVersion: 2018 } }, { code: String.raw`var foo = /\P{ASCII}/u`, parserOptions: { ecmaVersion: 2018 } }, { code: String.raw`var foo = /[\p{ASCII}]/u`, parserOptions: { ecmaVersion: 2018 } }, - { code: String.raw`var foo = /[\P{ASCII}]/u`, parserOptions: { ecmaVersion: 2018 } } + { code: String.raw`var foo = /[\P{ASCII}]/u`, parserOptions: { ecmaVersion: 2018 } }, + + // Carets + String.raw`/[^^]/`, + { code: String.raw`/[^^]/u`, parserOptions: { ecmaVersion: 2015 } }, + + // ES2024 + { code: String.raw`/[\q{abc}]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\(]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\)]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\{]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\]]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\}]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\/]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\-]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\|]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\$$]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\&&]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\!!]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\##]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\%%]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\**]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\++]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\,,]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\..]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\::]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\;;]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\<<]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\==]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\>>]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\??]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\@@]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: "/[\\``]/v", parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\~~]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[^\^^]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[_\^^]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[$\$]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[&\&]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[!\!]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[#\#]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[%\%]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[*\*]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[+\+]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[,\,]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[.\.]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[:\:]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[;\;]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[<\<]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[=\=]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[>\>]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[?\?]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[@\@]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: "/[`\\`]/v", parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[~\~]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[^^\^]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[_^\^]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\&&&\&]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[[\-]\-]/v`, parserOptions: { ecmaVersion: 2024 } }, + { code: String.raw`/[\^]/v`, parserOptions: { ecmaVersion: 2024 } } ], invalid: [ @@ -1103,6 +1161,680 @@ ruleTester.run("no-useless-escape", rule, { output: String.raw`({ foo() { "foo"; "bar"; "ba\\z" } })` }] }] + }, + + // Carets + { + code: String.raw`/[^\^]/`, + errors: [{ + line: 1, + column: 4, + message: "Unnecessary escape character: \\^.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: "/[^^]/" + }, + { + messageId: "escapeBackslash", + output: String.raw`/[^\\^]/` + } + ] + }] + }, + { + code: String.raw`/[^\^]/u`, + parserOptions: { ecmaVersion: 2015 }, + errors: [{ + line: 1, + column: 4, + message: "Unnecessary escape character: \\^.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: "/[^^]/u" + }, + { + messageId: "escapeBackslash", + output: String.raw`/[^\\^]/u` + } + ] + }] + }, + + // ES2024 + { + code: String.raw`/[\$]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + endColumn: 4, + message: "Unnecessary escape character: \\$.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: "/[$]/v" + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\$]/v` + } + ] + }] + }, + { + code: String.raw`/[\&\&]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\&.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[&\&]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\&\&]/v` + } + ] + }] + }, + { + code: String.raw`/[\!\!]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\!.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[!\!]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\!\!]/v` + } + ] + }] + }, + { + code: String.raw`/[\#\#]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\#.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[#\#]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\#\#]/v` + } + ] + }] + }, + { + code: String.raw`/[\%\%]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\%.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[%\%]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\%\%]/v` + } + ] + }] + }, + { + code: String.raw`/[\*\*]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\*.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[*\*]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\*\*]/v` + } + ] + }] + }, + { + code: String.raw`/[\+\+]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\+.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[+\+]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\+\+]/v` + } + ] + }] + }, + { + code: String.raw`/[\,\,]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\,.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[,\,]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\,\,]/v` + } + ] + }] + }, + { + code: String.raw`/[\.\.]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[.\.]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\.\.]/v` + } + ] + }] + }, + { + code: String.raw`/[\:\:]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\:.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[:\:]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\:\:]/v` + } + ] + }] + }, + { + code: String.raw`/[\;\;]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\;.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[;\;]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\;\;]/v` + } + ] + }] + }, + { + code: String.raw`/[\<\<]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\<.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[<\<]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\<\<]/v` + } + ] + }] + }, + { + code: String.raw`/[\=\=]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\=.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[=\=]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\=\=]/v` + } + ] + }] + }, + { + code: String.raw`/[\>\>]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\>.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[>\>]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\>\>]/v` + } + ] + }] + }, + { + code: String.raw`/[\?\?]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\?.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[?\?]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\?\?]/v` + } + ] + }] + }, + { + code: String.raw`/[\@\@]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\@.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[@\@]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\@\@]/v` + } + ] + }] + }, + { + code: "/[\\`\\`]/v", + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\`.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: "/[`\\`]/v" + }, + { + messageId: "escapeBackslash", + output: "/[\\\\`\\`]/v" + } + ] + }] + }, + { + code: String.raw`/[\~\~]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\~.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[~\~]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\~\~]/v` + } + ] + }] + }, + { + code: String.raw`/[^\^\^]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 4, + message: "Unnecessary escape character: \\^.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[^^\^]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[^\\^\^]/v` + } + ] + }] + }, + { + code: String.raw`/[_\^\^]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 4, + message: "Unnecessary escape character: \\^.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[_^\^]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[_\\^\^]/v` + } + ] + }] + }, + { + code: String.raw`/[\&\&&\&]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\&.", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[&\&&\&]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[\\&\&&\&]/v` + } + ] + }] + }, + { + code: String.raw`/[\p{ASCII}--\.]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 14, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[\p{ASCII}--.]/v` + } + ] + }] + }, + { + code: String.raw`/[\p{ASCII}&&\.]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 14, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[\p{ASCII}&&.]/v` + } + ] + }] + }, + { + code: String.raw`/[\.--[.&]]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[.--[.&]]/v` + } + ] + }] + }, + { + code: String.raw`/[\.&&[.&]]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[.&&[.&]]/v` + } + ] + }] + }, + { + code: String.raw`/[\.--\.--\.]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[.--\.--\.]/v` + } + ] + }, { + line: 1, + column: 7, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[\.--.--\.]/v` + } + ] + }, { + line: 1, + column: 11, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[\.--\.--.]/v` + } + ] + }] + }, + { + code: String.raw`/[\.&&\.&&\.]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 3, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[.&&\.&&\.]/v` + } + ] + }, { + line: 1, + column: 7, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[\.&&.&&\.]/v` + } + ] + }, { + line: 1, + column: 11, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[\.&&\.&&.]/v` + } + ] + }] + }, + { + code: String.raw`/[[\.&]--[\.&]]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 4, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[[.&]--[\.&]]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[[\\.&]--[\.&]]/v` + } + ] + }, { + line: 1, + column: 11, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[[\.&]--[.&]]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[[\.&]--[\\.&]]/v` + } + ] + }] + }, + { + code: String.raw`/[[\.&]&&[\.&]]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ + line: 1, + column: 4, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[[.&]&&[\.&]]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[[\\.&]&&[\.&]]/v` + } + ] + }, { + line: 1, + column: 11, + message: "Unnecessary escape character: \\..", + type: "Literal", + suggestions: [ + { + messageId: "removeEscape", + output: String.raw`/[[\.&]&&[.&]]/v` + }, + { + messageId: "escapeBackslash", + output: String.raw`/[[\.&]&&[\\.&]]/v` + } + ] + }] } ] }); From 091f44e4c72007edb2ac6d4db4eafa5501e41e94 Mon Sep 17 00:00:00 2001 From: Matt Wilkinson Date: Fri, 28 Jul 2023 16:05:24 +0100 Subject: [PATCH 117/248] docs: File extension named processor deprecation (#17362) --- docs/src/extend/custom-processors.md | 4 ++ docs/src/extend/plugins.md | 4 ++ docs/src/use/configure/migration-guide.md | 77 +++++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/docs/src/extend/custom-processors.md b/docs/src/extend/custom-processors.md index 697d052e6556..a47271e2e9e6 100644 --- a/docs/src/extend/custom-processors.md +++ b/docs/src/extend/custom-processors.md @@ -139,6 +139,10 @@ See [Specify a Processor](../use/configure/plugins#specify-a-processor) in the P ## File Extension-named Processor +::: warning +This feature is deprecated and only works in eslintrc-style configuration files. Flat config files do not automatically apply processors; you need to explicitly set the `processor` property. +::: + If a custom processor name starts with `.`, ESLint handles the processor as a **file extension-named processor**. ESLint applies the processor to files with that filename extension automatically. Users don't need to specify the file extension-named processors in their config files. For example: diff --git a/docs/src/extend/plugins.md b/docs/src/extend/plugins.md index 70bc3ee7c746..82e8fd8f1170 100644 --- a/docs/src/extend/plugins.md +++ b/docs/src/extend/plugins.md @@ -89,6 +89,10 @@ Plugin environments can define the following objects: ### Processors in Plugins +::: warning +File extension-named processors are deprecated and only work in eslintrc-style configuration files. Flat config files do not automatically apply processors; you need to explicitly set the `processor` property. +::: + You can add processors to plugins by including the processor functions in the `processors` key. For more information on defining custom processors, refer to [Custom Processors](custom-processors). ```js diff --git a/docs/src/use/configure/migration-guide.md b/docs/src/use/configure/migration-guide.md index b931244e144b..d89a8347dfc9 100644 --- a/docs/src/use/configure/migration-guide.md +++ b/docs/src/use/configure/migration-guide.md @@ -114,6 +114,83 @@ export default [ ]; ``` +### Processors + +In eslintrc files, processors had to be defined in a plugin, and then referenced by name in the configuration. Processors beginning with a dot indicated a [file extension-named processor](../../extend/custom-processors#file-extension-named-processor) which ESLint would automatically configure for that file extension. + +In flat config files, processors can still be referenced from plugins by their name, but they can now also be inserted directly into the configuration. Processors will _never_ be automatically configured, and must be explicitly set in the configuration. + +As an example with a custom plugin with processors: + +```javascript +// node_modules/eslint-plugin-someplugin/index.js +module.exports = { + processors: { + ".md": { + preprocess() {}, + postprocess() {} + }, + "someProcessor": { + preprocess() {}, + postprocess() {} + } + } +}; +``` + +In eslintrc, you would configure as follows: + +```javascript +// .eslintrc.js +module.exports = { + plugins: ["someplugin"], + processor: "someplugin/someProcessor" +}; +``` + +ESLint would also automatically add the equivalent of the following: + +```javascript +{ + overrides: [{ + files: ["**/*.md"], + processor: "someplugin/.md" + }] +} +``` + +In flat config, the following are all valid ways to express the same: + +```javascript +// eslint.config.js +import somePlugin from "eslint-plugin-someplugin"; + +export default [ + { + plugins: { somePlugin }, + processor: "somePlugin/someProcessor" + }, + { + plugins: { somePlugin }, + // We can embed the processor object in the config directly + processor: somePlugin.processors.someProcessor + }, + { + // We don't need the plugin to be present in the config to use the processor directly + processor: somePlugin.processors.someProcessor + } +]; +``` + +Note that because the `.md` processor is _not_ automatically added by flat config, you also need to specify an extra configuration element: + +```javascript +{ + files: ["**/*.md"], + processor: somePlugin.processors[".md"] +} +``` + ### Glob-Based Configs By default, eslintrc files lint all files (except those covered by `.eslintignore`) in the directory in which theyโ€™re placed and its child directories. If you want to have different configurations for different file glob patterns, you can specify them in the `overrides` property. From 4d474e351ba6ce0242f18e55c27cb3ae17b84f63 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:37:11 -0400 Subject: [PATCH 118/248] docs: update with TypeScript info (#17423) * docs: update with TypeScript info * fix: grammar * fix: ts numbers * Update constructor-super.md * Update no-dupe-class-members.md --- docs/src/rules/constructor-super.md | 2 ++ docs/src/rules/no-dupe-class-members.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/src/rules/constructor-super.md b/docs/src/rules/constructor-super.md index 93019eb2a795..c172b0a7c279 100644 --- a/docs/src/rules/constructor-super.md +++ b/docs/src/rules/constructor-super.md @@ -69,3 +69,5 @@ class A extends B { ## When Not To Use It If you don't want to be notified about invalid/missing `super()` callings in constructors, you can safely disable this rule. + +It is safe to disable this rule when using TypeScript because TypeScript's compiler enforces this check (`ts(2335) & ts(2377)`). diff --git a/docs/src/rules/no-dupe-class-members.md b/docs/src/rules/no-dupe-class-members.md index aaea4ce324e7..d50a7fe74e11 100644 --- a/docs/src/rules/no-dupe-class-members.md +++ b/docs/src/rules/no-dupe-class-members.md @@ -102,4 +102,4 @@ This rule should not be used in ES3/5 environments. In ES2015 (ES6) or later, if you don't want to be notified about duplicate names in class members, you can safely disable this rule. -It's also safe to disable this rule when using TypeScript because TypeScript's compiler already checks for duplicate function implementations. +It is safe to disable this rule when using TypeScript because TypeScript's compiler enforces this check (`ts(2300) & ts(2393)`). From 8a9343871f7dade19d910ca8e2a4177bfca28b64 Mon Sep 17 00:00:00 2001 From: SUZUKI Sosuke Date: Sat, 29 Jul 2023 00:38:24 +0900 Subject: [PATCH 119/248] feat: `require-unicode-regexp` support `v` flag (#17402) * feat: implement RegExp literal * test: add tests for RegExp literal * feat: implement for RegExp constructor * test: add tests for RegExp constructor * test: add invalid tests * feat: implement replacement for u and v * test: add tests * docs: update docs for `v` flag * fix: address review * Update lib/rules/require-unicode-regexp.js Co-authored-by: Milos Djermanovic * Update docs/src/rules/require-unicode-regexp.md Co-authored-by: Milos Djermanovic * Update tests/lib/rules/require-unicode-regexp.js Co-authored-by: Milos Djermanovic * Update tests/lib/rules/require-unicode-regexp.js Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- docs/src/rules/require-unicode-regexp.md | 37 ++++++++++++++++++++++- lib/rules/require-unicode-regexp.js | 6 ++-- tests/lib/rules/require-unicode-regexp.js | 15 ++++++++- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/docs/src/rules/require-unicode-regexp.md b/docs/src/rules/require-unicode-regexp.md index f3277066a5be..969818e2c0d9 100644 --- a/docs/src/rules/require-unicode-regexp.md +++ b/docs/src/rules/require-unicode-regexp.md @@ -21,7 +21,37 @@ RegExp `u` flag has two effects: The `u` flag disables the recovering logic Annex B defined. As a result, you can find errors early. This is similar to [the strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode). -Therefore, the `u` flag lets us work better with regular expressions. +The RegExp `v` flag, introduced in ECMAScript 2024, is a superset of the `u` flag, and offers two more features: + +1. **Unicode properties of strings** + + With the Unicode property escape, you can use properties of strings. + + ```js + const re = /^\p{RGI_Emoji}$/v; + + // Match an emoji that consists of just 1 code point: + re.test('โšฝ'); // '\u26BD' + // โ†’ true โœ… + + // Match an emoji that consists of multiple code points: + re.test('๐Ÿ‘จ๐Ÿพโ€โš•๏ธ'); // '\u{1F468}\u{1F3FE}\u200D\u2695\uFE0F' + // โ†’ true โœ… + ``` + +2. **Set notation** + + It allows for set operations between character classes. + + ```js + const re = /[\p{White_Space}&&\p{ASCII}]/v; + re.test('\n'); // โ†’ true + re.test('\u2028'); // โ†’ false + ``` + +Please see and for more details. + +Therefore, the `u` and `v` flags let us work better with regular expressions. ## Rule Details @@ -54,6 +84,11 @@ const b = /bbb/giu const c = new RegExp("ccc", "u") const d = new RegExp("ddd", "giu") +const e = /aaa/v +const f = /bbb/giv +const g = new RegExp("ccc", "v") +const h = new RegExp("ddd", "giv") + // This rule ignores RegExp calls if the flags could not be evaluated to a static value. function f(flags) { return new RegExp("eee", flags) diff --git a/lib/rules/require-unicode-regexp.js b/lib/rules/require-unicode-regexp.js index f748753a2d13..dd687f8d796b 100644 --- a/lib/rules/require-unicode-regexp.js +++ b/lib/rules/require-unicode-regexp.js @@ -28,7 +28,7 @@ module.exports = { type: "suggestion", docs: { - description: "Enforce the use of `u` flag on RegExp", + description: "Enforce the use of `u` or `v` flag on RegExp", recommended: false, url: "https://eslint.org/docs/latest/rules/require-unicode-regexp" }, @@ -51,7 +51,7 @@ module.exports = { "Literal[regex]"(node) { const flags = node.regex.flags || ""; - if (!flags.includes("u")) { + if (!flags.includes("u") && !flags.includes("v")) { context.report({ messageId: "requireUFlag", node, @@ -85,7 +85,7 @@ module.exports = { const pattern = getStringIfConstant(patternNode, scope); const flags = getStringIfConstant(flagsNode, scope); - if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) { + if (!flagsNode || (typeof flags === "string" && !flags.includes("u") && !flags.includes("v"))) { context.report({ messageId: "requireUFlag", node: refNode, diff --git a/tests/lib/rules/require-unicode-regexp.js b/tests/lib/rules/require-unicode-regexp.js index a75f6863168d..eab11b70fed8 100644 --- a/tests/lib/rules/require-unicode-regexp.js +++ b/tests/lib/rules/require-unicode-regexp.js @@ -45,7 +45,20 @@ ruleTester.run("require-unicode-regexp", rule, { { code: "const flags = 'u'; new globalThis.RegExp('', flags)", env: { es2020: true } }, { code: "const flags = 'g'; new globalThis.RegExp('', flags + 'u')", env: { es2020: true } }, { code: "const flags = 'gimu'; new globalThis.RegExp('foo', flags[3])", env: { es2020: true } }, - { code: "class C { #RegExp; foo() { new globalThis.#RegExp('foo') } }", parserOptions: { ecmaVersion: 2022 }, env: { es2020: true } } + { code: "class C { #RegExp; foo() { new globalThis.#RegExp('foo') } }", parserOptions: { ecmaVersion: 2022 }, env: { es2020: true } }, + + // for v flag + { code: "/foo/v", parserOptions: { ecmaVersion: 2024 } }, + { code: "/foo/gimvy", parserOptions: { ecmaVersion: 2024 } }, + { code: "RegExp('', 'v')", parserOptions: { ecmaVersion: 2024 } }, + { code: "RegExp('', `v`)", parserOptions: { ecmaVersion: 2024 } }, + { code: "new RegExp('', 'v')", parserOptions: { ecmaVersion: 2024 } }, + { code: "RegExp('', 'gimvy')", parserOptions: { ecmaVersion: 2024 } }, + { code: "RegExp('', `gimvy`)", parserOptions: { ecmaVersion: 2024 } }, + { code: "new RegExp('', 'gimvy')", parserOptions: { ecmaVersion: 2024 } }, + { code: "const flags = 'v'; new RegExp('', flags)", parserOptions: { ecmaVersion: 2024 } }, + { code: "const flags = 'g'; new RegExp('', flags + 'v')", parserOptions: { ecmaVersion: 2024 } }, + { code: "const flags = 'gimv'; new RegExp('foo', flags[3])", parserOptions: { ecmaVersion: 2024 } } ], invalid: [ { From 6246711e0650d03afe044c36acde048ed2d39ee3 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 28 Jul 2023 11:39:57 -0400 Subject: [PATCH 120/248] 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 245ac45d3e59..e5f79c7cc457 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "8.45.0", + "version": "8.46.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From fab9e97ef9dff40e98a5b3b97bdd3b0ff5439d46 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 28 Jul 2023 11:41:52 -0400 Subject: [PATCH 121/248] 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 ffd1fbceb93a..7d8a747537c8 100644 --- a/packages/eslint-config-eslint/package.json +++ b/packages/eslint-config-eslint/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-eslint", - "version": "8.0.0", + "version": "9.0.0", "author": "Nicholas C. Zakas ", "description": "Default ESLint configuration for ESLint projects.", "exports": { From d1eb7e46e954c64af8d7d13d087b3a18f43e6d72 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 28 Jul 2023 12:00:35 -0400 Subject: [PATCH 122/248] chore: Update ecosystem dependencies (#17427) --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3833ed0a2b8b..1bde40211c2d 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,8 @@ "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.0", - "@eslint/js": "8.44.0", + "@eslint/eslintrc": "^2.1.1", + "@eslint/js": "^8.46.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -73,9 +73,9 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.6.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.2", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", From b51015fa88c7d9c390872fe0a225fe1d9946233d Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 28 Jul 2023 12:04:40 -0400 Subject: [PATCH 123/248] Build: changelog update for 8.46.0 --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 366782db95b5..001632b74bc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,37 @@ +v8.46.0 - July 28, 2023 + +* [`d1eb7e4`](https://github.com/eslint/eslint/commit/d1eb7e46e954c64af8d7d13d087b3a18f43e6d72) chore: Update ecosystem dependencies (#17427) (Nicholas C. Zakas) +* [`fab9e97`](https://github.com/eslint/eslint/commit/fab9e97ef9dff40e98a5b3b97bdd3b0ff5439d46) chore: package.json update for eslint-config-eslint release (ESLint Jenkins) +* [`6246711`](https://github.com/eslint/eslint/commit/6246711e0650d03afe044c36acde048ed2d39ee3) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`8a93438`](https://github.com/eslint/eslint/commit/8a9343871f7dade19d910ca8e2a4177bfca28b64) feat: `require-unicode-regexp` support `v` flag (#17402) (SUZUKI Sosuke) +* [`4d474e3`](https://github.com/eslint/eslint/commit/4d474e351ba6ce0242f18e55c27cb3ae17b84f63) docs: update with TypeScript info (#17423) (James) +* [`091f44e`](https://github.com/eslint/eslint/commit/091f44e4c72007edb2ac6d4db4eafa5501e41e94) docs: File extension named processor deprecation (#17362) (Matt Wilkinson) +* [`1a2f966`](https://github.com/eslint/eslint/commit/1a2f966fabe35103141d2f936180d2f1a72154db) feat: `no-useless-escape` support `v` flag (#17420) (Yosuke Ota) +* [`0aa0bc3`](https://github.com/eslint/eslint/commit/0aa0bc365a5425440c8e86c96104d0053a51b602) chore: Add PRs to triage project (#17421) (Nicholas C. Zakas) +* [`ee68d1d`](https://github.com/eslint/eslint/commit/ee68d1d9630892d99ae0d8dabe2f9f8d3b1338be) feat: `no-empty-character-class` support `v` flag (#17419) (Milos Djermanovic) +* [`853d32b`](https://github.com/eslint/eslint/commit/853d32baa8934c08b59a738470b72522e1505f6f) feat: deprecate no-return-await (#17417) (Carlos Lopez) +* [`d4f02e4`](https://github.com/eslint/eslint/commit/d4f02e4bf1b9ae4e1fc8f2bc4e4851ae3c36a127) feat: `no-control-regex` support `v` flag (#17405) (Yosuke Ota) +* [`9254a6c`](https://github.com/eslint/eslint/commit/9254a6cea845dfaf2f3f52f718cb9b071853aa09) docs: Update README (GitHub Actions Bot) +* [`2a35f3e`](https://github.com/eslint/eslint/commit/2a35f3e6ed27deafbebba48b6aec570d3abf9974) feat: `prefer-named-capture-group` support `v` flag (#17409) (Yosuke Ota) +* [`8ca8b50`](https://github.com/eslint/eslint/commit/8ca8b50b0425b3bad34a9505bc3095168e2f59d8) feat: Better error message for flat config plugins (#17399) (Nicholas C. Zakas) +* [`6d6dc51`](https://github.com/eslint/eslint/commit/6d6dc5141f535728029eef8735854a421bc08eba) docs: fix overlapping of `open in playground` button (#17403) (Tanuj Kanti) +* [`509f753`](https://github.com/eslint/eslint/commit/509f75395035822280245772e2a95732a0dde0e1) feat: `no-misleading-character-class` support `v` flag (#17406) (Yosuke Ota) +* [`3caf514`](https://github.com/eslint/eslint/commit/3caf51487decdf93a4b17765a2af2a51c337e974) feat: `no-regex-spaces` support `v` flag (#17407) (Yosuke Ota) +* [`b7fad2b`](https://github.com/eslint/eslint/commit/b7fad2b52f23667628cf209663795a721c88d0ba) feat: `prefer-regex-literals` support `v` flag (#17410) (Yosuke Ota) +* [`a6a3ad4`](https://github.com/eslint/eslint/commit/a6a3ad4ae438ea7fc3a1d97cd2555f6534b565f1) feat: `no-useless-backreference` support `v` flag (#17408) (Yosuke Ota) +* [`94954a7`](https://github.com/eslint/eslint/commit/94954a715448d5794f2892bf212fe986b43228ed) feat: `no-invalid-regexp` support `v` flag (#17404) (Yosuke Ota) +* [`7fc3a2c`](https://github.com/eslint/eslint/commit/7fc3a2ce68979a2c2a6fc779e647b3004ab6f4ac) docs: Add private class features info to no-underscore-dangle (#17386) (Matt Wilkinson) +* [`da73e58`](https://github.com/eslint/eslint/commit/da73e583e1703a420551d8fa8f7c70b56dc88dd5) docs: Migrating `eslint-env` configuration comments (#17390) (Francesco Trotta) +* [`10e9cfa`](https://github.com/eslint/eslint/commit/10e9cfa01ac043961f2c476198848f0ca5e8bbb0) Merge pull request from GHSA-qwh7-v8hg-w8rh (leo-centurion) +* [`1af6eac`](https://github.com/eslint/eslint/commit/1af6eac5727080c809e37c07dc729b44ef24483c) feat: adds option for allowing empty object patterns as parameter (#17365) (Tanuj Kanti) +* [`9803c7c`](https://github.com/eslint/eslint/commit/9803c7c04078f0672d8a480fd39cf3bbef8017e6) fix: FlatESLint#getRulesMetaForResults shouldn't throw on unknown rules (#17393) (Milos Djermanovic) +* [`80dffed`](https://github.com/eslint/eslint/commit/80dffed4c81dcc71fb72bc187aff2f87d141a6ed) docs: fix Ignoring Files section in config migration guide (#17392) (Milos Djermanovic) +* [`8a9abb7`](https://github.com/eslint/eslint/commit/8a9abb7cf424bd49d45c09345dc45ae95f29cc9d) docs: Update README (GitHub Actions Bot) +* [`cf03104`](https://github.com/eslint/eslint/commit/cf03104b278fea59ef46e09f667110f5eaaf95e3) feat: Improve config error messages (#17385) (Nicholas C. Zakas) +* [`42faa17`](https://github.com/eslint/eslint/commit/42faa17b1c93f801b14bea2840d1d528e25c7211) fix: Update no-loop-func to not overlap with no-undef (#17358) (Matt Wilkinson) +* [`7e9be4b`](https://github.com/eslint/eslint/commit/7e9be4bd7331d0e8e8e0af0b075a2f6d28d1bea3) docs: Update README (GitHub Actions Bot) +* [`0b0bbe0`](https://github.com/eslint/eslint/commit/0b0bbe07d4fb0870f3916e975b8ec6978f838077) docs: Update README (GitHub Actions Bot) + v8.45.0 - July 14, 2023 * [`68f63d7`](https://github.com/eslint/eslint/commit/68f63d76ce785fab4f42b76f1599026eea379bf7) chore: package.json update for @eslint/js release (ESLint Jenkins) From c87db63f597287b22d40c4ab1dd9d07e5760d7c9 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 28 Jul 2023 12:04:41 -0400 Subject: [PATCH 124/248] 8.46.0 --- docs/package.json | 2 +- docs/src/_data/rules.json | 2 +- docs/src/_data/rules_meta.json | 6 +++--- docs/src/use/formatters/html-formatter-example.html | 2 +- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/package.json b/docs/package.json index 3bb66bb434b1..ba2fe2472a45 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.45.0", + "version": "8.46.0", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 15fc4227d5f5..6ac78e8a1b25 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -1372,7 +1372,7 @@ }, { "name": "require-unicode-regexp", - "description": "Enforce the use of `u` flag on RegExp", + "description": "Enforce the use of `u` or `v` flag on RegExp", "recommended": false, "fixable": false, "hasSuggestions": true diff --git a/docs/src/_data/rules_meta.json b/docs/src/_data/rules_meta.json index 450c073c54dc..c7e8b97cca13 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -1578,9 +1578,9 @@ "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-return-await" }, + "fixable": null, "deprecated": true, - "replacedBy": [], - "fixable": null + "replacedBy": [] }, "no-script-url": { "type": "suggestion", @@ -2260,7 +2260,7 @@ "require-unicode-regexp": { "type": "suggestion", "docs": { - "description": "Enforce the use of `u` flag on RegExp", + "description": "Enforce the use of `u` or `v` flag on RegExp", "recommended": false, "url": "https://eslint.org/docs/latest/rules/require-unicode-regexp" }, diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index b4a942be584d..d7e362c12fcb 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

- 9 problems (5 errors, 4 warnings) - Generated on Fri Jul 14 2023 11:53:18 GMT-0400 (Eastern Daylight Time) + 9 problems (5 errors, 4 warnings) - Generated on Fri Jul 28 2023 12:04:41 GMT-0400 (Eastern Daylight Time)
diff --git a/package.json b/package.json index 1bde40211c2d..ece696efe4eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.45.0", + "version": "8.46.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From a41a8e4a7da14726d6fce71a023f12101fd52fdb Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sun, 30 Jul 2023 02:39:43 +0530 Subject: [PATCH 125/248] docs: update script names in README (#17432) --- docs/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index 33f91bd3f731..026430cc0d26 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,19 +25,19 @@ Once the script finishes building the documentation site, you can visit it at To update the links data file, run this from the root folder (not the `docs` folder): ```shell -npm run docs:update-links +npm run build:docs:update-links ``` To lint JS files, run this from the root folder (not the `docs` folder): ```shell -npm run lint:docsjs +npm run lint:docs:js ``` To autofix JS files, run this from the root folder (not the `docs` folder): ```shell -npm run fix:docsjs +npm run lint:fix:docs:js ``` ## License From 224376cd99a08394291a9584ad9c1ea1283673c6 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Tue, 1 Aug 2023 08:06:53 +0000 Subject: [PATCH 126/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e16c09bb9867..7184ed7aadc8 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord iBoysoft BairesDev GitHub Transloadit Ignition HeroCoders QuickBooks Tool hub

+

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord iBoysoft GitHub Transloadit Ignition HeroCoders QuickBooks Tool hub

## Technology Sponsors From d743ed3c06c62a639da0389ad27907b324ea1715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Tue, 1 Aug 2023 12:21:00 -0400 Subject: [PATCH 127/248] docs: add metadata for parser/processor (#17438) * docs: add metadata for parser/processor * Update docs/src/extend/custom-parsers.md Co-authored-by: Nicholas C. Zakas * review comments * rearrange `meta` object after `postprocess` --------- Co-authored-by: Nicholas C. Zakas --- docs/src/extend/custom-parsers.md | 22 ++++++++++- docs/src/extend/custom-processors.md | 6 +++ docs/src/extend/plugins.md | 56 ++++++++++++++-------------- 3 files changed, 54 insertions(+), 30 deletions(-) diff --git a/docs/src/extend/custom-parsers.md b/docs/src/extend/custom-parsers.md index a7327fb91f60..55ed726f046c 100644 --- a/docs/src/extend/custom-parsers.md +++ b/docs/src/extend/custom-parsers.md @@ -12,6 +12,8 @@ ESLint custom parsers let you extend ESLint to support linting new non-standard ## Creating a Custom Parser +### Methods in Custom Parsers + A custom parser is a JavaScript object with either a `parse` or `parseForESLint` method. The `parse` method only returns the AST, whereas `parseForESLint` also returns additional values that let the parser customize the behavior of ESLint even more. Both methods should take in the source code as the first argument, and an optional configuration object as the second argument, which is provided as [`parserOptions`](../use/configure/language-options#specifying-parser-options) in a configuration file. @@ -33,11 +35,11 @@ function parse(code, options) { module.exports = { parse }; ``` -## `parse` Return Object +### `parse` Return Object The `parse` method should simply return the [AST](#ast-specification) object. -## `parseForESLint` Return Object +### `parseForESLint` Return Object The `parseForESLint` method should return an object that contains the required property `ast` and optional properties `services`, `scopeManager`, and `visitorKeys`. @@ -48,6 +50,22 @@ The `parseForESLint` method should return an object that contains the required p * `visitorKeys` can be an object to customize AST traversal. The keys of the object are the type of AST nodes. Each value is an array of the property names which should be traversed. The default is [KEYS of `eslint-visitor-keys`](https://github.com/eslint/eslint-visitor-keys#evkkeys). * Support for `visitorKeys` was added in ESLint v4.14.0. ESLint versions that support `visitorKeys` will provide an `eslintVisitorKeys: true` property in `parserOptions`, which can be used for feature detection. +### Meta Data in Custom Parsers + +For easier debugging and more effective caching of custom parsers, it's recommended to provide a name and version in a `meta` object at the root of your custom parsers, like this: + +```js +// preferred location of name and version +module.exports = { + meta: { + name: "eslint-parser-custom", + version: "1.2.3" + } +}; +``` + +The `meta.name` property should match the npm package name for your custom parser and the `meta.version` property should match the npm package version for your custom parser. The easiest way to accomplish this is by reading this information from your `package.json`. + ## AST Specification The AST that custom parsers should create is based on [ESTree](https://github.com/estree/estree). The AST requires some additional properties about detail information of the source code. diff --git a/docs/src/extend/custom-processors.md b/docs/src/extend/custom-processors.md index a47271e2e9e6..d5a5261d9769 100644 --- a/docs/src/extend/custom-processors.md +++ b/docs/src/extend/custom-processors.md @@ -18,6 +18,10 @@ In order to create a custom processor, the object exported from your module has module.exports = { processors: { "processor-name": { + meta: { + name: "eslint-processor-name", + version: "1.2.3" + }, // takes text of the file and filename preprocess: function(text, filename) { // here, you can strip out any non-JS content @@ -121,6 +125,8 @@ By default, ESLint does not perform autofixes when a custom processor is used, e You can have both rules and custom processors in a single plugin. You can also have multiple processors in one plugin. To support multiple extensions, add each one to the `processors` element and point them to the same object. +**The `meta` object** helps ESLint cache the processor and provide more friendly debug message. The `meta.name` property should match the processor name and the `meta.version` property should match the npm package version for your processors. The easiest way to accomplish this is by reading this information from your `package.json`. + ## Specifying Processor in Config Files To use a processor, add its ID to a `processor` section in the config file. Processor ID is a concatenated string of plugin name and processor name with a slash as a separator. This can also be added to a `overrides` section of the config, to specify which processors should handle which files. diff --git a/docs/src/extend/plugins.md b/docs/src/extend/plugins.md index 82e8fd8f1170..5374a0f50946 100644 --- a/docs/src/extend/plugins.md +++ b/docs/src/extend/plugins.md @@ -18,34 +18,6 @@ Each plugin is an npm module with a name in the format of `eslint-plugin- Date: Wed, 2 Aug 2023 13:04:08 +0900 Subject: [PATCH 128/248] docs: update with "Specifying Parser Options" (#17435) * docs: update with "Specifying Parser Options" * docs : reapply lint * Update docs/src/use/configure/language-options.md * docs : reapply lint --------- Co-authored-by: Francesco Trotta --- docs/src/use/configure/language-options.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/use/configure/language-options.md b/docs/src/use/configure/language-options.md index a89224780ac3..4bcd63e3c98f 100644 --- a/docs/src/use/configure/language-options.md +++ b/docs/src/use/configure/language-options.md @@ -191,7 +191,7 @@ ESLint allows you to specify the JavaScript language options you want to support Please note that supporting JSX syntax is not the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) if you are using React. -By the same token, supporting ES6 syntax is not the same as supporting new ES6 globals (e.g., new types such as `Set`). For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 global variables, use `{ "env": { "es6": true } }`. Setting `{ "env": { "es6": true } }` enables ES6 syntax automatically, but `{ "parserOptions": { "ecmaVersion": 6 } }` does not enable ES6 globals automatically. +By the same token, supporting ES6 syntax is not the same as supporting new ES6 globals (e.g., new types such as `Set`). For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 global variables, use `{ "env": { "es6": true } }`. Setting `{ "env": { "es6": true } }` enables ES6 syntax automatically, but `{ "parserOptions": { "ecmaVersion": 6 } }` does not enable ES6 globals automatically. In summary, to support only ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`, and to support both ES6 syntax and new ES6 global variables, such as `Set` and others, use `{ "env": { "es6": true } }`. Parser options are set in your `.eslintrc.*` file with the `parserOptions` property. The available options are: From fcdc85d3a6bc14970c3349cc8d6f3a47eca172a3 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Wed, 2 Aug 2023 08:06:23 +0000 Subject: [PATCH 129/248] docs: Update README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 7184ed7aadc8..bf686b31b180 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,11 @@ Bryan Mishkin
Francesco Trotta +
+ +
+Yosuke Ota +
### Website Team From 53d750800b1c0c1f8c29393c488bb3167bb1d2a5 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Wed, 2 Aug 2023 16:52:21 +0200 Subject: [PATCH 130/248] feat: update regex for methods with `thisArg` (#17439) * feat: update regex for methods with `thisArg` * rename regex constant --- lib/rules/utils/ast-utils.js | 8 +-- tests/lib/rules/no-eval.js | 24 +++++++ tests/lib/rules/no-invalid-this.js | 112 ++++++++--------------------- 3 files changed, 56 insertions(+), 88 deletions(-) diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index 08a23c888786..bebb4d5168b3 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -26,8 +26,8 @@ const { const anyFunctionPattern = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/u; const anyLoopPattern = /^(?:DoWhile|For|ForIn|ForOf|While)Statement$/u; +const arrayMethodWithThisArgPattern = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|some)$/u; const arrayOrTypedArrayPattern = /Array$/u; -const arrayMethodPattern = /^(?:every|filter|find|findIndex|forEach|map|some)$/u; const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/u; const thisTagPattern = /^[\s*]*@this/mu; @@ -467,12 +467,12 @@ function isArrayFromMethod(node) { } /** - * Checks whether or not a node is a method which has `thisArg`. + * Checks whether or not a node is a method which expects a function as a first argument, and `thisArg` as a second argument. * @param {ASTNode} node A node to check. - * @returns {boolean} Whether or not the node is a method which has `thisArg`. + * @returns {boolean} Whether or not the node is a method which expects a function as a first argument, and `thisArg` as a second argument. */ function isMethodWhichHasThisArg(node) { - return isSpecificMemberAccess(node, null, arrayMethodPattern); + return isSpecificMemberAccess(node, null, arrayMethodWithThisArgPattern); } /** diff --git a/tests/lib/rules/no-eval.js b/tests/lib/rules/no-eval.js index 840bcb20f1de..de1e11df19ed 100644 --- a/tests/lib/rules/no-eval.js +++ b/tests/lib/rules/no-eval.js @@ -57,6 +57,11 @@ ruleTester.run("no-eval", rule, { { code: "class A { field = () => this.eval(); }", parserOptions: { ecmaVersion: 2022 } }, { code: "class A { static { this.eval(); } }", parserOptions: { ecmaVersion: 2022 } }, + // User-defined this.eval in callbacks + "array.findLast(function (x) { return this.eval.includes(x); }, { eval: ['foo', 'bar'] });", + "callbacks.findLastIndex(function (cb) { return cb(this.eval); }, this);", + "['1+1'].flatMap(function (str) { return this.eval(str); }, new Evaluator);", + // Allows indirect eval { code: "(0, eval)('foo')", options: [{ allowIndirect: true }] }, { code: "(0, window.eval)('foo')", options: [{ allowIndirect: true }], env: { browser: true } }, @@ -162,6 +167,25 @@ ruleTester.run("no-eval", rule, { code: "function foo() { 'use strict'; this.eval(); }", parserOptions: { ecmaVersion: 3 }, errors: [{ messageId: "unexpected" }] + }, + + // this.eval in callbacks (not user-defined) + { + code: "array.findLast(x => this.eval.includes(x), { eval: 'abc' });", + parserOptions: { ecmaVersion: 2023 }, + errors: [{ messageId: "unexpected" }] + }, + { + code: "callbacks.findLastIndex(function (cb) { return cb(eval); }, this);", + errors: [{ messageId: "unexpected" }] + }, + { + code: "['1+1'].flatMap(function (str) { return this.eval(str); });", + errors: [{ messageId: "unexpected" }] + }, + { + code: "['1'].reduce(function (a, b) { return this.eval(a) ? a : b; }, '0');", + errors: [{ messageId: "unexpected" }] } ] }); diff --git a/tests/lib/rules/no-invalid-this.js b/tests/lib/rules/no-invalid-this.js index dd72be499915..7e8d4776d9bc 100644 --- a/tests/lib/rules/no-invalid-this.js +++ b/tests/lib/rules/no-invalid-this.js @@ -474,103 +474,47 @@ const patterns = [ valid: [NORMAL], invalid: [USE_STRICT, IMPLIED_STRICT, MODULES] }, - { - code: "foo.every(function() { console.log(this); z(x => console.log(x, this)); });", - parserOptions: { ecmaVersion: 6 }, - errors, - valid: [NORMAL], - invalid: [USE_STRICT, IMPLIED_STRICT, MODULES] - }, - { - code: "foo.filter(function() { console.log(this); z(x => console.log(x, this)); });", - parserOptions: { ecmaVersion: 6 }, - errors, - valid: [NORMAL], - invalid: [USE_STRICT, IMPLIED_STRICT, MODULES] - }, - { - code: "foo.find(function() { console.log(this); z(x => console.log(x, this)); });", - parserOptions: { ecmaVersion: 6 }, - errors, - valid: [NORMAL], - invalid: [USE_STRICT, IMPLIED_STRICT, MODULES] - }, - { - code: "foo.findIndex(function() { console.log(this); z(x => console.log(x, this)); });", - parserOptions: { ecmaVersion: 6 }, - errors, - valid: [NORMAL], - invalid: [USE_STRICT, IMPLIED_STRICT, MODULES] - }, - { - code: "foo.forEach(function() { console.log(this); z(x => console.log(x, this)); });", - parserOptions: { ecmaVersion: 6 }, - errors, - valid: [NORMAL], - invalid: [USE_STRICT, IMPLIED_STRICT, MODULES] - }, - { - code: "foo.map(function() { console.log(this); z(x => console.log(x, this)); });", - parserOptions: { ecmaVersion: 6 }, - errors, - valid: [NORMAL], - invalid: [USE_STRICT, IMPLIED_STRICT, MODULES] - }, - { - code: "foo.some(function() { console.log(this); z(x => console.log(x, this)); });", + ...[ + "every", + "filter", + "find", + "findIndex", + "findLast", + "findLastIndex", + "flatMap", + "forEach", + "map", + "some" + ].map(methodName => ({ + code: `foo.${methodName}(function() { console.log(this); z(x => console.log(x, this)); });`, parserOptions: { ecmaVersion: 6 }, errors, valid: [NORMAL], invalid: [USE_STRICT, IMPLIED_STRICT, MODULES] - }, + })), { code: "Array.from([], function() { console.log(this); z(x => console.log(x, this)); }, obj);", parserOptions: { ecmaVersion: 6 }, valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], invalid: [] }, - { - code: "foo.every(function() { console.log(this); z(x => console.log(x, this)); }, obj);", - parserOptions: { ecmaVersion: 6 }, - valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], - invalid: [] - }, - { - code: "foo.filter(function() { console.log(this); z(x => console.log(x, this)); }, obj);", - parserOptions: { ecmaVersion: 6 }, - valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], - invalid: [] - }, - { - code: "foo.find(function() { console.log(this); z(x => console.log(x, this)); }, obj);", + ...[ + "every", + "filter", + "find", + "findIndex", + "findLast", + "findLastIndex", + "flatMap", + "forEach", + "map", + "some" + ].map(methodName => ({ + code: `foo.${methodName}(function() { console.log(this); z(x => console.log(x, this)); }, obj);`, parserOptions: { ecmaVersion: 6 }, valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], invalid: [] - }, - { - code: "foo.findIndex(function() { console.log(this); z(x => console.log(x, this)); }, obj);", - parserOptions: { ecmaVersion: 6 }, - valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], - invalid: [] - }, - { - code: "foo.forEach(function() { console.log(this); z(x => console.log(x, this)); }, obj);", - parserOptions: { ecmaVersion: 6 }, - valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], - invalid: [] - }, - { - code: "foo.map(function() { console.log(this); z(x => console.log(x, this)); }, obj);", - parserOptions: { ecmaVersion: 6 }, - valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], - invalid: [] - }, - { - code: "foo.some(function() { console.log(this); z(x => console.log(x, this)); }, obj);", - parserOptions: { ecmaVersion: 6 }, - valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], - invalid: [] - }, + })), { code: "foo.forEach(function() { console.log(this); z(x => console.log(x, this)); }, null);", parserOptions: { ecmaVersion: 6 }, From 47a08597966651975126dd6726939cd34f13b80e Mon Sep 17 00:00:00 2001 From: SUZUKI Sosuke Date: Thu, 3 Aug 2023 00:03:48 +0900 Subject: [PATCH 131/248] docs: update `require-unicode-regexp.md` as following up #17402 (#17441) * docs: add `further_reading` links * docs: mention about `v` * Update docs/src/rules/require-unicode-regexp.md --------- Co-authored-by: Nicholas C. Zakas --- docs/src/_data/further_reading_links.json | 14 ++++++++++++++ docs/src/rules/require-unicode-regexp.md | 9 +++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/src/_data/further_reading_links.json b/docs/src/_data/further_reading_links.json index e66ef76ce696..045bd4faf628 100644 --- a/docs/src/_data/further_reading_links.json +++ b/docs/src/_data/further_reading_links.json @@ -726,5 +726,19 @@ "logo": "https://v8.dev/favicon.ico", "title": "Faster async functions and promises ยท V8", "description": "Faster and easier-to-debug async functions and promises are coming to V8 v7.2 / Chrome 72." + }, + "https://github.com/tc39/proposal-regexp-v-flag": { + "domain": "github.com", + "url": "https://github.com/tc39/proposal-regexp-v-flag", + "logo": "https://github.com/fluidicon.png", + "title": "GitHub - tc39/proposal-regexp-v-flag: UTS18 set notation in regular expressions", + "description": "UTS18 set notation in regular expressions. Contribute to tc39/proposal-regexp-v-flag development by creating an account on GitHub." + }, + "https://v8.dev/features/regexp-v-flag": { + "domain": "v8.dev", + "url": "https://v8.dev/features/regexp-v-flag", + "logo": "https://v8.dev/favicon.ico", + "title": "RegExp v flag with set notation and properties of strings ยท V8", + "description": "The new RegExp `v` flag enables `unicodeSets` mode, unlocking support for extended character classes, including Unicode properties of strings, set notation, and improved case-insensitive matching." } } \ No newline at end of file diff --git a/docs/src/rules/require-unicode-regexp.md b/docs/src/rules/require-unicode-regexp.md index 969818e2c0d9..d0069de15963 100644 --- a/docs/src/rules/require-unicode-regexp.md +++ b/docs/src/rules/require-unicode-regexp.md @@ -1,6 +1,9 @@ --- title: require-unicode-regexp rule_type: suggestion +further_reading: +- https://github.com/tc39/proposal-regexp-v-flag +- https://v8.dev/features/regexp-v-flag --- @@ -49,13 +52,11 @@ The RegExp `v` flag, introduced in ECMAScript 2024, is a superset of the `u` fla re.test('\u2028'); // โ†’ false ``` -Please see and for more details. - Therefore, the `u` and `v` flags let us work better with regular expressions. ## Rule Details -This rule aims to enforce the use of `u` flag on regular expressions. +This rule aims to enforce the use of `u` or `v` flag on regular expressions. Examples of **incorrect** code for this rule: @@ -99,4 +100,4 @@ function f(flags) { ## When Not To Use It -If you don't want to notify regular expressions with no `u` flag, then it's safe to disable this rule. +If you don't want to warn on regular expressions without either a `u` or a `v` flag, then it's safe to disable this rule. From 6b2410f911dd2e3d915c879041c6e257d41a2f4e Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 2 Aug 2023 11:53:19 -0400 Subject: [PATCH 132/248] chore: Update add-to-triage.yml (#17444) --- .github/workflows/add-to-triage.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/add-to-triage.yml b/.github/workflows/add-to-triage.yml index 64ef0397e17c..83297dcab333 100644 --- a/.github/workflows/add-to-triage.yml +++ b/.github/workflows/add-to-triage.yml @@ -4,9 +4,6 @@ on: issues: types: - opened - pull_request: - types: - - opened jobs: add-to-project: From a1635d6198a8baf6571b3351e098e5ac960be887 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 3 Aug 2023 08:06:22 +0000 Subject: [PATCH 133/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf686b31b180..0ce3a4a5a832 100644 --- a/README.md +++ b/README.md @@ -289,7 +289,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord iBoysoft GitHub Transloadit Ignition HeroCoders QuickBooks Tool hub

+

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord GitHub Transloadit Ignition HeroCoders QuickBooks Tool hub

## Technology Sponsors From a766a48030d4359db76523d5b413d6332130e485 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Fri, 4 Aug 2023 10:50:11 -0400 Subject: [PATCH 134/248] docs: document lack of config file names (#17442) * docs: document lack of config file names * Update configuration-files-new.md * Update docs/src/use/configure/configuration-files-new.md Co-authored-by: Nicholas C. Zakas --------- Co-authored-by: Nicholas C. Zakas --- docs/src/use/configure/configuration-files-new.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/src/use/configure/configuration-files-new.md b/docs/src/use/configure/configuration-files-new.md index 4c2bd1fa4df1..7903854f0622 100644 --- a/docs/src/use/configure/configuration-files-new.md +++ b/docs/src/use/configure/configuration-files-new.md @@ -58,6 +58,10 @@ module.exports = (async () => { })(); ``` +::: warning +ESLint only automatically looks for a config file named `eslint.config.js` and does not look for `eslint.config.cjs` or `eslint.config.mjs`. If you'd like to specify a different config filename than the default, use the `--config` command line option. +::: + ## Configuration Objects Each configuration object contains all of the information ESLint needs to execute on a set of files. Each configuration object is made up of these properties: @@ -667,7 +671,7 @@ When ESLint is run on the command line, it first checks the current working dire You can prevent this search for `eslint.config.js` by setting the `ESLINT_USE_FLAT_CONFIG` environment variable to `true` and using the `-c` or `--config` option on the command line to specify an alternate configuration file, such as: ```shell -ESLINT_USE_FLAT_CONFIG=true npx eslint -c some-other-file.js **/*.js +ESLINT_USE_FLAT_CONFIG=true npx eslint --config some-other-file.js **/*.js ``` In this case, ESLint does not search for `eslint.config.js` and instead uses `some-other-file.js`. From b066640b7040ec30f740dcc803511244fe19473b Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Mon, 7 Aug 2023 20:35:13 +0530 Subject: [PATCH 135/248] chore: standardize npm script names (#17431) * chore: standardize npm script names * chore: use `build:minify-images` instead of `minify:images` --- docs/package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/package.json b/docs/package.json index ba2fe2472a45..85cbda38e1d2 100644 --- a/docs/package.json +++ b/docs/package.json @@ -9,18 +9,18 @@ "license": "MIT", "files": [], "scripts": { - "images": "imagemin '_site/assets/images' --out-dir='_site/assets/images'", - "watch:postcss": "postcss src/assets/css -d src/assets/css --watch --poll", - "watch:sass": "sass --watch --poll src/assets/scss:src/assets/css --no-source-map", - "watch:eleventy": "eleventy --serve --port=2023", + "build": "npm-run-all build:sass build:postcss build:website build:minify-images", "build:postcss": "postcss src/assets/css -d src/assets/css", + "build:postcss:watch": "postcss src/assets/css -d src/assets/css --watch --poll", "build:sass": "sass src/assets/scss:src/assets/css --no-source-map", - "build:eleventy": "npx @11ty/eleventy", - "start": "npm-run-all build:sass build:postcss --parallel watch:*", - "build": "npm-run-all build:sass build:postcss build:eleventy images", - "lint:scss": "stylelint \"**/*.{scss,html}\"", + "build:sass:watch": "sass --watch --poll src/assets/scss:src/assets/css --no-source-map", + "build:website": "npx @11ty/eleventy", + "build:website:watch": "eleventy --serve --port=2023", "lint:links": "cross-env NODE_OPTIONS=--max-old-space-size=4096 node tools/validate-links.js", - "lint:fix:scss": "npm run lint:scss -- --fix" + "lint:scss": "stylelint \"**/*.{scss,html}\"", + "lint:fix:scss": "npm run lint:scss -- --fix", + "build:minify-images": "imagemin '_site/assets/images' --out-dir='_site/assets/images'", + "start": "npm-run-all build:sass build:postcss --parallel *:*:watch" }, "devDependencies": { "@11ty/eleventy": "^1.0.1", From 757bfe1c35b5ddab7042d388f8d21e834875fff5 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 8 Aug 2023 13:11:34 -0400 Subject: [PATCH 136/248] chore: Remove add-to-triage (#17450) --- .github/workflows/add-to-triage.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .github/workflows/add-to-triage.yml diff --git a/.github/workflows/add-to-triage.yml b/.github/workflows/add-to-triage.yml deleted file mode 100644 index 83297dcab333..000000000000 --- a/.github/workflows/add-to-triage.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Add to Triage - -on: - issues: - types: - - opened - -jobs: - add-to-project: - name: Add issue to project - runs-on: ubuntu-latest - steps: - - uses: actions/add-to-project@v0.5.0 - with: - project-url: https://github.com/orgs/eslint/projects/3 - github-token: ${{ secrets.PROJECT_BOT_TOKEN }} - labeled: "triage:no" - label-operator: NOT From 631648ee0b51a8951ce576ccd4430e09c9c8bcae Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Wed, 9 Aug 2023 17:13:04 +0200 Subject: [PATCH 137/248] fix: do not report on shadowed constructors in `no-new-wrappers` (#17447) * fix: do not report on shadowed constructors in `no-new-wrappers` * unit tests for undefined constructors --- lib/rules/no-new-wrappers.js | 26 +++++++++++----- tests/lib/rules/no-new-wrappers.js | 49 +++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/lib/rules/no-new-wrappers.js b/lib/rules/no-new-wrappers.js index 9a12e1a3b5d2..5050a98a044a 100644 --- a/lib/rules/no-new-wrappers.js +++ b/lib/rules/no-new-wrappers.js @@ -5,6 +5,12 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { getVariableByName } = require("./utils/ast-utils"); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -28,18 +34,24 @@ module.exports = { }, create(context) { + const { sourceCode } = context; return { NewExpression(node) { const wrapperObjects = ["String", "Number", "Boolean"]; - - if (wrapperObjects.includes(node.callee.name)) { - context.report({ - node, - messageId: "noConstructor", - data: { fn: node.callee.name } - }); + const { name } = node.callee; + + if (wrapperObjects.includes(name)) { + const variable = getVariableByName(sourceCode.getScope(node), name); + + if (variable && variable.identifiers.length === 0) { + context.report({ + node, + messageId: "noConstructor", + data: { fn: name } + }); + } } } }; diff --git a/tests/lib/rules/no-new-wrappers.js b/tests/lib/rules/no-new-wrappers.js index 57dba4e12851..0b5b686aef15 100644 --- a/tests/lib/rules/no-new-wrappers.js +++ b/tests/lib/rules/no-new-wrappers.js @@ -21,7 +21,36 @@ const ruleTester = new RuleTester(); ruleTester.run("no-new-wrappers", rule, { valid: [ "var a = new Object();", - "var a = String('test'), b = String.fromCharCode(32);" + "var a = String('test'), b = String.fromCharCode(32);", + ` + function test(Number) { + return new Number; + } + `, + { + code: ` + import String from "./string"; + const str = new String(42); + `, + parserOptions: { ecmaVersion: 6, sourceType: "module" } + }, + ` + if (foo) { + result = new Boolean(bar); + } else { + var Boolean = CustomBoolean; + } + `, + { + code: "new String()", + globals: { + String: "off" + } + }, + ` + /* global Boolean:off */ + assert(new Boolean); + ` ], invalid: [ { @@ -53,6 +82,24 @@ ruleTester.run("no-new-wrappers", rule, { }, type: "NewExpression" }] + }, + { + code: ` + const a = new String('bar'); + { + const String = CustomString; + const b = new String('foo'); + } + `, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "noConstructor", + data: { + fn: "String" + }, + type: "NewExpression", + line: 2 + }] } ] }); From 0e4576012ab938b880e6f27641bff55fb4313d20 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 11 Aug 2023 10:56:19 -0400 Subject: [PATCH 138/248] 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 e5f79c7cc457..1847404d7c99 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "8.46.0", + "version": "8.47.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From bf69aa6408f5403a88d8c9b71b0e58232b1ea833 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 11 Aug 2023 11:14:20 -0400 Subject: [PATCH 139/248] chore: Update dependencies (#17456) --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ece696efe4eb..ccab1e9e803a 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,8 @@ "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.1", - "@eslint/js": "^8.46.0", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "^8.47.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -74,7 +74,7 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.2", + "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", From 928cecc754da77e1e52304b0a71b8b0a3a23fd7a Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 11 Aug 2023 11:18:14 -0400 Subject: [PATCH 140/248] Build: changelog update for 8.47.0 --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 001632b74bc5..b749600969e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +v8.47.0 - August 11, 2023 + +* [`bf69aa6`](https://github.com/eslint/eslint/commit/bf69aa6408f5403a88d8c9b71b0e58232b1ea833) chore: Update dependencies (#17456) (Nicholas C. Zakas) +* [`0e45760`](https://github.com/eslint/eslint/commit/0e4576012ab938b880e6f27641bff55fb4313d20) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`631648e`](https://github.com/eslint/eslint/commit/631648ee0b51a8951ce576ccd4430e09c9c8bcae) fix: do not report on shadowed constructors in `no-new-wrappers` (#17447) (Francesco Trotta) +* [`757bfe1`](https://github.com/eslint/eslint/commit/757bfe1c35b5ddab7042d388f8d21e834875fff5) chore: Remove add-to-triage (#17450) (Nicholas C. Zakas) +* [`b066640`](https://github.com/eslint/eslint/commit/b066640b7040ec30f740dcc803511244fe19473b) chore: standardize npm script names (#17431) (Nitin Kumar) +* [`a766a48`](https://github.com/eslint/eslint/commit/a766a48030d4359db76523d5b413d6332130e485) docs: document lack of config file names (#17442) (James) +* [`a1635d6`](https://github.com/eslint/eslint/commit/a1635d6198a8baf6571b3351e098e5ac960be887) docs: Update README (GitHub Actions Bot) +* [`6b2410f`](https://github.com/eslint/eslint/commit/6b2410f911dd2e3d915c879041c6e257d41a2f4e) chore: Update add-to-triage.yml (#17444) (Nicholas C. Zakas) +* [`47a0859`](https://github.com/eslint/eslint/commit/47a08597966651975126dd6726939cd34f13b80e) docs: update `require-unicode-regexp.md` as following up #17402 (#17441) (SUZUKI Sosuke) +* [`53d7508`](https://github.com/eslint/eslint/commit/53d750800b1c0c1f8c29393c488bb3167bb1d2a5) feat: update regex for methods with `thisArg` (#17439) (Francesco Trotta) +* [`fcdc85d`](https://github.com/eslint/eslint/commit/fcdc85d3a6bc14970c3349cc8d6f3a47eca172a3) docs: Update README (GitHub Actions Bot) +* [`2a92b6c`](https://github.com/eslint/eslint/commit/2a92b6cc9520a27255520369206556e9841a3af8) docs: update with "Specifying Parser Options" (#17435) (Cheol-Won) +* [`d743ed3`](https://github.com/eslint/eslint/commit/d743ed3c06c62a639da0389ad27907b324ea1715) docs: add metadata for parser/processor (#17438) (Huรกng Jรนnliร ng) +* [`224376c`](https://github.com/eslint/eslint/commit/224376cd99a08394291a9584ad9c1ea1283673c6) docs: Update README (GitHub Actions Bot) +* [`a41a8e4`](https://github.com/eslint/eslint/commit/a41a8e4a7da14726d6fce71a023f12101fd52fdb) docs: update script names in README (#17432) (Nitin Kumar) + v8.46.0 - July 28, 2023 * [`d1eb7e4`](https://github.com/eslint/eslint/commit/d1eb7e46e954c64af8d7d13d087b3a18f43e6d72) chore: Update ecosystem dependencies (#17427) (Nicholas C. Zakas) From 98b155fb9d8ae5afa8c544453133d0c5a12c12ba Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 11 Aug 2023 11:18:14 -0400 Subject: [PATCH 141/248] 8.47.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 85cbda38e1d2..981b67d4e29c 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.46.0", + "version": "8.47.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 d7e362c12fcb..4f87732fd189 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

- 9 problems (5 errors, 4 warnings) - Generated on Fri Jul 28 2023 12:04:41 GMT-0400 (Eastern Daylight Time) + 9 problems (5 errors, 4 warnings) - Generated on Fri Aug 11 2023 11:18:15 GMT-0400 (Eastern Daylight Time)
diff --git a/package.json b/package.json index ccab1e9e803a..661e8fcaf4d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.46.0", + "version": "8.47.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 9e9edf93ecfa0658e8b79e71bc98530ade150081 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Mon, 14 Aug 2023 14:18:29 +0530 Subject: [PATCH 142/248] docs: update documentation URL in error message (#17465) --- messages/eslintrc-incompat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/eslintrc-incompat.js b/messages/eslintrc-incompat.js index deffca57c595..ee77cb2328ee 100644 --- a/messages/eslintrc-incompat.js +++ b/messages/eslintrc-incompat.js @@ -19,7 +19,7 @@ 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: -https://eslint.org/docs/latest/use/configure/migration-guide#predefined-configs +https://eslint.org/docs/latest/use/configure/migration-guide#predefined-and-shareable-configs `, globals: ` From ee2f718188d32e9888b1932fe6b9bd2a62c529a4 Mon Sep 17 00:00:00 2001 From: nopeless <38830903+nopeless@users.noreply.github.com> Date: Sun, 20 Aug 2023 04:59:08 -0500 Subject: [PATCH 143/248] feat: Allow `void` in rule `no-promise-executor-return` (#17282) * feat: Allow `void` in `no-promise-executor-return` (#17278) * feat: Autofix and suggestions for `no-promise-executor-return` * fix: update behavior for `no-promise-executor-return` * fix: update suggestion behavior * docs: update to match code * docs: misc fixes * fix: refactors * fix: parentheses issues * Update docs/src/rules/no-promise-executor-return.md Co-authored-by: Milos Djermanovic * Update docs/src/rules/no-promise-executor-return.md Co-authored-by: Milos Djermanovic * Update lib/rules/no-promise-executor-return.js Co-authored-by: Milos Djermanovic * fix: adjacent token issue * fix comments --------- Co-authored-by: Milos Djermanovic --- docs/src/rules/no-promise-executor-return.md | 47 +++ lib/rules/no-promise-executor-return.js | 170 ++++++++- tests/lib/rules/no-promise-executor-return.js | 351 ++++++++++++++++-- 3 files changed, 518 insertions(+), 50 deletions(-) diff --git a/docs/src/rules/no-promise-executor-return.md b/docs/src/rules/no-promise-executor-return.md index 28891624822b..d82e44734771 100644 --- a/docs/src/rules/no-promise-executor-return.md +++ b/docs/src/rules/no-promise-executor-return.md @@ -63,6 +63,8 @@ new Promise((resolve, reject) => getSomething((err, data) => { new Promise(() => { return 1; }); + +new Promise(r => r(1)); ``` ::: @@ -74,6 +76,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-promise-executor-return: "error"*/ +// Turn return inline into two lines new Promise((resolve, reject) => { if (someCondition) { resolve(defaultResult); @@ -88,6 +91,7 @@ new Promise((resolve, reject) => { }); }); +// Add curly braces new Promise((resolve, reject) => { getSomething((err, data) => { if (err) { @@ -98,7 +102,50 @@ new Promise((resolve, reject) => { }); }); +new Promise(r => { r(1) }); +// or just use Promise.resolve Promise.resolve(1); ``` ::: + +## Options + +This rule takes one option, an object, with the following properties: + +* `allowVoid`: If set to `true` (`false` by default), this rule will allow returning void values. + +### allowVoid + +Examples of **correct** code for this rule with the `{ "allowVoid": true }` option: + +::: correct + +```js +/*eslint no-promise-executor-return: ["error", { allowVoid: true }]*/ + +new Promise((resolve, reject) => { + if (someCondition) { + return void resolve(defaultResult); + } + getSomething((err, result) => { + if (err) { + reject(err); + } else { + resolve(result); + } + }); +}); + +new Promise((resolve, reject) => void getSomething((err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } +})); + +new Promise(r => void r(1)); +``` + +::: diff --git a/lib/rules/no-promise-executor-return.js b/lib/rules/no-promise-executor-return.js index d46a730e4746..e6ed7a22efc5 100644 --- a/lib/rules/no-promise-executor-return.js +++ b/lib/rules/no-promise-executor-return.js @@ -10,6 +10,7 @@ //------------------------------------------------------------------------------ const { findVariable } = require("@eslint-community/eslint-utils"); +const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ // Helpers @@ -59,6 +60,78 @@ function isPromiseExecutor(node, scope) { isGlobalReference(parent.callee, getOuterScope(scope)); } +/** + * Checks if the given node is a void expression. + * @param {ASTNode} node The node to check. + * @returns {boolean} - `true` if the node is a void expression + */ +function expressionIsVoid(node) { + return node.type === "UnaryExpression" && node.operator === "void"; +} + +/** + * Fixes the linting error by prepending "void " to the given node + * @param {Object} sourceCode context given by context.sourceCode + * @param {ASTNode} node The node to fix. + * @param {Object} fixer The fixer object provided by ESLint. + * @returns {Array} - An array of fix objects to apply to the node. + */ +function voidPrependFixer(sourceCode, node, fixer) { + + const requiresParens = + + // prepending `void ` will fail if the node has a lower precedence than void + astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression", operator: "void" }) && + + // check if there are parentheses around the node to avoid redundant parentheses + !astUtils.isParenthesised(sourceCode, node); + + // avoid parentheses issues + const returnOrArrowToken = sourceCode.getTokenBefore( + node, + node.parent.type === "ArrowFunctionExpression" + ? astUtils.isArrowToken + + // isReturnToken + : token => token.type === "Keyword" && token.value === "return" + ); + + const firstToken = sourceCode.getTokenAfter(returnOrArrowToken); + + const prependSpace = + + // is return token, as => allows void to be adjacent + returnOrArrowToken.value === "return" && + + // If two tokens (return and "(") are adjacent + returnOrArrowToken.range[1] === firstToken.range[0]; + + return [ + fixer.insertTextBefore(firstToken, `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`), + fixer.insertTextAfter(node, requiresParens ? ")" : "") + ]; +} + +/** + * Fixes the linting error by `wrapping {}` around the given node's body. + * @param {Object} sourceCode context given by context.sourceCode + * @param {ASTNode} node The node to fix. + * @param {Object} fixer The fixer object provided by ESLint. + * @returns {Array} - An array of fix objects to apply to the node. + */ +function curlyWrapFixer(sourceCode, node, fixer) { + + // https://github.com/eslint/eslint/pull/17282#issuecomment-1592795923 + const arrowToken = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken); + const firstToken = sourceCode.getTokenAfter(arrowToken); + const lastToken = sourceCode.getLastToken(node); + + return [ + fixer.insertTextBefore(firstToken, "{"), + fixer.insertTextAfter(lastToken, "}") + ]; +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -74,10 +147,27 @@ module.exports = { url: "https://eslint.org/docs/latest/rules/no-promise-executor-return" }, - schema: [], + hasSuggestions: true, + + schema: [{ + type: "object", + properties: { + allowVoid: { + type: "boolean", + default: false + } + }, + additionalProperties: false + }], messages: { - returnsValue: "Return values from promise executor functions cannot be read." + returnsValue: "Return values from promise executor functions cannot be read.", + + // arrow and function suggestions + prependVoid: "Prepend `void` to the expression.", + + // only arrow suggestions + wrapBraces: "Wrap the expression in `{}`." } }, @@ -85,26 +175,52 @@ module.exports = { let funcInfo = null; const sourceCode = context.sourceCode; - - /** - * Reports the given node. - * @param {ASTNode} node Node to report. - * @returns {void} - */ - function report(node) { - context.report({ node, messageId: "returnsValue" }); - } + const { + allowVoid = false + } = context.options[0] || {}; return { onCodePathStart(_, node) { funcInfo = { upper: funcInfo, - shouldCheck: functionTypesToCheck.has(node.type) && isPromiseExecutor(node, sourceCode.getScope(node)) + shouldCheck: + functionTypesToCheck.has(node.type) && + isPromiseExecutor(node, sourceCode.getScope(node)) }; - if (funcInfo.shouldCheck && node.type === "ArrowFunctionExpression" && node.expression) { - report(node.body); + if (// Is a Promise executor + funcInfo.shouldCheck && + node.type === "ArrowFunctionExpression" && + node.expression && + + // Except void + !(allowVoid && expressionIsVoid(node.body)) + ) { + const suggest = []; + + // prevent useless refactors + if (allowVoid) { + suggest.push({ + messageId: "prependVoid", + fix(fixer) { + return voidPrependFixer(sourceCode, node.body, fixer); + } + }); + } + + suggest.push({ + messageId: "wrapBraces", + fix(fixer) { + return curlyWrapFixer(sourceCode, node, fixer); + } + }); + + context.report({ + node: node.body, + messageId: "returnsValue", + suggest + }); } }, @@ -113,9 +229,31 @@ module.exports = { }, ReturnStatement(node) { - if (funcInfo.shouldCheck && node.argument) { - report(node); + if (!(funcInfo.shouldCheck && node.argument)) { + return; } + + // node is `return ` + if (!allowVoid) { + context.report({ node, messageId: "returnsValue" }); + return; + } + + if (expressionIsVoid(node.argument)) { + return; + } + + // allowVoid && !expressionIsVoid + context.report({ + node, + messageId: "returnsValue", + suggest: [{ + messageId: "prependVoid", + fix(fixer) { + return voidPrependFixer(sourceCode, node.argument, fixer); + } + }] + }); } }; } diff --git a/tests/lib/rules/no-promise-executor-return.js b/tests/lib/rules/no-promise-executor-return.js index a24629b6a441..4054edc72441 100644 --- a/tests/lib/rules/no-promise-executor-return.js +++ b/tests/lib/rules/no-promise-executor-return.js @@ -22,7 +22,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); * @param {string} [type="ReturnStatement"] Reported node type. * @returns {Object} The error object. */ -function error(column, type = "ReturnStatement") { +function eReturnsValue(column, type = "ReturnStatement") { const errorObject = { messageId: "returnsValue", type @@ -35,6 +35,26 @@ function error(column, type = "ReturnStatement") { return errorObject; } + +/** + * Creates invalid object + * @param {Object} [properties] suggestion properties + * @param {string} [properties.code] code + * @param {number} [properties.options] rule options + * @param {string[]} [fixes] Code suggestions + * @returns {Object} The invalid object. + */ +function suggestion(properties, fixes = []) { + return { + ...properties, + errors: [{ + suggestions: fixes.map(fix => ({ + output: fix + })) + }] + }; +} + //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ @@ -149,7 +169,37 @@ ruleTester.run("no-promise-executor-return", rule, { { code: "new Promise(function (resolve, reject) {}); return 1;", env: { node: true } - } + }, + + /* + * allowVoid: true + * `=> void` and `return void` are allowed + */ + { + code: "new Promise((r) => void cbf(r));", + options: [{ + allowVoid: true + }] + }, + { + code: "new Promise(r => void 0)", + options: [{ + allowVoid: true + }] + }, + { + code: "new Promise(r => { return void 0 })", + options: [{ + allowVoid: true + }] + }, + { + code: "new Promise(r => { if (foo) { return void 0 } return void 0 })", + options: [{ + allowVoid: true + }] + }, + "new Promise(r => {0})" ], invalid: [ @@ -161,143 +211,376 @@ ruleTester.run("no-promise-executor-return", rule, { }, { code: "new Promise((resolve, reject) => resolve(1))", - errors: [{ message: "Return values from promise executor functions cannot be read.", type: "CallExpression", column: 34, endColumn: 44 }] + options: [{ + allowVoid: true + }], + errors: [{ + message: "Return values from promise executor functions cannot be read.", + type: "CallExpression", + column: 34, + endColumn: 44, + suggestions: [ + { output: "new Promise((resolve, reject) => void resolve(1))" }, + { output: "new Promise((resolve, reject) => {resolve(1)})" } + ] + }] + }, + { + code: "new Promise((resolve, reject) => { return 1 })", + options: [{ + allowVoid: true + }], + errors: [{ + message: "Return values from promise executor functions cannot be read.", + type: "ReturnStatement", + column: 36, + endColumn: 44, + suggestions: [ + { output: "new Promise((resolve, reject) => { return void 1 })" } + ] + }] }, + // suggestions arrow function expression + suggestion({ + code: "new Promise(r => 1)", + options: [{ + allowVoid: true + }] + }, [ + "new Promise(r => void 1)", + "new Promise(r => {1})" + ]), + suggestion({ + code: "new Promise(r => 1 ? 2 : 3)", + options: [{ + allowVoid: true + }] + }, [ + "new Promise(r => void (1 ? 2 : 3))", + "new Promise(r => {1 ? 2 : 3})" + ]), + suggestion({ + code: "new Promise(r => (1 ? 2 : 3))", + options: [{ + allowVoid: true + }] + }, [ + "new Promise(r => void (1 ? 2 : 3))", + "new Promise(r => {(1 ? 2 : 3)})" + ]), + suggestion({ + code: + "new Promise(r => (1))", + options: [{ + allowVoid: true + }] + }, [ + "new Promise(r => void (1))", + "new Promise(r => {(1)})" + ]), + suggestion({ + code: + "new Promise(r => () => {})", + options: [{ + allowVoid: true + }] + }, [ + "new Promise(r => void (() => {}))", + "new Promise(r => {() => {}})" + ]), + + // primitives + suggestion({ + code: + "new Promise(r => null)", + options: [{ + allowVoid: true + }] + }, [ + "new Promise(r => void null)", + "new Promise(r => {null})" + ]), + suggestion({ + code: + "new Promise(r => null)", + options: [{ + allowVoid: false + }] + }, [ + "new Promise(r => {null})" + ]), + + // inline comments + suggestion({ + code: + "new Promise(r => /*hi*/ ~0)", + options: [{ + allowVoid: true + }] + }, [ + "new Promise(r => /*hi*/ void ~0)", + "new Promise(r => /*hi*/ {~0})" + ]), + suggestion({ + code: + "new Promise(r => /*hi*/ ~0)", + options: [{ + allowVoid: false + }] + }, [ + "new Promise(r => /*hi*/ {~0})" + ]), + + // suggestions function + suggestion({ + code: + "new Promise(r => { return 0 })", + options: [{ + allowVoid: true + }] + }, [ + "new Promise(r => { return void 0 })" + ]), + suggestion({ + code: + "new Promise(r => { return 0 })", + options: [{ + allowVoid: false + }] + }), + + // multiple returns + suggestion({ + code: + "new Promise(r => { if (foo) { return void 0 } return 0 })", + options: [{ + allowVoid: true + }] + }, [ + "new Promise(r => { if (foo) { return void 0 } return void 0 })" + ]), + + // return assignment + suggestion({ + code: "new Promise(resolve => { return (foo = resolve(1)); })", + options: [{ + allowVoid: true + }] + }, [ + "new Promise(resolve => { return void (foo = resolve(1)); })" + ]), + suggestion({ + code: "new Promise(resolve => r = resolve)", + options: [{ + allowVoid: true + }] + }, [ + "new Promise(resolve => void (r = resolve))", + "new Promise(resolve => {r = resolve})" + ]), + + // return (range check) + suggestion({ + code: + "new Promise(r => { return(1) })", + options: [{ + allowVoid: true + }] + }, [ + "new Promise(r => { return void (1) })" + ]), + suggestion({ + code: + "new Promise(r =>1)", + options: [{ + allowVoid: true + }] + }, [ + "new Promise(r =>void 1)", + "new Promise(r =>{1})" + ]), + + // snapshot + suggestion({ + code: + "new Promise(r => ((1)))", + options: [{ + allowVoid: true + }] + }, [ + "new Promise(r => void ((1)))", + "new Promise(r => {((1))})" + ]), + // other basic tests { code: "new Promise(function foo(resolve, reject) { return 1; })", - errors: [error()] + errors: [eReturnsValue()] }, { code: "new Promise((resolve, reject) => { return 1; })", - errors: [error()] + errors: [eReturnsValue()] }, // any returned value { code: "new Promise(function (resolve, reject) { return undefined; })", - errors: [error()] + errors: [eReturnsValue()] }, { code: "new Promise((resolve, reject) => { return null; })", - errors: [error()] + errors: [eReturnsValue()] }, { code: "new Promise(function (resolve, reject) { return false; })", - errors: [error()] + errors: [eReturnsValue()] }, { code: "new Promise((resolve, reject) => resolve)", - errors: [error(34, "Identifier")] + errors: [eReturnsValue(34, "Identifier")] }, { code: "new Promise((resolve, reject) => null)", - errors: [error(34, "Literal")] + errors: [eReturnsValue(34, "Literal")] }, { code: "new Promise(function (resolve, reject) { return resolve(foo); })", - errors: [error()] + errors: [eReturnsValue()] }, { code: "new Promise((resolve, reject) => { return reject(foo); })", - errors: [error()] + errors: [eReturnsValue()] }, { code: "new Promise((resolve, reject) => x + y)", - errors: [error(34, "BinaryExpression")] + errors: [eReturnsValue(34, "BinaryExpression")] }, { code: "new Promise((resolve, reject) => { return Promise.resolve(42); })", - errors: [error()] + errors: [eReturnsValue()] }, // any return statement location { code: "new Promise(function (resolve, reject) { if (foo) { return 1; } })", - errors: [error()] + errors: [eReturnsValue()] }, { code: "new Promise((resolve, reject) => { try { return 1; } catch(e) {} })", - errors: [error()] + errors: [eReturnsValue()] }, { code: "new Promise(function (resolve, reject) { while (foo){ if (bar) break; else return 1; } })", - errors: [error()] + errors: [eReturnsValue()] + }, + + // `return void` is not allowed without `allowVoid: true` + { + code: "new Promise(() => { return void 1; })", + errors: [eReturnsValue()] + }, + + { + code: "new Promise(() => (1))", + errors: [eReturnsValue(20, "Literal")] + }, + { + code: "() => new Promise(() => ({}));", + errors: [eReturnsValue(26, "ObjectExpression")] }, // absence of arguments has no effect { code: "new Promise(function () { return 1; })", - errors: [error()] + errors: [eReturnsValue()] }, { code: "new Promise(() => { return 1; })", - errors: [error()] + errors: [eReturnsValue()] }, { code: "new Promise(() => 1)", - errors: [error(19, "Literal")] + errors: [eReturnsValue(19, "Literal")] }, // various scope tracking tests { code: "function foo() {} new Promise(function () { return 1; });", - errors: [error(45)] + errors: [eReturnsValue(45)] }, { code: "function foo() { return; } new Promise(() => { return 1; });", - errors: [error(48)] + errors: [eReturnsValue(48)] }, { code: "function foo() { return 1; } new Promise(() => { return 2; });", - errors: [error(50)] + errors: [eReturnsValue(50)] }, { code: "function foo () { return new Promise(function () { return 1; }); }", - errors: [error(52)] + errors: [eReturnsValue(52)] }, { code: "function foo() { return new Promise(() => { bar(() => { return 1; }); return false; }); }", - errors: [error(71)] + errors: [eReturnsValue(71)] }, { code: "() => new Promise(() => { if (foo) { return 0; } else bar(() => { return 1; }); })", - errors: [error(38)] + errors: [eReturnsValue(38)] }, { code: "function foo () { return 1; return new Promise(function () { return 2; }); return 3;}", - errors: [error(62)] + errors: [eReturnsValue(62)] }, { code: "() => 1; new Promise(() => { return 1; })", - errors: [error(30)] + errors: [eReturnsValue(30)] }, { code: "new Promise(function () { return 1; }); function foo() { return 1; } ", - errors: [error(27)] + errors: [eReturnsValue(27)] }, { code: "() => new Promise(() => { return 1; });", - errors: [error(27)] + errors: [eReturnsValue(27)] }, { code: "() => new Promise(() => 1);", - errors: [error(25, "Literal")] + errors: [eReturnsValue(25, "Literal")] }, { code: "() => new Promise(() => () => 1);", - errors: [error(25, "ArrowFunctionExpression")] + errors: [eReturnsValue(25, "ArrowFunctionExpression")] + }, + { + code: "() => new Promise(() => async () => 1);", + parserOptions: { ecmaVersion: 2017 }, + + // for async + errors: [eReturnsValue(25, "ArrowFunctionExpression")] + }, + { + code: "() => new Promise(() => function () {});", + errors: [eReturnsValue(25, "FunctionExpression")] + }, + { + code: "() => new Promise(() => function foo() {});", + errors: [eReturnsValue(25, "FunctionExpression")] + }, + { + code: "() => new Promise(() => []);", + errors: [eReturnsValue(25, "ArrayExpression")] }, // edge cases for global Promise reference { code: "new Promise((Promise) => { return 1; })", - errors: [error()] + errors: [eReturnsValue()] }, { code: "new Promise(function Promise(resolve, reject) { return 1; })", - errors: [error()] + errors: [eReturnsValue()] } ] }); From d73fbf2228631d6c468cd24710e2579fe6cb70fd Mon Sep 17 00:00:00 2001 From: fnx Date: Mon, 21 Aug 2023 03:22:22 +0200 Subject: [PATCH 144/248] feat: rule tester do not create empty valid or invalid test suites (#17475) * feat: rule-tester do not create empty valid or invalid test suites * chore: fix grammatical errors in test case descriptions * fix: pass an invalid test case when checking for missing valid test cases * chore: make test cases more consistent * chore: actually make tests consistent --- lib/rule-tester/flat-rule-tester.js | 42 ++++++++------ lib/rule-tester/rule-tester.js | 42 ++++++++------ tests/lib/rule-tester/flat-rule-tester.js | 69 +++++++++++++++++++++++ tests/lib/rule-tester/rule-tester.js | 69 +++++++++++++++++++++++ 4 files changed, 186 insertions(+), 36 deletions(-) diff --git a/lib/rule-tester/flat-rule-tester.js b/lib/rule-tester/flat-rule-tester.js index f143873f7bc7..f297965ec33b 100644 --- a/lib/rule-tester/flat-rule-tester.js +++ b/lib/rule-tester/flat-rule-tester.js @@ -1011,29 +1011,35 @@ class FlatRuleTester { /* * This creates a mocha test suite and pipes all supplied info through * one of the templates above. + * The test suites for valid/invalid are created conditionally as + * test runners (eg. vitest) fail for empty test suites. */ this.constructor.describe(ruleName, () => { - this.constructor.describe("valid", () => { - test.valid.forEach(valid => { - this.constructor[valid.only ? "itOnly" : "it"]( - sanitize(typeof valid === "object" ? valid.name || valid.code : valid), - () => { - testValidTemplate(valid); - } - ); + if (test.valid.length > 0) { + this.constructor.describe("valid", () => { + test.valid.forEach(valid => { + this.constructor[valid.only ? "itOnly" : "it"]( + sanitize(typeof valid === "object" ? valid.name || valid.code : valid), + () => { + testValidTemplate(valid); + } + ); + }); }); - }); + } - this.constructor.describe("invalid", () => { - test.invalid.forEach(invalid => { - this.constructor[invalid.only ? "itOnly" : "it"]( - sanitize(invalid.name || invalid.code), - () => { - testInvalidTemplate(invalid); - } - ); + if (test.invalid.length > 0) { + this.constructor.describe("invalid", () => { + test.invalid.forEach(invalid => { + this.constructor[invalid.only ? "itOnly" : "it"]( + sanitize(invalid.name || invalid.code), + () => { + testInvalidTemplate(invalid); + } + ); + }); }); - }); + } }); } } diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index e4dc126783c8..a61f4028609c 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -1021,29 +1021,35 @@ class RuleTester { /* * This creates a mocha test suite and pipes all supplied info through * one of the templates above. + * The test suites for valid/invalid are created conditionally as + * test runners (eg. vitest) fail for empty test suites. */ this.constructor.describe(ruleName, () => { - this.constructor.describe("valid", () => { - test.valid.forEach(valid => { - this.constructor[valid.only ? "itOnly" : "it"]( - sanitize(typeof valid === "object" ? valid.name || valid.code : valid), - () => { - testValidTemplate(valid); - } - ); + if (test.valid.length > 0) { + this.constructor.describe("valid", () => { + test.valid.forEach(valid => { + this.constructor[valid.only ? "itOnly" : "it"]( + sanitize(typeof valid === "object" ? valid.name || valid.code : valid), + () => { + testValidTemplate(valid); + } + ); + }); }); - }); + } - this.constructor.describe("invalid", () => { - test.invalid.forEach(invalid => { - this.constructor[invalid.only ? "itOnly" : "it"]( - sanitize(invalid.name || invalid.code), - () => { - testInvalidTemplate(invalid); - } - ); + if (test.invalid.length > 0) { + this.constructor.describe("invalid", () => { + test.invalid.forEach(invalid => { + this.constructor[invalid.only ? "itOnly" : "it"]( + sanitize(invalid.name || invalid.code), + () => { + testInvalidTemplate(invalid); + } + ); + }); }); - }); + } }); } } diff --git a/tests/lib/rule-tester/flat-rule-tester.js b/tests/lib/rule-tester/flat-rule-tester.js index f5cdba967ecd..8e1f83af6163 100644 --- a/tests/lib/rule-tester/flat-rule-tester.js +++ b/tests/lib/rule-tester/flat-rule-tester.js @@ -2626,4 +2626,73 @@ describe("FlatRuleTester", () => { }); + describe("Optional Test Suites", () => { + let originalRuleTesterDescribe; + let spyRuleTesterDescribe; + + before(() => { + originalRuleTesterDescribe = FlatRuleTester.describe; + spyRuleTesterDescribe = sinon.spy((title, callback) => callback()); + FlatRuleTester.describe = spyRuleTesterDescribe; + }); + after(() => { + FlatRuleTester.describe = originalRuleTesterDescribe; + }); + beforeEach(() => { + spyRuleTesterDescribe.resetHistory(); + ruleTester = new FlatRuleTester(); + }); + + it("should create a test suite with the rule name even if there are no test cases", () => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: [], + invalid: [] + }); + sinon.assert.calledWith(spyRuleTesterDescribe, "no-var"); + }); + + it("should create a valid test suite if there is a valid test case", () => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: ["value = 0;"], + invalid: [] + }); + sinon.assert.calledWith(spyRuleTesterDescribe, "valid"); + }); + + it("should not create a valid test suite if there are no valid test cases", () => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: [], + invalid: [ + { + code: "var value = 0;", + errors: [/^Bad var/u], + output: " value = 0;" + } + ] + }); + sinon.assert.neverCalledWith(spyRuleTesterDescribe, "valid"); + }); + + it("should create an invalid test suite if there is an invalid test case", () => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: [], + invalid: [ + { + code: "var value = 0;", + errors: [/^Bad var/u], + output: " value = 0;" + } + ] + }); + sinon.assert.calledWith(spyRuleTesterDescribe, "invalid"); + }); + + it("should not create an invalid test suite if there are no invalid test cases", () => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: ["value = 0;"], + invalid: [] + }); + sinon.assert.neverCalledWith(spyRuleTesterDescribe, "invalid"); + }); + }); }); diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js index cb1cf346f62f..c607e4476a02 100644 --- a/tests/lib/rule-tester/rule-tester.js +++ b/tests/lib/rule-tester/rule-tester.js @@ -2870,4 +2870,73 @@ describe("RuleTester", () => { }); + describe("Optional Test Suites", () => { + let originalRuleTesterDescribe; + let spyRuleTesterDescribe; + + before(() => { + originalRuleTesterDescribe = RuleTester.describe; + spyRuleTesterDescribe = sinon.spy((title, callback) => callback()); + RuleTester.describe = spyRuleTesterDescribe; + }); + after(() => { + RuleTester.describe = originalRuleTesterDescribe; + }); + beforeEach(() => { + spyRuleTesterDescribe.resetHistory(); + ruleTester = new RuleTester(); + }); + + it("should create a test suite with the rule name even if there are no test cases", () => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: [], + invalid: [] + }); + sinon.assert.calledWith(spyRuleTesterDescribe, "no-var"); + }); + + it("should create a valid test suite if there is a valid test case", () => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: ["value = 0;"], + invalid: [] + }); + sinon.assert.calledWith(spyRuleTesterDescribe, "valid"); + }); + + it("should not create a valid test suite if there are no valid test cases", () => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: [], + invalid: [ + { + code: "var value = 0;", + errors: [/^Bad var/u], + output: " value = 0;" + } + ] + }); + sinon.assert.neverCalledWith(spyRuleTesterDescribe, "valid"); + }); + + it("should create an invalid test suite if there is an invalid test case", () => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: [], + invalid: [ + { + code: "var value = 0;", + errors: [/^Bad var/u], + output: " value = 0;" + } + ] + }); + sinon.assert.calledWith(spyRuleTesterDescribe, "invalid"); + }); + + it("should not create an invalid test suite if there are no invalid test cases", () => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: ["value = 0;"], + invalid: [] + }); + sinon.assert.neverCalledWith(spyRuleTesterDescribe, "invalid"); + }); + }); }); From 056499de31a139dbc965d18652b0b520e11b408d Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Mon, 21 Aug 2023 10:25:54 +0200 Subject: [PATCH 145/248] docs: fix example of flat config from plugin (#17482) --- docs/src/use/configure/configuration-files-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/use/configure/configuration-files-new.md b/docs/src/use/configure/configuration-files-new.md index 7903854f0622..2d472a4e7e15 100644 --- a/docs/src/use/configure/configuration-files-new.md +++ b/docs/src/use/configure/configuration-files-new.md @@ -475,7 +475,7 @@ import jsdoc from "eslint-plugin-jsdoc"; export default [ // configuration included in plugin - jsdoc.configs.recommended, + jsdoc.configs["flat/recommended"], // other configuration objects... { files: ["**/*.js"], From cab21e64a8f79779c641178f825945958667c6e4 Mon Sep 17 00:00:00 2001 From: Ashish Yadav <48384865+criticalAY@users.noreply.github.com> Date: Tue, 22 Aug 2023 20:47:33 +0530 Subject: [PATCH 146/248] docs: advice for inline disabling of rules (#17458) * docs: advice for dev while disabling rules * docs: advice for dev while disabling rules * docs: advice for dev while disabling rules * Update docs/src/use/configure/rules.md --------- Co-authored-by: Nicholas C. Zakas --- docs/src/use/configure/rules.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/src/use/configure/rules.md b/docs/src/use/configure/rules.md index fd93c2919a04..7fa0bde7f318 100644 --- a/docs/src/use/configure/rules.md +++ b/docs/src/use/configure/rules.md @@ -144,6 +144,17 @@ You can also use this format with configuration comments, such as: ### Using configuration comments +* **Use with Caution.** Disabling ESLint rules inline should be restricted and used only in situations with a clear and + valid reason for doing so. Disabling rules inline should not be the default solution to resolve linting errors. +* **Document the Reason.** Provide a comment explaining the reason for disabling a particular rule after the `--` section of the comment. This + documentation should clarify why the rule is being disabled and why it is necessary in that specific situation. +* **Temporary Solutions.** If a disable comment is added as a temporary measure to address a pressing issue, create a follow-up task to address the underlying problem adequately. This ensures that the + disable comment is revisited and resolved at a later stage. +* **Code Reviews and Pair Programming.** Encourage team members to review each other's code regularly. Code reviews can help + identify the reasons behind disable comments and ensure that they are used appropriately. +* **Configurations.** Whenever possible, prefer using ESLint configuration files over disable comments. Configuration + files allow for consistent and project-wide rule handling. + To disable rule warnings in a part of a file, use block comments in the following format: ```js From 9d4216d638d39844decffac33ee3d5a47413c80a Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 22 Aug 2023 13:56:12 -0400 Subject: [PATCH 147/248] chore: Refactor and document CodePathSegment (#17474) * chore: Refactor and document CodePathSegment * Update lib/linter/code-path-analysis/code-path-segment.js Co-authored-by: Francesco Trotta * Remove unneeded array --------- Co-authored-by: Francesco Trotta --- .../code-path-analysis/code-path-segment.js | 76 +++++++++++++------ 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/lib/linter/code-path-analysis/code-path-segment.js b/lib/linter/code-path-analysis/code-path-segment.js index fd2726a9937a..3b8dbb41be64 100644 --- a/lib/linter/code-path-analysis/code-path-segment.js +++ b/lib/linter/code-path-analysis/code-path-segment.js @@ -1,5 +1,5 @@ /** - * @fileoverview A class of the code path segment. + * @fileoverview The CodePathSegment class. * @author Toru Nagashima */ @@ -30,10 +30,22 @@ function isReachable(segment) { /** * A code path segment. + * + * Each segment is arranged in a series of linked lists (implemented by arrays) + * that keep track of the previous and next segments in a code path. In this way, + * you can navigate between all segments in any code path so long as you have a + * reference to any segment in that code path. + * + * When first created, the segment is in a detached state, meaning that it knows the + * segments that came before it but those segments don't know that this new segment + * follows it. Only when `CodePathSegment#markUsed()` is called on a segment does it + * officially become part of the code path by updating the previous segments to know + * that this new segment follows. */ class CodePathSegment { /** + * Creates a new instance. * @param {string} id An identifier. * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. * This array includes unreachable segments. @@ -49,27 +61,25 @@ class CodePathSegment { this.id = id; /** - * An array of the next segments. + * An array of the next reachable segments. * @type {CodePathSegment[]} */ this.nextSegments = []; /** - * An array of the previous segments. + * An array of the previous reachable segments. * @type {CodePathSegment[]} */ this.prevSegments = allPrevSegments.filter(isReachable); /** - * An array of the next segments. - * This array includes unreachable segments. + * An array of all next segments including reachable and unreachable. * @type {CodePathSegment[]} */ this.allNextSegments = []; /** - * An array of the previous segments. - * This array includes unreachable segments. + * An array of all previous segments including reachable and unreachable. * @type {CodePathSegment[]} */ this.allPrevSegments = allPrevSegments; @@ -83,7 +93,11 @@ class CodePathSegment { // Internal data. Object.defineProperty(this, "internal", { value: { + + // determines if the segment has been attached to the code path used: false, + + // array of previous segments coming from the end of a loop loopedPrevSegments: [] } }); @@ -113,9 +127,10 @@ class CodePathSegment { } /** - * Creates a segment that follows given segments. + * Creates a new segment and appends it after the given segments. * @param {string} id An identifier. - * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. + * @param {CodePathSegment[]} allPrevSegments An array of the previous segments + * to append to. * @returns {CodePathSegment} The created segment. */ static newNext(id, allPrevSegments) { @@ -127,7 +142,7 @@ class CodePathSegment { } /** - * Creates an unreachable segment that follows given segments. + * Creates an unreachable segment and appends it after the given segments. * @param {string} id An identifier. * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. * @returns {CodePathSegment} The created segment. @@ -137,7 +152,7 @@ class CodePathSegment { /* * In `if (a) return a; foo();` case, the unreachable segment preceded by - * the return statement is not used but must not be remove. + * the return statement is not used but must not be removed. */ CodePathSegment.markUsed(segment); @@ -157,7 +172,7 @@ class CodePathSegment { } /** - * Makes a given segment being used. + * Marks a given segment as used. * * And this function registers the segment into the previous segments as a next. * @param {CodePathSegment} segment A segment to mark. @@ -172,6 +187,13 @@ class CodePathSegment { let i; if (segment.reachable) { + + /* + * If the segment is reachable, then it's officially part of the + * code path. This loops through all previous segments to update + * their list of next segments. Because the segment is reachable, + * it's added to both `nextSegments` and `allNextSegments`. + */ for (i = 0; i < segment.allPrevSegments.length; ++i) { const prevSegment = segment.allPrevSegments[i]; @@ -179,6 +201,13 @@ class CodePathSegment { prevSegment.nextSegments.push(segment); } } else { + + /* + * If the segment is not reachable, then it's not officially part of the + * code path. This loops through all previous segments to update + * their list of next segments. Because the segment is not reachable, + * it's added only to `allNextSegments`. + */ for (i = 0; i < segment.allPrevSegments.length; ++i) { segment.allPrevSegments[i].allNextSegments.push(segment); } @@ -196,19 +225,20 @@ class CodePathSegment { } /** - * Replaces unused segments with the previous segments of each unused segment. - * @param {CodePathSegment[]} segments An array of segments to replace. - * @returns {CodePathSegment[]} The replaced array. + * Creates a new array based on an array of segments. If any segment in the + * array is unused, then it is replaced by all of its previous segments. + * All used segments are returned as-is without replacement. + * @param {CodePathSegment[]} segments The array of segments to flatten. + * @returns {CodePathSegment[]} The flattened array. */ static flattenUnusedSegments(segments) { - const done = Object.create(null); - const retv = []; + const done = new Set(); for (let i = 0; i < segments.length; ++i) { const segment = segments[i]; // Ignores duplicated. - if (done[segment.id]) { + if (done.has(segment)) { continue; } @@ -217,18 +247,16 @@ class CodePathSegment { for (let j = 0; j < segment.allPrevSegments.length; ++j) { const prevSegment = segment.allPrevSegments[j]; - if (!done[prevSegment.id]) { - done[prevSegment.id] = true; - retv.push(prevSegment); + if (!done.has(prevSegment)) { + done.add(prevSegment); } } } else { - done[segment.id] = true; - retv.push(segment); + done.add(segment); } } - return retv; + return [...done]; } } From 1fbb3b0b477c814c0d179564fe495f4c50a451e9 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Wed, 23 Aug 2023 16:49:50 +0200 Subject: [PATCH 148/248] feat: correct update direction in `for-direction` (#17483) * feat: correct update direction in `for-direction` * Improve documentation * Update rule overview Co-authored-by: Nicholas C. Zakas * Improve formatting --------- Co-authored-by: Nicholas C. Zakas --- docs/src/rules/for-direction.md | 14 ++++++++++++-- lib/rules/for-direction.js | 23 +++++++++++++++-------- tests/lib/rules/for-direction.js | 23 +++++++++++++++++++++-- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/docs/src/rules/for-direction.md b/docs/src/rules/for-direction.md index 832c87cde3df..45759eb48f9d 100644 --- a/docs/src/rules/for-direction.md +++ b/docs/src/rules/for-direction.md @@ -3,11 +3,11 @@ title: for-direction rule_type: problem --- - +A `for` loop with a stop condition that can never be reached, such as one with a counter that moves in the wrong direction, will run infinitely. While there are occasions when an infinite loop is intended, the convention is to construct such loops as `while` loops. More typically, an infinite `for` loop is a bug. ## Rule Details -A `for` loop with a stop condition that can never be reached, such as one with a counter that moves in the wrong direction, will run infinitely. While there are occasions when an infinite loop is intended, the convention is to construct such loops as `while` loops. More typically, an infinite for loop is a bug. +This rule forbids `for` loops where the counter variable changes in such a way that the stop condition will never be met. For example, if the counter variable is increasing (i.e. `i++`) and the stop condition tests that the counter is greater than zero (`i >= 0`) then the loop will never exit. Examples of **incorrect** code for this rule: @@ -23,6 +23,10 @@ for (var i = 10; i >= 0; i++) { for (var i = 0; i > 10; i++) { } + +const n = -2; +for (let i = 0; i < 10; i += n) { +} ``` ::: @@ -35,6 +39,12 @@ Examples of **correct** code for this rule: /*eslint for-direction: "error"*/ for (var i = 0; i < 10; i++) { } + +for (let i = 10; i >= 0; i += this.step) { // direction unknown +} + +for (let i = MIN; i <= MAX; i -= 0) { // not increasing or decreasing +} ``` ::: diff --git a/lib/rules/for-direction.js b/lib/rules/for-direction.js index 4ed73501581a..3f2ad9df6450 100644 --- a/lib/rules/for-direction.js +++ b/lib/rules/for-direction.js @@ -5,6 +5,12 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { getStaticValue } = require("@eslint-community/eslint-utils"); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -29,6 +35,7 @@ module.exports = { }, create(context) { + const { sourceCode } = context; /** * report an error. @@ -46,17 +53,17 @@ module.exports = { * check the right side of the assignment * @param {ASTNode} update UpdateExpression to check * @param {int} dir expected direction that could either be turned around or invalidated - * @returns {int} return dir, the negated dir or zero if it's not clear for identifiers + * @returns {int} return dir, the negated dir, or zero if the counter does not change or the direction is not clear */ function getRightDirection(update, dir) { - if (update.right.type === "UnaryExpression") { - if (update.right.operator === "-") { - return -dir; - } - } else if (update.right.type === "Identifier") { - return 0; + const staticValue = getStaticValue(update.right, sourceCode.getScope(update)); + + if (staticValue && ["bigint", "boolean", "number"].includes(typeof staticValue.value)) { + const sign = Math.sign(Number(staticValue.value)) || 0; // convert NaN to 0 + + return dir * sign; } - return dir; + return 0; } /** diff --git a/tests/lib/rules/for-direction.js b/tests/lib/rules/for-direction.js index 8ecd843c238c..6e59f6bacf4f 100644 --- a/tests/lib/rules/for-direction.js +++ b/tests/lib/rules/for-direction.js @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } }); const incorrectDirection = { messageId: "incorrectDirection" }; ruleTester.run("for-direction", rule, { @@ -37,6 +37,12 @@ ruleTester.run("for-direction", rule, { "for(var i = 10; i >= 0; i-=1){}", "for(var i = 10; i > 0; i+=-1){}", "for(var i = 10; i >= 0; i+=-1){}", + "for(var i = 0n; i > l; i-=1n){}", + "for(var i = 0n; i < l; i-=-1n){}", + "for(var i = MIN; i <= MAX; i+=true){}", + "for(var i = 0; i < 10; i+=+5e-7){}", + "for(var i = 0; i < MAX; i -= ~2);", + "for(var i = 0, n = -1; i < MAX; i += -n);", // test if no update. "for(var i = 10; i > 0;){}", @@ -54,6 +60,13 @@ ruleTester.run("for-direction", rule, { "for(var i = 0; i < MAX; i += STEP_SIZE);", "for(var i = 0; i < MAX; i -= STEP_SIZE);", "for(var i = 10; i > 0; i += STEP_SIZE);", + "for(var i = 10; i >= 0; i += 0);", + "for(var i = 10n; i >= 0n; i += 0n);", + "for(var i = 10; i >= 0; i += this.step);", + "for(var i = 10; i >= 0; i += 'foo');", + "for(var i = 10; i > 0; i += !foo);", + "for(var i = MIN; i <= MAX; i -= false);", + "for(var i = MIN; i <= MAX; i -= 0/0);", // other cond-expressions. "for(var i = 0; i !== 10; i+=1){}", @@ -77,6 +90,12 @@ ruleTester.run("for-direction", rule, { { code: "for(var i = 0; i < 10; i+=-1){}", errors: [incorrectDirection] }, { code: "for(var i = 0; i <= 10; i+=-1){}", errors: [incorrectDirection] }, { code: "for(var i = 10; i > 10; i-=-1){}", errors: [incorrectDirection] }, - { code: "for(var i = 10; i >= 0; i-=-1){}", errors: [incorrectDirection] } + { code: "for(var i = 10; i >= 0; i-=-1){}", errors: [incorrectDirection] }, + { code: "for(var i = 0n; i > l; i+=1n){}", errors: [incorrectDirection] }, + { code: "for(var i = 0n; i < l; i+=-1n){}", errors: [incorrectDirection] }, + { code: "for(var i = MIN; i <= MAX; i-=true){}", errors: [incorrectDirection] }, + { code: "for(var i = 0; i < 10; i-=+5e-7){}", errors: [incorrectDirection] }, + { code: "for(var i = 0; i < MAX; i += (2 - 3));", errors: [incorrectDirection] }, + { code: "var n = -2; for(var i = 0; i < 10; i += n);", errors: [incorrectDirection] } ] }); From 7234f6a706a209aa2d79259110328752e9ae3928 Mon Sep 17 00:00:00 2001 From: Jonas Berlin Date: Fri, 25 Aug 2023 04:48:00 +0300 Subject: [PATCH 149/248] fix: update RuleTester JSDoc and deprecations (#17496) * RuleTester: Allow new-style rule definitions * RuleTester: Add check for legacy rule API usage warning for defineRule() * RuleTester: Move typedef import to correct location * FlatRuleTester: Allow new-style rule definitions * FlatRuleTester: Fix copypaste bug --- lib/rule-tester/flat-rule-tester.js | 3 ++- lib/rule-tester/rule-tester.js | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/rule-tester/flat-rule-tester.js b/lib/rule-tester/flat-rule-tester.js index f297965ec33b..5a93be1cce8f 100644 --- a/lib/rule-tester/flat-rule-tester.js +++ b/lib/rule-tester/flat-rule-tester.js @@ -32,6 +32,7 @@ const { ConfigArraySymbol } = require("@humanwhocodes/config-array"); /** @typedef {import("../shared/types").Parser} Parser */ /** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */ +/** @typedef {import("../shared/types").Rule} Rule */ /** @@ -446,7 +447,7 @@ class FlatRuleTester { /** * Adds a new rule test to execute. * @param {string} ruleName The name of the rule to run. - * @param {Function} rule The rule to test. + * @param {Function | Rule} rule The rule to test. * @param {{ * valid: (ValidTestCase | string)[], * invalid: InvalidTestCase[] diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index a61f4028609c..030a986867f3 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -62,6 +62,7 @@ const { SourceCode } = require("../source-code"); //------------------------------------------------------------------------------ /** @typedef {import("../shared/types").Parser} Parser */ +/** @typedef {import("../shared/types").Rule} Rule */ /** @@ -508,17 +509,20 @@ class RuleTester { /** * Define a rule for one particular run of tests. * @param {string} name The name of the rule to define. - * @param {Function} rule The rule definition. + * @param {Function | Rule} rule The rule definition. * @returns {void} */ defineRule(name, rule) { + if (typeof rule === "function") { + emitLegacyRuleAPIWarning(name); + } this.rules[name] = rule; } /** * Adds a new rule test to execute. * @param {string} ruleName The name of the rule to run. - * @param {Function} rule The rule to test. + * @param {Function | Rule} rule The rule to test. * @param {{ * valid: (ValidTestCase | string)[], * invalid: InvalidTestCase[] From 9cd7ac2fdb6b1d71a9fb1b8297a478cafacbdafd Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Fri, 25 Aug 2023 20:14:20 +0530 Subject: [PATCH 150/248] docs: add `fetch` script to package.json conventions (#17459) * docs: add `fetch` script to package.json conventions * docs: revert formatting changes * docs: apply suggestions from code review Co-authored-by: Nicholas C. Zakas * docs: update docs/src/contribute/package-json-conventions.md * docs: add "fetch" script --------- Co-authored-by: Nicholas C. Zakas --- docs/src/contribute/core-rules.md | 2 +- docs/src/contribute/governance.md | 2 +- docs/src/contribute/package-json-conventions.md | 13 ++++++++++++- docs/src/contribute/pull-requests.md | 2 +- .../src/contribute/report-security-vulnerability.md | 2 +- docs/src/contribute/work-on-issue.md | 2 +- 6 files changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/src/contribute/core-rules.md b/docs/src/contribute/core-rules.md index 5610c000e849..93b7647f52be 100644 --- a/docs/src/contribute/core-rules.md +++ b/docs/src/contribute/core-rules.md @@ -4,7 +4,7 @@ eleventyNavigation: key: contribute core rule parent: contribute to eslint title: Contribute to Core Rules - order: 10 + order: 11 --- The ESLint core rules are the rules included in the ESLint package. diff --git a/docs/src/contribute/governance.md b/docs/src/contribute/governance.md index 563840a01dc6..3f761fb2a513 100644 --- a/docs/src/contribute/governance.md +++ b/docs/src/contribute/governance.md @@ -4,7 +4,7 @@ eleventyNavigation: key: governance parent: contribute to eslint title: Governance - order: 11 + order: 12 --- diff --git a/docs/src/contribute/package-json-conventions.md b/docs/src/contribute/package-json-conventions.md index 99afe8b22224..a030dc3f3dc9 100644 --- a/docs/src/contribute/package-json-conventions.md +++ b/docs/src/contribute/package-json-conventions.md @@ -1,6 +1,11 @@ --- title: Package.json Conventions edit_link: https://github.com/eslint/eslint/edit/main/docs/src/contribute/package-json-conventions.md +eleventyNavigation: + key: package.json conventions + parent: contribute to eslint + title: Package.json Conventions + order: 8 --- The following applies to the "scripts" section of `package.json` files. @@ -14,7 +19,7 @@ Here is a summary of the proposal in ABNF. ```abnf name = life-cycle / main target? option* ":watch"? life-cycle = "prepare" / "preinstall" / "install" / "postinstall" / "prepublish" / "preprepare" / "prepare" / "postprepare" / "prepack" / "postpack" / "prepublishOnly" -main = "build" / "lint" ":fix"? / "release" / "start" / "test" +main = "build" / "lint" ":fix"? / "release" / "start" / "test" / "fetch" target = ":" word ("-" word)* / extension ("+" extension)* option = ":" word ("-" word)* word = ALPHA + @@ -35,6 +40,12 @@ Scripts that generate a set of files from source code and / or data MUST have na If a package contains any `build:*` scripts, there MAY be a script named `build`. If so, SHOULD produce the same output as running each of the `build` scripts individually. It MUST produce a subset of the output from running those scripts. +### Fetch + +Scripts that generate a set of files from external data or resources MUST have names that begin with `fetch`. + +If a package contains any `fetch:*` scripts, there MAY be a script named `fetch`. If so, it SHOULD produce the same output as running each of the `fetch` scripts individually. It MUST produce a subset of the output from running those scripts. + ### Release Scripts that have public side effects (publishing the web site, committing to Git, etc.) MUST begin with `release`. diff --git a/docs/src/contribute/pull-requests.md b/docs/src/contribute/pull-requests.md index 8854ee2fbb24..44f2378d8dc1 100644 --- a/docs/src/contribute/pull-requests.md +++ b/docs/src/contribute/pull-requests.md @@ -4,7 +4,7 @@ eleventyNavigation: key: submit pull request parent: contribute to eslint title: Submit a Pull Request - order: 9 + order: 10 --- If you want to contribute to an ESLint repo, please use a GitHub pull request. This is the fastest way for us to evaluate your code and to merge it into the code base. Please don't file an issue with snippets of code. Doing so means that we need to manually merge the changes in and update any appropriate tests. That decreases the likelihood that your code is going to get included in a timely manner. Please use pull requests. diff --git a/docs/src/contribute/report-security-vulnerability.md b/docs/src/contribute/report-security-vulnerability.md index f68319fd34e1..69f31a9a73b4 100644 --- a/docs/src/contribute/report-security-vulnerability.md +++ b/docs/src/contribute/report-security-vulnerability.md @@ -4,7 +4,7 @@ eleventyNavigation: key: report security vulnerability parent: contribute to eslint title: Report a Security Vulnerability - order: 11 + order: 13 --- To report a security vulnerability in ESLint, please use our [create an advisory form](https://github.com/eslint/eslint/security/advisories/new) on GitHub. diff --git a/docs/src/contribute/work-on-issue.md b/docs/src/contribute/work-on-issue.md index a141d59a8e61..d258aa4eb156 100644 --- a/docs/src/contribute/work-on-issue.md +++ b/docs/src/contribute/work-on-issue.md @@ -4,7 +4,7 @@ eleventyNavigation: key: work on issues parent: contribute to eslint title: Work on Issues - order: 8 + order: 9 --- Our public [issues tracker](https://github.com/eslint/eslint/issues) lists all of the things we plan on doing as well as suggestions from the community. Before starting to work on an issue, be sure you read through the rest of this page. From 7a51d77c0a066e461ff288568fdfee0e9539a2b5 Mon Sep 17 00:00:00 2001 From: Stephen Hardy <2132243+stephen-hardy@users.noreply.github.com> Date: Fri, 25 Aug 2023 07:49:28 -0700 Subject: [PATCH 151/248] docs: no-param-reassign mention strict mode (#17494) * no-param-reassign strict mode mention * Update docs/src/rules/no-param-reassign.md Fixes #17484 Co-authored-by: Nitin Kumar --------- Co-authored-by: Nicholas C. Zakas Co-authored-by: Nitin Kumar --- docs/src/rules/no-param-reassign.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/src/rules/no-param-reassign.md b/docs/src/rules/no-param-reassign.md index 2bddf7b9921c..9c23c45f981a 100644 --- a/docs/src/rules/no-param-reassign.md +++ b/docs/src/rules/no-param-reassign.md @@ -6,7 +6,7 @@ further_reading: --- -Assignment to variables declared as function parameters can be misleading and lead to confusing behavior, as modifying function parameters will also mutate the `arguments` object. Often, assignment to function parameters is unintended and indicative of a mistake or programmer error. +Assignment to variables declared as function parameters can be misleading and lead to confusing behavior, as modifying function parameters will also mutate the `arguments` object when not in strict mode (see [When Not To Use It](#when-not-to-use-it) below). Often, assignment to function parameters is unintended and indicative of a mistake or programmer error. This rule can be also configured to fail when function parameters are modified. Side effects on parameters can cause counter-intuitive execution flow and make errors difficult to track down. @@ -183,3 +183,5 @@ function foo(barBaz) { ## When Not To Use It If you want to allow assignment to function parameters, then you can safely disable this rule. + +Strict mode code doesn't sync indices of the arguments object with each parameter binding. Therefore, this rule is not necessary to protect against arguments object mutation in ESM modules or other strict mode functions. From 6d0496e9476fb2210fba0a3d541df8c052ecf73a Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 25 Aug 2023 15:20:38 -0400 Subject: [PATCH 152/248] 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 1847404d7c99..965acdba0581 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "8.47.0", + "version": "8.48.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 8dd3cec90c97ed97d243a83b87ad4ea9e6b4781a Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 25 Aug 2023 21:46:15 +0200 Subject: [PATCH 153/248] chore: upgrade @eslint/js@8.48.0 (#17501) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 661e8fcaf4d8..287f4e0dc60c 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "^8.47.0", + "@eslint/js": "8.48.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", From 5013ad72a34275fe81c0c1cb907a9ff87dcf0627 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 25 Aug 2023 16:01:23 -0400 Subject: [PATCH 154/248] Build: changelog update for 8.48.0 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b749600969e0..dbbab930b601 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +v8.48.0 - August 25, 2023 + +* [`8dd3cec`](https://github.com/eslint/eslint/commit/8dd3cec90c97ed97d243a83b87ad4ea9e6b4781a) chore: upgrade @eslint/js@8.48.0 (#17501) (Milos Djermanovic) +* [`6d0496e`](https://github.com/eslint/eslint/commit/6d0496e9476fb2210fba0a3d541df8c052ecf73a) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`7a51d77`](https://github.com/eslint/eslint/commit/7a51d77c0a066e461ff288568fdfee0e9539a2b5) docs: no-param-reassign mention strict mode (#17494) (Stephen Hardy) +* [`9cd7ac2`](https://github.com/eslint/eslint/commit/9cd7ac2fdb6b1d71a9fb1b8297a478cafacbdafd) docs: add `fetch` script to package.json conventions (#17459) (Nitin Kumar) +* [`7234f6a`](https://github.com/eslint/eslint/commit/7234f6a706a209aa2d79259110328752e9ae3928) fix: update RuleTester JSDoc and deprecations (#17496) (Jonas Berlin) +* [`1fbb3b0`](https://github.com/eslint/eslint/commit/1fbb3b0b477c814c0d179564fe495f4c50a451e9) feat: correct update direction in `for-direction` (#17483) (Francesco Trotta) +* [`9d4216d`](https://github.com/eslint/eslint/commit/9d4216d638d39844decffac33ee3d5a47413c80a) chore: Refactor and document CodePathSegment (#17474) (Nicholas C. Zakas) +* [`cab21e6`](https://github.com/eslint/eslint/commit/cab21e64a8f79779c641178f825945958667c6e4) docs: advice for inline disabling of rules (#17458) (Ashish Yadav) +* [`056499d`](https://github.com/eslint/eslint/commit/056499de31a139dbc965d18652b0b520e11b408d) docs: fix example of flat config from plugin (#17482) (Francesco Trotta) +* [`d73fbf2`](https://github.com/eslint/eslint/commit/d73fbf2228631d6c468cd24710e2579fe6cb70fd) feat: rule tester do not create empty valid or invalid test suites (#17475) (fnx) +* [`ee2f718`](https://github.com/eslint/eslint/commit/ee2f718188d32e9888b1932fe6b9bd2a62c529a4) feat: Allow `void` in rule `no-promise-executor-return` (#17282) (nopeless) +* [`9e9edf9`](https://github.com/eslint/eslint/commit/9e9edf93ecfa0658e8b79e71bc98530ade150081) docs: update documentation URL in error message (#17465) (Nitin Kumar) + v8.47.0 - August 11, 2023 * [`bf69aa6`](https://github.com/eslint/eslint/commit/bf69aa6408f5403a88d8c9b71b0e58232b1ea833) chore: Update dependencies (#17456) (Nicholas C. Zakas) From 10c4f85dca978b42d37619f50565a06b9a28c9ac Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 25 Aug 2023 16:01:24 -0400 Subject: [PATCH 155/248] 8.48.0 --- docs/package.json | 2 +- docs/src/_data/rules.json | 2 +- docs/src/_data/rules_meta.json | 3 ++- docs/src/use/formatters/html-formatter-example.html | 2 +- package.json | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/package.json b/docs/package.json index 981b67d4e29c..15cab31348a8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.47.0", + "version": "8.48.0", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 6ac78e8a1b25..0f3893f14f20 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -255,7 +255,7 @@ "description": "Disallow returning values from Promise executor functions", "recommended": false, "fixable": false, - "hasSuggestions": false + "hasSuggestions": true }, { "name": "no-prototype-builtins", diff --git a/docs/src/_data/rules_meta.json b/docs/src/_data/rules_meta.json index c7e8b97cca13..7113de13bdb5 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -1477,7 +1477,8 @@ "description": "Disallow returning values from Promise executor functions", "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-promise-executor-return" - } + }, + "hasSuggestions": true }, "no-proto": { "type": "suggestion", diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index 4f87732fd189..16db7f6d6eb2 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

- 9 problems (5 errors, 4 warnings) - Generated on Fri Aug 11 2023 11:18:15 GMT-0400 (Eastern Daylight Time) + 9 problems (5 errors, 4 warnings) - Generated on Fri Aug 25 2023 16:01:24 GMT-0400 (Eastern Daylight Time)
diff --git a/package.json b/package.json index 287f4e0dc60c..7b58827056a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.47.0", + "version": "8.48.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From f591d2c88bf15af72e3a207b34fa872b4b90464b Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 29 Aug 2023 12:31:58 -0400 Subject: [PATCH 156/248] chore: Upgrade config-array (#17512) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7b58827056a8..d52a2aef60ce 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", "@eslint/js": "8.48.0", - "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", From 926a28684282aeec37680bbc52a66973b8055f54 Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Tue, 29 Aug 2023 19:50:25 -0700 Subject: [PATCH 157/248] test: replace Karma with Webdriver.IO (#17126) * test: replace Karma with Webdriver.IO The current test framework for browser testing (Karma) is not maintained anymore and WebdriverIO provides a more modern stack that allows to test in different browser. This patch replaces these test frameworks. fixes: #17009 * update webdriverio deps * PR feedback * adjust tests * build eslint before running tests * make test file an esm file * revert more esm changes * make it work * remove return value * custom log dir for wdio tests * auto detect chromedriver * bump timeout, store logs * bump timeout again * update wdio deps * update wdio deps * set log level to trace * update wdio deps and unskip tests * no need to have this be an async test * update deps * make path spec file explicit * remove Chromedriver deps * removed wdio command --- .github/workflows/ci.yml | 8 +- .gitignore | 2 + Makefile.js | 8 +- karma.conf.js | 125 ----------- lib/config/rule-validator.js | 3 +- package.json | 14 +- tests/lib/linter/linter.js | 14 +- wdio.conf.js | 387 +++++++++++++++++++++++++++++++++++ 8 files changed, 416 insertions(+), 145 deletions(-) delete mode 100644 karma.conf.js create mode 100644 wdio.conf.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27dae4c1262e..92e2a7eed35e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,12 @@ jobs: - name: Install Packages run: npm install - name: Test - run: node Makefile karma + 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/.gitignore b/.gitignore index 075a4d740c70..148181e07769 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ test.js coverage/ build/ +logs +wdio-logs npm-debug.log yarn-error.log .pnpm-debug.log diff --git a/Makefile.js b/Makefile.js index 717cc7859467..528fd5aa1e72 100644 --- a/Makefile.js +++ b/Makefile.js @@ -628,12 +628,10 @@ target.mocha = () => { } }; -target.karma = () => { +target.wdio = () => { echo("Running unit tests on browsers"); - target.webpack("production"); - - const lastReturn = exec(`${getBinFile("karma")} start karma.conf.js`); + const lastReturn = exec(`${getBinFile("wdio")} run wdio.conf.js`); if (lastReturn.code !== 0) { exit(1); @@ -643,7 +641,7 @@ target.karma = () => { target.test = function() { target.checkRuleFiles(); target.mocha(); - target.karma(); + target.wdio(); target.fuzz({ amount: 150, fuzzBrokenAutofixes: false }); target.checkLicenses(); }; diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index 606d13f88f62..000000000000 --- a/karma.conf.js +++ /dev/null @@ -1,125 +0,0 @@ -"use strict"; -const os = require("os"); -const NodePolyfillPlugin = require("node-polyfill-webpack-plugin"); - -if (os.platform === "linux" && os.arch() === "arm64") { - - // For arm64 architecture, install chromium-browser using "apt-get install chromium-browser" - process.env.CHROME_BIN = "/usr/bin/chromium-browser"; -} else { - process.env.CHROME_BIN = require("puppeteer").executablePath(); -} - -module.exports = function(config) { - config.set({ - - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: "", - - // next three sections allow console.log to work - client: { - captureConsole: true - }, - - browserConsoleLogOptions: { - terminal: true, - level: "log" - }, - - /* - * frameworks to use - * available frameworks: https://npmjs.org/browse/keyword/karma-adapter - */ - frameworks: ["mocha", "webpack"], - - - // list of files / patterns to load in the browser - files: [ - "tests/lib/linter/linter.js" - ], - - - // list of files to exclude - exclude: [ - ], - - - /* - * preprocess matching files before serving them to the browser - * available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - */ - preprocessors: { - "tests/lib/linter/linter.js": ["webpack"] - }, - webpack: { - mode: "none", - plugins: [ - new NodePolyfillPlugin() - ], - resolve: { - alias: { - "../../../lib/linter$": "../../../build/eslint.js" - } - }, - stats: "errors-only" - }, - webpackMiddleware: { - logLevel: "error" - }, - - - /* - * test results reporter to use - * possible values: "dots", "progress" - * available reporters: https://npmjs.org/browse/keyword/karma-reporter - */ - reporters: ["mocha"], - - mochaReporter: { - output: "minimal" - }, - - // web server port - port: 9876, - - - // enable / disable colors in the output (reporters and logs) - colors: true, - - - /* - * level of logging - * possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - */ - logLevel: config.LOG_INFO, - - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: false, - - - /* - * start these browsers - * available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - */ - browsers: ["HeadlessChrome"], - customLaunchers: { - HeadlessChrome: { - base: "ChromeHeadless", - flags: ["--no-sandbox"] - } - }, - - /* - * Continuous Integration mode - * if true, Karma captures browsers, runs the tests and exits - */ - singleRun: true, - - /* - * Concurrency level - * how many browser should be started simultaneous - */ - concurrency: Infinity - }); -}; diff --git a/lib/config/rule-validator.js b/lib/config/rule-validator.js index 0b5858fb30f3..eee5b40bd07b 100644 --- a/lib/config/rule-validator.js +++ b/lib/config/rule-validator.js @@ -9,7 +9,8 @@ // Requirements //----------------------------------------------------------------------------- -const ajv = require("../shared/ajv")(); +const ajvImport = require("../shared/ajv"); +const ajv = ajvImport(); const { parseRuleId, getRuleFromConfig, diff --git a/package.json b/package.json index d52a2aef60ce..4c22a4a94f09 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,11 @@ "devDependencies": { "@babel/core": "^7.4.3", "@babel/preset-env": "^7.4.3", + "@wdio/browser-runner": "^8.14.6", + "@wdio/cli": "^8.14.6", + "@wdio/concise-reporter": "^8.14.0", + "@wdio/globals": "^8.14.6", + "@wdio/mocha-framework": "^8.14.0", "babel-loader": "^8.0.5", "c8": "^7.12.0", "chai": "^4.0.1", @@ -124,11 +129,6 @@ "glob": "^7.1.6", "got": "^11.8.3", "gray-matter": "^4.0.3", - "karma": "^6.1.1", - "karma-chrome-launcher": "^3.1.0", - "karma-mocha": "^2.0.1", - "karma-mocha-reporter": "^2.2.5", - "karma-webpack": "^5.0.0", "lint-staged": "^11.0.0", "load-perf": "^0.2.0", "markdownlint": "^0.25.1", @@ -148,12 +148,14 @@ "pirates": "^4.0.5", "progress": "^2.0.3", "proxyquire": "^2.0.1", - "puppeteer": "^13.7.0", "recast": "^0.20.4", "regenerator-runtime": "^0.13.2", + "rollup-plugin-node-polyfills": "^0.2.1", "semver": "^7.5.3", "shelljs": "^0.8.2", "sinon": "^11.0.0", + "vite-plugin-commonjs": "^0.8.2", + "webdriverio": "^8.14.6", "webpack": "^5.23.0", "webpack-cli": "^4.5.0", "yorkie": "^2.0.0" diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index f86e98ba8e0d..65957f82a756 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -9,7 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("chai").assert, +const { assert } = require("chai"), sinon = require("sinon"), espree = require("espree"), esprima = require("esprima"), @@ -7263,12 +7263,12 @@ var a = "test2"; it("should have file path passed to it", () => { const code = "/* this is code */"; - const parseSpy = sinon.spy(testParsers.stubParser, "parse"); + const parseSpy = { parse: sinon.spy() }; - linter.defineParser("stub-parser", testParsers.stubParser); + linter.defineParser("stub-parser", parseSpy); linter.verify(code, { parser: "stub-parser" }, filename, true); - sinon.assert.calledWithMatch(parseSpy, "", { filePath: filename }); + sinon.assert.calledWithMatch(parseSpy.parse, "", { filePath: filename }); }); it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => { @@ -8068,16 +8068,16 @@ describe("Linter with FlatConfigArray", () => { it("should have file path passed to it", () => { const code = "/* this is code */"; - const parseSpy = sinon.spy(testParsers.stubParser, "parse"); + const parseSpy = { parse: sinon.spy() }; const config = { languageOptions: { - parser: testParsers.stubParser + parser: parseSpy } }; linter.verify(code, config, filename, true); - sinon.assert.calledWithMatch(parseSpy, "", { filePath: filename }); + sinon.assert.calledWithMatch(parseSpy.parse, "", { filePath: filename }); }); it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => { diff --git a/wdio.conf.js b/wdio.conf.js new file mode 100644 index 000000000000..f32d757133e6 --- /dev/null +++ b/wdio.conf.js @@ -0,0 +1,387 @@ +"use strict"; + +const path = require("path"); +const commonjs = require("vite-plugin-commonjs").default; + +exports.config = { + + /* + * + * ==================== + * Runner Configuration + * ==================== + * WebdriverIO supports running e2e tests as well as unit and component tests. + */ + runner: ["browser", { + viteConfig: { + resolve: { + alias: { + util: "rollup-plugin-node-polyfills/polyfills/util", + path: "rollup-plugin-node-polyfills/polyfills/path", + assert: "rollup-plugin-node-polyfills/polyfills/assert" + } + }, + plugins: [ + commonjs(), + { + name: "wdio:import-fix", + enforce: "pre", + transform(source, id) { + if (!id.endsWith("/tests/lib/linter/linter.js")) { + return source; + } + + return source.replace( + 'const { Linter } = require("../../../lib/linter");', + 'const { Linter } = require("../../../build/eslint");\n' + + 'process.cwd = () => "/";' + ); + } + } + ] + } + }], + + /* + * + * ================== + * Specify Test Files + * ================== + * Define which test specs should run. The pattern is relative to the directory + * of the configuration file being run. + * + * The specs are defined as an array of spec files (optionally using wildcards + * that will be expanded). The test for each spec file will be run in a separate + * worker process. In order to have a group of spec files run in the same worker + * process simply enclose them in an array within the specs array. + * + * If you are calling `wdio` from an NPM script (see https://docs.npmjs.com/cli/run-script), + * then the current working directory is where your `package.json` resides, so `wdio` + * will be called from there. + * + */ + specs: [ + path.join(__dirname, "tests", "lib", "linter", "linter.js") + ], + + // Patterns to exclude. + exclude: [], + + /* + * + * ============ + * Capabilities + * ============ + * Define your capabilities here. WebdriverIO can run multiple capabilities at the same + * time. Depending on the number of capabilities, WebdriverIO launches several test + * sessions. Within your capabilities you can overwrite the spec and exclude options in + * order to group specific specs to a specific capability. + * + * First, you can define how many instances should be started at the same time. Let"s + * say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have + * set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec + * files and you set maxInstances to 10, all spec files will get tested at the same time + * and 30 processes will get spawned. The property handles how many capabilities + * from the same test should run tests. + * + */ + maxInstances: 10, + + /* + * + * If you have trouble getting all important capabilities together, check out the + * Sauce Labs platform configurator - a great tool to configure your capabilities: + * https://saucelabs.com/platform/platform-configurator + * + */ + capabilities: [{ + browserName: "chrome", + "goog:chromeOptions": { + args: process.env.CI ? ["headless", "disable-gpu"] : [] + } + }], + + /* + * + * =================== + * Test Configurations + * =================== + * Define all options that are relevant for the WebdriverIO instance here + * + * Level of logging verbosity: trace | debug | info | warn | error | silent + */ + logLevel: "trace", + outputDir: "./wdio-logs", + + /* + * + * Set specific log levels per logger + * loggers: + * - webdriver, webdriverio + * - @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service + * - @wdio/mocha-framework, @wdio/jasmine-framework + * - @wdio/local-runner + * - @wdio/sumologic-reporter + * - @wdio/cli, @wdio/config, @wdio/utils + * Level of logging verbosity: trace | debug | info | warn | error | silent + * logLevels: { + * webdriver: 'info', + * '@wdio/appium-service': 'info' + * }, + * + * If you only want to run your tests until a specific amount of tests have failed use + * bail (default is 0 - don't bail, run all tests). + */ + bail: 0, + + /* + * + * Set a base URL in order to shorten url command calls. If your `url` parameter starts + * with `/`, the base url gets prepended, not including the path portion of your baseUrl. + * If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url + * gets prepended directly. + */ + baseUrl: "", + + /* + * + * Default timeout for all waitFor* commands. + */ + waitforTimeout: 10000, + + /* + * + * Default timeout in milliseconds for request + * if browser driver or grid doesn't send response + */ + connectionRetryTimeout: 120000, + + /* + * + * Default request retries count + */ + connectionRetryCount: 3, + + /* + * Framework you want to run your specs with. + * The following are supported: Mocha, Jasmine, and Cucumber + * see also: https://webdriver.io/docs/frameworks + * + * Make sure you have the wdio adapter package for the specific framework installed + * before running any tests. + */ + framework: "mocha", + + /* + * + * The number of times to retry the entire specfile when it fails as a whole + * specFileRetries: 1, + * + * Delay in seconds between the spec file retry attempts + * specFileRetriesDelay: 0, + * + * Whether or not retried specfiles should be retried immediately or deferred to the end of the queue + * specFileRetriesDeferred: false, + * + * Test reporter for stdout. + * The only one supported by default is 'dot' + * see also: https://webdriver.io/docs/dot-reporter + */ + reporters: ["concise"], + + /* + * + * Options to be passed to Mocha. + * See the full list at http://mochajs.org/ + */ + mochaOpts: { + ui: "bdd", + timeout: 5 * 60 * 1000, // 5min + grep: "@skipWeb", + invert: true + } + + /* + * + * ===== + * Hooks + * ===== + * WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance + * it and to build services around it. You can either apply a single function or an array of + * methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got + * resolved to continue. + */ + /** + * Gets executed once before all workers get launched. + * @param {Object} config wdio configuration object + * @param {Array} capabilities list of capabilities details + */ + /* + * onPrepare: function (config, capabilities) { + * }, + */ + /** + * Gets executed before a worker process is spawned and can be used to initialise specific service + * for that worker as well as modify runtime environments in an async fashion. + * @param {string} cid capability id (e.g 0-0) + * @param {Object} caps object containing capabilities for session that will be spawn in the worker + * @param {Object} specs specs to be run in the worker process + * @param {Object} args object that will be merged with the main configuration once worker is initialized + * @param {Object} execArgv list of string arguments passed to the worker process + */ + /* + * onWorkerStart: function (cid, caps, specs, args, execArgv) { + * }, + */ + /** + * Gets executed just after a worker process has exited. + * @param {string} cid capability id (e.g 0-0) + * @param {number} exitCode 0 - success, 1 - fail + * @param {Object} specs specs to be run in the worker process + * @param {number} retries number of retries used + */ + /* + * onWorkerEnd: function (cid, exitCode, specs, retries) { + * }, + */ + /** + * Gets executed just before initialising the webdriver session and test framework. It allows you + * to manipulate configurations depending on the capability or spec. + * @param {Object} config wdio configuration object + * @param {Array} capabilities list of capabilities details + * @param {Array} specs List of spec file paths that are to be run + * @param {string} cid worker id (e.g. 0-0) + */ + /* + * beforeSession: function (config, capabilities, specs, cid) { + * }, + */ + /** + * Gets executed before test execution begins. At this point you can access to all global + * variables like `browser`. It is the perfect place to define custom commands. + * @param {Array} capabilities list of capabilities details + * @param {Array} specs List of spec file paths that are to be run + * @param {Object} browser instance of created browser/device session + */ + /* + * before: function (capabilities, specs) { + * }, + */ + /** + * Runs before a WebdriverIO command gets executed. + * @param {string} commandName hook command name + * @param {Array} args arguments that command would receive + */ + /* + * beforeCommand: function (commandName, args) { + * }, + */ + /** + * Hook that gets executed before the suite starts + * @param {Object} suite suite details + */ + /* + * beforeSuite: function (suite) { + * }, + */ + /** + * Function to be executed before a test (in Mocha/Jasmine) starts. + */ + /* + * beforeTest: function (test, context) { + * }, + */ + /** + * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling + * beforeEach in Mocha) + */ + /* + * beforeHook: function (test, context) { + * }, + */ + /** + * Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling + * afterEach in Mocha) + */ + /* + * afterHook: function (test, context, { error, result, duration, passed, retries }) { + * }, + */ + /** + * Function to be executed after a test (in Mocha/Jasmine only) + * @param {Object} test test object + * @param {Object} context scope object the test was executed with + * @param {Error} result.error error object in case the test fails, otherwise `undefined` + * @param {any} result.result return object of test function + * @param {number} result.duration duration of test + * @param {boolean} result.passed true if test has passed, otherwise false + * @param {Object} result.retries informations to spec related retries, e.g. `{ attempts: 0, limit: 0 }` + */ + /* + * afterTest: function(test, context, { error, result, duration, passed, retries }) { + * }, + */ + + + /** + * Hook that gets executed after the suite has ended + * @param {Object} suite suite details + */ + /* + * afterSuite: function (suite) { + * }, + */ + /** + * Runs after a WebdriverIO command gets executed + * @param {string} commandName hook command name + * @param {Array} args arguments that command would receive + * @param {number} result 0 - command success, 1 - command error + * @param {Object} error error object if any + */ + /* + * afterCommand: function (commandName, args, result, error) { + * }, + */ + /** + * Gets executed after all tests are done. You still have access to all global variables from + * the test. + * @param {number} result 0 - test pass, 1 - test fail + * @param {Array} capabilities list of capabilities details + * @param {Array} specs List of spec file paths that ran + */ + /* + * after: function (result, capabilities, specs) { + * }, + */ + /** + * Gets executed right after terminating the webdriver session. + * @param {Object} config wdio configuration object + * @param {Array} capabilities list of capabilities details + * @param {Array} specs List of spec file paths that ran + */ + /* + * afterSession: function (config, capabilities, specs) { + * }, + */ + /** + * Gets executed after all workers got shut down and the process is about to exit. An error + * thrown in the onComplete hook will result in the test run failing. + * @param {Object} exitCode 0 - success, 1 - fail + * @param {Object} config wdio configuration object + * @param {Array} capabilities list of capabilities details + * @param {Object} results object containing test results + */ + /* + * onComplete: function(exitCode, config, capabilities, results) { + * }, + */ + /** + * Gets executed when a refresh happens. + * @param {string} oldSessionId session ID of the old session + * @param {string} newSessionId session ID of the new session + */ + /* + * onReload: function(oldSessionId, newSessionId) { + * } + */ +}; From a40fa509922b36bb986eb1be9394591f84f62d9e Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 1 Sep 2023 02:39:45 +0200 Subject: [PATCH 158/248] chore: use eslint-plugin-jsdoc's flat config (#17516) --- packages/eslint-config-eslint/base.js | 3 +-- packages/eslint-config-eslint/package.json | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/eslint-config-eslint/base.js b/packages/eslint-config-eslint/base.js index 1365108390d5..083336919e18 100644 --- a/packages/eslint-config-eslint/base.js +++ b/packages/eslint-config-eslint/base.js @@ -9,7 +9,6 @@ const unicorn = require("eslint-plugin-unicorn"); * the plugins' configs are not updated to support the flat config, * need to manually update the `plugins` property */ -jsdoc.configs.recommended.plugins = { jsdoc }; eslintComments.configs.recommended.plugins = { "eslint-comments": eslintComments }; // extends eslint recommended config @@ -262,7 +261,7 @@ const jsConfigs = [js.configs.recommended, { }]; // extends eslint-plugin-jsdoc's recommended config -const jsdocConfigs = [jsdoc.configs.recommended, { +const jsdocConfigs = [jsdoc.configs["flat/recommended"], { settings: { jsdoc: { mode: "typescript", diff --git a/packages/eslint-config-eslint/package.json b/packages/eslint-config-eslint/package.json index 7d8a747537c8..f8c7fab87686 100644 --- a/packages/eslint-config-eslint/package.json +++ b/packages/eslint-config-eslint/package.json @@ -30,7 +30,7 @@ "dependencies": { "@eslint/js": "^8.42.0", "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-jsdoc": "^46.2.5", + "eslint-plugin-jsdoc": "^46.5.1", "eslint-plugin-n": "^16.0.0", "eslint-plugin-unicorn": "^42.0.0" }, From cd7da5cc3154f86f7ca45fb58929d27a7af359ed Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Fri, 1 Sep 2023 08:06:33 +0000 Subject: [PATCH 159/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0ce3a4a5a832..818a06e25ed6 100644 --- a/README.md +++ b/README.md @@ -289,7 +289,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff American Express

Bronze Sponsors

-

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord GitHub Transloadit Ignition HeroCoders QuickBooks Tool hub

+

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8 Discord GitHub Transloadit Ignition HeroCoders QuickBooks Tool hub

## Technology Sponsors From 032c4b1476a7b8cfd917a66772d2221950ea87eb Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Fri, 1 Sep 2023 14:26:02 -0400 Subject: [PATCH 160/248] docs: add typescript template (#17500) * docs: add typescript template * fix: formatting * chore: update docs with AndreaPontrandolfo's findings * Update no-import-assign.md * chore: update no-redeclare * fix: update TS code numbers * feat: cleanup typescript error codes * Update docs/src/_includes/layouts/doc.html Co-authored-by: Nicholas C. Zakas * Update docs/src/rules/no-redeclare.md Co-authored-by: Nicholas C. Zakas * Update docs/src/rules/no-import-assign.md Co-authored-by: Nicholas C. Zakas * Update docs/src/rules/no-invalid-this.md Co-authored-by: Nicholas C. Zakas * fix: formatting error --------- Co-authored-by: Nicholas C. Zakas --- docs/src/_includes/layouts/doc.html | 21 +++++++++++++++++++-- docs/src/rules/constructor-super.md | 3 +-- docs/src/rules/getter-return.md | 1 + docs/src/rules/no-const-assign.md | 1 + docs/src/rules/no-dupe-args.md | 1 + docs/src/rules/no-dupe-class-members.md | 3 +-- docs/src/rules/no-dupe-keys.md | 1 + docs/src/rules/no-func-assign.md | 1 + docs/src/rules/no-import-assign.md | 2 ++ docs/src/rules/no-invalid-this.md | 2 ++ docs/src/rules/no-new-symbol.md | 1 + docs/src/rules/no-obj-calls.md | 1 + docs/src/rules/no-redeclare.md | 2 ++ docs/src/rules/no-setter-return.md | 1 + docs/src/rules/no-this-before-super.md | 1 + docs/src/rules/no-undef.md | 1 + docs/src/rules/no-unreachable.md | 1 + docs/src/rules/no-unsafe-negation.md | 1 + 18 files changed, 39 insertions(+), 6 deletions(-) diff --git a/docs/src/_includes/layouts/doc.html b/docs/src/_includes/layouts/doc.html index 4050a901063d..58d8986a5dcc 100644 --- a/docs/src/_includes/layouts/doc.html +++ b/docs/src/_includes/layouts/doc.html @@ -19,6 +19,22 @@ {% set added_version = rule_versions.added[title] %} {% set removed_version = rule_versions.removed[title] %} + {% if handled_by_typescript %} + {% set handled_by_typescript_content %} +

Handled by TypeScript

+

+ It is safe to disable this rule when using TypeScript because TypeScript's compiler enforces this check. +

+ {% if extra_typescript_info %} +

+ {{ extra_typescript_info | markdown | safe }} +

+ {% endif %} + {% endset %} + + {% set all_content = [all_content, handled_by_typescript_content] | join %} + {% endif %} + {% if related_rules %} {% set related_rules_content %} @@ -48,7 +64,7 @@

Further Reading

{% set all_content = [all_content, further_reading_content] | join %} {% endif %} - + {% if rule_meta %} {% set resources_content %}

Resources

@@ -76,7 +92,7 @@

{{ title }}

{% endif %} {% include 'components/docs-toc.html' %} - + {{ all_content | safe }} @@ -102,6 +118,7 @@

{{ title }}

{% include "partials/docs-footer.html" %} + diff --git a/docs/src/rules/constructor-super.md b/docs/src/rules/constructor-super.md index c172b0a7c279..7c19df77dbab 100644 --- a/docs/src/rules/constructor-super.md +++ b/docs/src/rules/constructor-super.md @@ -1,6 +1,7 @@ --- title: constructor-super rule_type: problem +handled_by_typescript: true --- Constructors of derived classes must call `super()`. @@ -69,5 +70,3 @@ class A extends B { ## When Not To Use It If you don't want to be notified about invalid/missing `super()` callings in constructors, you can safely disable this rule. - -It is safe to disable this rule when using TypeScript because TypeScript's compiler enforces this check (`ts(2335) & ts(2377)`). diff --git a/docs/src/rules/getter-return.md b/docs/src/rules/getter-return.md index 0c8937d14ccb..9d316303d025 100644 --- a/docs/src/rules/getter-return.md +++ b/docs/src/rules/getter-return.md @@ -1,6 +1,7 @@ --- title: getter-return rule_type: problem +handled_by_typescript: true further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get - https://leanpub.com/understandinges6/read/#leanpub-auto-accessor-properties diff --git a/docs/src/rules/no-const-assign.md b/docs/src/rules/no-const-assign.md index f9f0ed172bb8..ca62132a757e 100644 --- a/docs/src/rules/no-const-assign.md +++ b/docs/src/rules/no-const-assign.md @@ -1,6 +1,7 @@ --- title: no-const-assign rule_type: problem +handled_by_typescript: true --- diff --git a/docs/src/rules/no-dupe-args.md b/docs/src/rules/no-dupe-args.md index 79f791c4666c..3cb9133c83fa 100644 --- a/docs/src/rules/no-dupe-args.md +++ b/docs/src/rules/no-dupe-args.md @@ -1,6 +1,7 @@ --- title: no-dupe-args rule_type: problem +handled_by_typescript: true --- diff --git a/docs/src/rules/no-dupe-class-members.md b/docs/src/rules/no-dupe-class-members.md index d50a7fe74e11..216f3c9fecd3 100644 --- a/docs/src/rules/no-dupe-class-members.md +++ b/docs/src/rules/no-dupe-class-members.md @@ -1,6 +1,7 @@ --- title: no-dupe-class-members rule_type: problem +handled_by_typescript: true --- @@ -101,5 +102,3 @@ class Foo { This rule should not be used in ES3/5 environments. In ES2015 (ES6) or later, if you don't want to be notified about duplicate names in class members, you can safely disable this rule. - -It is safe to disable this rule when using TypeScript because TypeScript's compiler enforces this check (`ts(2300) & ts(2393)`). diff --git a/docs/src/rules/no-dupe-keys.md b/docs/src/rules/no-dupe-keys.md index 75fc9491fb62..1527bf8f1ec7 100644 --- a/docs/src/rules/no-dupe-keys.md +++ b/docs/src/rules/no-dupe-keys.md @@ -1,6 +1,7 @@ --- title: no-dupe-keys rule_type: problem +handled_by_typescript: true --- diff --git a/docs/src/rules/no-func-assign.md b/docs/src/rules/no-func-assign.md index ffbcb46c6b81..a0f146203742 100644 --- a/docs/src/rules/no-func-assign.md +++ b/docs/src/rules/no-func-assign.md @@ -1,6 +1,7 @@ --- title: no-func-assign rule_type: problem +handled_by_typescript: true --- diff --git a/docs/src/rules/no-import-assign.md b/docs/src/rules/no-import-assign.md index ca4b912de817..b0a7432bb801 100644 --- a/docs/src/rules/no-import-assign.md +++ b/docs/src/rules/no-import-assign.md @@ -1,6 +1,8 @@ --- title: no-import-assign rule_type: problem +handled_by_typescript: true +extra_typescript_info: Note that the compiler will not catch the `Object.assign()` case. Thus, if you use `Object.assign()` in your codebase, this rule will still provide some value. --- diff --git a/docs/src/rules/no-invalid-this.md b/docs/src/rules/no-invalid-this.md index f3aa6ed763ed..9e4a2aedffae 100644 --- a/docs/src/rules/no-invalid-this.md +++ b/docs/src/rules/no-invalid-this.md @@ -1,6 +1,8 @@ --- title: no-invalid-this rule_type: suggestion +handled_by_typescript: true +extra_typescript_info: Note that, technically, TypeScript will only catch this if you have the `strict` or `noImplicitThis` flags enabled. These are enabled in most TypeScript projects, since they are considered to be best practice. --- diff --git a/docs/src/rules/no-new-symbol.md b/docs/src/rules/no-new-symbol.md index 44c34a4eef00..d557811f30c4 100644 --- a/docs/src/rules/no-new-symbol.md +++ b/docs/src/rules/no-new-symbol.md @@ -1,6 +1,7 @@ --- title: no-new-symbol rule_type: problem +handled_by_typescript: true further_reading: - https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-objects --- diff --git a/docs/src/rules/no-obj-calls.md b/docs/src/rules/no-obj-calls.md index 8d72e4ce2770..2fde92e5cbf5 100644 --- a/docs/src/rules/no-obj-calls.md +++ b/docs/src/rules/no-obj-calls.md @@ -1,6 +1,7 @@ --- title: no-obj-calls rule_type: problem +handled_by_typescript: true further_reading: - https://es5.github.io/#x15.8 --- diff --git a/docs/src/rules/no-redeclare.md b/docs/src/rules/no-redeclare.md index e66f0570fc66..009ba889fcc3 100644 --- a/docs/src/rules/no-redeclare.md +++ b/docs/src/rules/no-redeclare.md @@ -1,6 +1,8 @@ --- title: no-redeclare rule_type: suggestion +handled_by_typescript: true +extra_typescript_info: Note that while TypeScript will catch `let` redeclares and `const` redeclares, it will not catch `var` redeclares. Thus, if you use the legacy `var` keyword in your TypeScript codebase, this rule will still provide some value. related_rules: - no-shadow --- diff --git a/docs/src/rules/no-setter-return.md b/docs/src/rules/no-setter-return.md index ceb4558c15ac..50353c754dab 100644 --- a/docs/src/rules/no-setter-return.md +++ b/docs/src/rules/no-setter-return.md @@ -1,6 +1,7 @@ --- title: no-setter-return rule_type: problem +handled_by_typescript: true related_rules: - getter-return further_reading: diff --git a/docs/src/rules/no-this-before-super.md b/docs/src/rules/no-this-before-super.md index f1425b5ed35b..c1a654796cb6 100644 --- a/docs/src/rules/no-this-before-super.md +++ b/docs/src/rules/no-this-before-super.md @@ -1,6 +1,7 @@ --- title: no-this-before-super rule_type: problem +handled_by_typescript: true --- diff --git a/docs/src/rules/no-undef.md b/docs/src/rules/no-undef.md index 0bc7a28279fb..72ea516f966b 100644 --- a/docs/src/rules/no-undef.md +++ b/docs/src/rules/no-undef.md @@ -1,6 +1,7 @@ --- title: no-undef rule_type: problem +handled_by_typescript: true related_rules: - no-global-assign - no-redeclare diff --git a/docs/src/rules/no-unreachable.md b/docs/src/rules/no-unreachable.md index 4f762084b41e..15f77f8173ee 100644 --- a/docs/src/rules/no-unreachable.md +++ b/docs/src/rules/no-unreachable.md @@ -1,6 +1,7 @@ --- title: no-unreachable rule_type: problem +handled_by_typescript: true --- diff --git a/docs/src/rules/no-unsafe-negation.md b/docs/src/rules/no-unsafe-negation.md index 522e4ab4d1a6..f3b495223168 100644 --- a/docs/src/rules/no-unsafe-negation.md +++ b/docs/src/rules/no-unsafe-negation.md @@ -1,6 +1,7 @@ --- title: no-unsafe-negation rule_type: problem +handled_by_typescript: true --- From acb7df35b9a7485f26bc6b3e1f9083d1c585dce9 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Sat, 2 Sep 2023 22:00:32 +0530 Subject: [PATCH 161/248] feat: add new `enforce` option to `lines-between-class-members` (#17462) * feat: add new \`enforce\` option (`lines-between-class-members`) * test: add cases for enforce with exceptAfterSingleLine option * docs: add `enforce` option * docs: fix example * fix: update schema to make enforce option required * refactor: remove redundant if condition * refactor: remove redundant if condition * docs: add suggestions Co-authored-by: Milos Djermanovic * test: add cases where multiple config objects match a pair --------- Co-authored-by: Milos Djermanovic --- docs/src/rules/lines-between-class-members.md | 185 +- lib/rules/lines-between-class-members.js | 99 +- .../lib/rules/lines-between-class-members.js | 2471 ++++++++++++++++- 3 files changed, 2714 insertions(+), 41 deletions(-) diff --git a/docs/src/rules/lines-between-class-members.md b/docs/src/rules/lines-between-class-members.md index 8daf2a2f78db..55627501096a 100644 --- a/docs/src/rules/lines-between-class-members.md +++ b/docs/src/rules/lines-between-class-members.md @@ -69,14 +69,19 @@ class MyClass { ### Options -This rule has a string option and an object option. +This rule has two options, first option can be string or object, second option is object. -String option: +First option can be string `"always"` or `"never"` or an object with a property named `enforce`: * `"always"`(default) require an empty line after class members * `"never"` disallows an empty line after class members +* `Object`: An object with a property named `enforce`. The enforce property should be an array of objects, each specifying the configuration for enforcing empty lines between specific pairs of class members. + * **enforce**: You can supply any number of configurations. If a member pair matches multiple configurations, the last matched configuration will be used. If a member pair does not match any configurations, it will be ignored. Each object should have the following properties: + * **blankLine**: Can be set to either `"always"` or `"never"`, indicating whether a blank line should be required or disallowed between the specified members. + * **prev**: Specifies the type of the preceding class member. It can be `"method"` for class methods, `"field"` for class fields, or `"*"` for any class member. + * **next**: Specifies the type of the following class member. It follows the same options as `prev`. -Object option: +Second option is an object with a property named `exceptAfterSingleLine`: * `"exceptAfterSingleLine": false`(default) **do not** skip checking empty lines after single-line class members * `"exceptAfterSingleLine": true` skip checking empty lines after single-line class members @@ -129,6 +134,146 @@ class Foo{ ::: +Examples of **incorrect** code for this rule with the array of configurations option: + +::: incorrect + +```js +// disallows blank lines between methods +/*eslint lines-between-class-members: [ + "error", + { + enforce: [ + { blankLine: "never", prev: "method", next: "method" } + ] + }, +]*/ + +class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} +} +``` + +::: + +::: incorrect + +```js +// requires blank lines around fields, disallows blank lines between methods +/*eslint lines-between-class-members: [ + "error", + { + enforce: [ + { blankLine: "always", prev: "*", next: "field" }, + { blankLine: "always", prev: "field", next: "*" }, + { blankLine: "never", prev: "method", next: "method" } + ] + }, +]*/ + +class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + + get area() { + return this.method1(); + } + + method2() {} +} +``` + +::: + +Examples of **correct** code for this rule with the array of configurations option: + +::: correct + +```js +// disallows blank lines between methods +/*eslint lines-between-class-members: [ + "error", + { + enforce: [ + { blankLine: "never", prev: "method", next: "method" } + ] + }, +]*/ + +class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} +} +``` + +::: + +::: correct + +```js +// requires blank lines around fields, disallows blank lines between methods +/*eslint lines-between-class-members: [ + "error", + { + enforce: [ + { blankLine: "always", prev: "*", next: "field" }, + { blankLine: "always", prev: "field", next: "*" }, + { blankLine: "never", prev: "method", next: "method" } + ] + }, +]*/ + +class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} +} +``` + +::: + Examples of **correct** code for this rule with the object option: ::: correct @@ -148,6 +293,40 @@ class Foo{ ::: +::: correct + +```js +/*eslint lines-between-class-members: [ + "error", + { + enforce: [ + { blankLine: "always", prev: "*", next: "method" }, + { blankLine: "always", prev: "method", next: "*" }, + { blankLine: "always", prev: "field", next: "field" } + ] + }, + { exceptAfterSingleLine: true } +]*/ + +class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + + method2() {} +} +``` + +::: + ## When Not To Use It If you don't want to enforce empty lines between class members, you can disable this rule. diff --git a/lib/rules/lines-between-class-members.js b/lib/rules/lines-between-class-members.js index dee4bab5f54b..3d0a5e6738e9 100644 --- a/lib/rules/lines-between-class-members.js +++ b/lib/rules/lines-between-class-members.js @@ -10,6 +10,21 @@ const astUtils = require("./utils/ast-utils"); +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Types of class members. + * Those have `test` method to check it matches to the given class member. + * @private + */ +const ClassMemberTypes = { + "*": { test: () => true }, + field: { test: node => node.type === "PropertyDefinition" }, + method: { test: node => node.type === "MethodDefinition" } +}; + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -29,7 +44,32 @@ module.exports = { schema: [ { - enum: ["always", "never"] + anyOf: [ + { + type: "object", + properties: { + enforce: { + type: "array", + items: { + type: "object", + properties: { + blankLine: { enum: ["always", "never"] }, + prev: { enum: ["method", "field", "*"] }, + next: { enum: ["method", "field", "*"] } + }, + additionalProperties: false, + required: ["blankLine", "prev", "next"] + }, + minItems: 1 + } + }, + additionalProperties: false, + required: ["enforce"] + }, + { + enum: ["always", "never"] + } + ] }, { type: "object", @@ -55,6 +95,7 @@ module.exports = { options[0] = context.options[0] || "always"; options[1] = context.options[1] || { exceptAfterSingleLine: false }; + const configureList = typeof options[0] === "object" ? options[0].enforce : [{ blankLine: options[0], prev: "*", next: "*" }]; const sourceCode = context.sourceCode; /** @@ -144,6 +185,38 @@ module.exports = { return sourceCode.getTokensBetween(before, after, { includeComments: true }).length !== 0; } + /** + * Checks whether the given node matches the given type. + * @param {ASTNode} node The class member node to check. + * @param {string} type The class member type to check. + * @returns {boolean} `true` if the class member node matched the type. + * @private + */ + function match(node, type) { + return ClassMemberTypes[type].test(node); + } + + /** + * Finds the last matched configuration from the configureList. + * @param {ASTNode} prevNode The previous node to match. + * @param {ASTNode} nextNode The current node to match. + * @returns {string|null} Padding type or `null` if no matches were found. + * @private + */ + function getPaddingType(prevNode, nextNode) { + for (let i = configureList.length - 1; i >= 0; --i) { + const configure = configureList[i]; + const matched = + match(prevNode, configure.prev) && + match(nextNode, configure.next); + + if (matched) { + return configure.blankLine; + } + } + return null; + } + return { ClassBody(node) { const body = node.body; @@ -158,22 +231,34 @@ module.exports = { const isPadded = afterPadding.loc.start.line - beforePadding.loc.end.line > 1; const hasTokenInPadding = hasTokenOrCommentBetween(beforePadding, afterPadding); const curLineLastToken = findLastConsecutiveTokenAfter(curLast, nextFirst, 0); + const paddingType = getPaddingType(body[i], body[i + 1]); + + if (paddingType === "never" && isPadded) { + context.report({ + node: body[i + 1], + messageId: "never", - if ((options[0] === "always" && !skip && !isPadded) || - (options[0] === "never" && isPadded)) { + fix(fixer) { + if (hasTokenInPadding) { + return null; + } + return fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n"); + } + }); + } else if (paddingType === "always" && !skip && !isPadded) { context.report({ node: body[i + 1], - messageId: isPadded ? "never" : "always", + messageId: "always", + fix(fixer) { if (hasTokenInPadding) { return null; } - return isPadded - ? fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n") - : fixer.insertTextAfter(curLineLastToken, "\n"); + return fixer.insertTextAfter(curLineLastToken, "\n"); } }); } + } } }; diff --git a/tests/lib/rules/lines-between-class-members.js b/tests/lib/rules/lines-between-class-members.js index feb9c085e37e..2ee17f713c3a 100644 --- a/tests/lib/rules/lines-between-class-members.js +++ b/tests/lib/rules/lines-between-class-members.js @@ -50,24 +50,810 @@ ruleTester.run("lines-between-class-members", rule, { "class C {\naaa;\n\n#bbb;\n\nccc(){}\n\n#ddd(){}\n}", { code: "class foo{ bar(){}\nbaz(){}}", options: ["never"] }, - { code: "class foo{ bar(){}\n/*comments*/baz(){}}", options: ["never"] }, - { code: "class foo{ bar(){}\n//comments\nbaz(){}}", options: ["never"] }, - { code: "class foo{ bar(){}/* comments\n\n*/baz(){}}", options: ["never"] }, - { code: "class foo{ bar(){}/* \ncomments\n*/baz(){}}", options: ["never"] }, - { code: "class foo{ bar(){}\n/* \ncomments\n*/\nbaz(){}}", options: ["never"] }, + { + code: "class foo{ bar(){}\n/*comments*/baz(){}}", + options: ["never"] + }, + { + code: "class foo{ bar(){}\n//comments\nbaz(){}}", + options: ["never"] + }, + { + code: "class foo{ bar(){}/* comments\n\n*/baz(){}}", + options: ["never"] + }, + { + code: "class foo{ bar(){}/* \ncomments\n*/baz(){}}", + options: ["never"] + }, + { + code: "class foo{ bar(){}\n/* \ncomments\n*/\nbaz(){}}", + options: ["never"] + }, { code: "class foo{ bar(){}\n\nbaz(){}}", options: ["always"] }, - { code: "class foo{ bar(){}\n\n/*comments*/baz(){}}", options: ["always"] }, - { code: "class foo{ bar(){}\n\n//comments\nbaz(){}}", options: ["always"] }, + { + code: "class foo{ bar(){}\n\n/*comments*/baz(){}}", + options: ["always"] + }, + { + code: "class foo{ bar(){}\n\n//comments\nbaz(){}}", + options: ["always"] + }, - { code: "class foo{ bar(){}\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] }, - { code: "class foo{ bar(){\n}\n\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] }, - { code: "class foo{\naaa;\n#bbb;\nccc(){\n}\n\n#ddd(){\n}\n}", options: ["always", { exceptAfterSingleLine: true }] }, + { + code: "class foo{ bar(){}\nbaz(){}}", + options: ["always", { exceptAfterSingleLine: true }] + }, + { + code: "class foo{ bar(){\n}\n\nbaz(){}}", + options: ["always", { exceptAfterSingleLine: true }] + }, + { + code: "class foo{\naaa;\n#bbb;\nccc(){\n}\n\n#ddd(){\n}\n}", + options: ["always", { exceptAfterSingleLine: true }] + }, // semicolon-less style (semicolons are at the beginning of lines) { code: "class C { foo\n\n;bar }", options: ["always"] }, - { code: "class C { foo\n;bar }", options: ["always", { exceptAfterSingleLine: true }] }, - { code: "class C { foo\n;bar }", options: ["never"] } + { + code: "class C { foo\n;bar }", + options: ["always", { exceptAfterSingleLine: true }] + }, + { code: "class C { foo\n;bar }", options: ["never"] }, + + // enforce option with blankLine: "always" + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "method", next: "method" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "method", next: "method" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "method", next: "field" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "method", next: "field" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "method", next: "*" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "field", next: "method" } + ] + } + ] + }, + + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "field", next: "field" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "field", next: "*" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "*", next: "method" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "*", next: "field" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { enforce: [{ blankLine: "always", prev: "*", next: "*" }] } + ] + }, + + // enforce option - blankLine: "never" + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "method", next: "method" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "method", next: "method" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "method", next: "field" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "method", next: "field" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "method", next: "*" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "field", next: "method" } + ] + } + ] + }, + + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "field", next: "field" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [{ blankLine: "never", prev: "field", next: "*" }] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "*", next: "method" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [{ blankLine: "never", prev: "*", next: "field" }] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { enforce: [{ blankLine: "never", prev: "*", next: "*" }] } + ] + }, + + // enforce option - multiple configurations + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + + // requires blank lines around methods, disallows blank lines between fields + enforce: [ + { blankLine: "always", prev: "*", next: "method" }, + { blankLine: "always", prev: "method", next: "*" }, + { blankLine: "never", prev: "field", next: "field" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + + // requires blank lines around fields, disallows blank lines between methods + enforce: [ + { blankLine: "always", prev: "*", next: "field" }, + { blankLine: "always", prev: "field", next: "*" }, + { blankLine: "never", prev: "method", next: "method" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + + // requires blank lines around methods and fields + enforce: [ + { blankLine: "always", prev: "*", next: "method" }, + { blankLine: "always", prev: "method", next: "*" }, + { blankLine: "always", prev: "field", next: "field" } + ] + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + + // requires blank lines around methods and fields + enforce: [ + { blankLine: "never", prev: "*", next: "method" }, + { blankLine: "never", prev: "method", next: "*" }, + { blankLine: "never", prev: "field", next: "field" }, + + // This should take precedence over the above + { blankLine: "always", prev: "*", next: "method" }, + { blankLine: "always", prev: "method", next: "*" }, + { blankLine: "always", prev: "field", next: "field" } + ] + } + ] + }, + + // enforce with exceptAfterSingleLine option + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + + // requires blank lines around methods and fields + enforce: [ + { blankLine: "always", prev: "*", next: "method" }, + { blankLine: "always", prev: "method", next: "*" }, + { blankLine: "always", prev: "field", next: "field" } + ] + }, + { + exceptAfterSingleLine: true + } + ] + } ], invalid: [ { @@ -75,97 +861,116 @@ ruleTester.run("lines-between-class-members", rule, { output: "class foo{ bar(){}\n\nbaz(){}}", options: ["always"], errors: [alwaysError] - }, { + }, + { code: "class foo{ bar(){}\n\nbaz(){}}", output: "class foo{ bar(){}\nbaz(){}}", options: ["never"], errors: [neverError] - }, { + }, + { code: "class foo{ bar(){\n}\nbaz(){}}", output: "class foo{ bar(){\n}\n\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }], errors: [alwaysError] - }, { + }, + { code: "class foo{ bar(){\n}\n/* comment */\nbaz(){}}", output: "class foo{ bar(){\n}\n\n/* comment */\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }], errors: [alwaysError] - }, { + }, + { code: "class foo{ bar(){}\n\n// comment\nbaz(){}}", output: "class foo{ bar(){}\n// comment\nbaz(){}}", options: ["never"], errors: [neverError] - }, { + }, + { code: "class foo{ bar(){}\n\n/* comment */\nbaz(){}}", output: "class foo{ bar(){}\n/* comment */\nbaz(){}}", options: ["never"], errors: [neverError] - }, { + }, + { code: "class foo{ bar(){}\n/* comment-1 */\n\n/* comment-2 */\nbaz(){}}", output: "class foo{ bar(){}\n/* comment-1 */\n/* comment-2 */\nbaz(){}}", options: ["never"], errors: [neverError] - }, { + }, + { code: "class foo{ bar(){}\n\n/* comment */\n\nbaz(){}}", output: null, options: ["never"], errors: [neverError] - }, { + }, + { code: "class foo{ bar(){}\n\n// comment\n\nbaz(){}}", output: null, options: ["never"], errors: [neverError] - }, { + }, + { code: "class foo{ bar(){}\n/* comment-1 */\n\n/* comment-2 */\n\n/* comment-3 */\nbaz(){}}", output: null, options: ["never"], errors: [neverError] - }, { + }, + { code: "class foo{ bar(){}\n/* comment-1 */\n\n;\n\n/* comment-3 */\nbaz(){}}", output: null, options: ["never"], errors: [neverError] - }, { + }, + { code: "class A {\nfoo() {}// comment\n;\n/* comment */\nbar() {}\n}", output: "class A {\nfoo() {}// comment\n\n;\n/* comment */\nbar() {}\n}", options: ["always"], errors: [alwaysError] - }, { + }, + { code: "class A {\nfoo() {}\n/* comment */;\n;\n/* comment */\nbar() {}\n}", output: "class A {\nfoo() {}\n\n/* comment */;\n;\n/* comment */\nbar() {}\n}", options: ["always"], errors: [alwaysError] - }, { + }, + { code: "class foo{ bar(){};\nbaz(){}}", output: "class foo{ bar(){};\n\nbaz(){}}", options: ["always"], errors: [alwaysError] - }, { + }, + { code: "class foo{ bar(){} // comment \nbaz(){}}", output: "class foo{ bar(){} // comment \n\nbaz(){}}", options: ["always"], errors: [alwaysError] - }, { + }, + { code: "class A {\nfoo() {}\n/* comment */;\n;\nbar() {}\n}", output: "class A {\nfoo() {}\n\n/* comment */;\n;\nbar() {}\n}", options: ["always"], errors: [alwaysError] - }, { + }, + { code: "class C {\nfield1\nfield2\n}", output: "class C {\nfield1\n\nfield2\n}", options: ["always"], errors: [alwaysError] - }, { + }, + { code: "class C {\n#field1\n#field2\n}", output: "class C {\n#field1\n\n#field2\n}", options: ["always"], errors: [alwaysError] - }, { + }, + { code: "class C {\nfield1\n\nfield2\n}", output: "class C {\nfield1\nfield2\n}", options: ["never"], errors: [neverError] - }, { + }, + { code: "class C {\nfield1 = () => {\n}\nfield2\nfield3\n}", output: "class C {\nfield1 = () => {\n}\n\nfield2\nfield3\n}", options: ["always", { exceptAfterSingleLine: true }], @@ -208,6 +1013,1610 @@ ruleTester.run("lines-between-class-members", rule, { output: "class C { foo\n\n;;bar }", options: ["always"], errors: [alwaysError] + }, + + // enforce option with blankLine: "always" + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "method", next: "method" } + ] + } + ], + errors: [ + { + messageId: "always", + line: 11, + column: 17 + }, + { + messageId: "always", + line: 14, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "method", next: "method" } + ] + } + ], + errors: [ + { + messageId: "always", + line: 13, + column: 17 + }, + { + messageId: "always", + line: 16, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "method", next: "field" } + ] + } + ], + errors: [ + { + messageId: "always", + line: 7, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "method", next: "field" } + ] + } + ], + errors: [ + { + messageId: "always", + line: 7, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "method", next: "*" } + ] + } + ], + errors: [ + { + messageId: "always", + line: 7, + column: 17 + }, + { + messageId: "always", + line: 11, + column: 17 + }, + { + messageId: "always", + line: 14, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "field", next: "method" } + ] + } + ], + errors: [ + { + messageId: "always", + line: 9, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "field", next: "field" } + ] + } + ], + errors: [ + { + messageId: "always", + line: 8, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "field", next: "*" } + ] + } + ], + errors: [ + { + messageId: "always", + line: 8, + column: 17 + }, + { + messageId: "always", + line: 9, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "*", next: "method" } + ] + } + ], + errors: [ + { + messageId: "always", + line: 9, + column: 17 + }, + { + messageId: "always", + line: 10, + column: 17 + }, + { + messageId: "always", + line: 13, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "always", prev: "*", next: "field" } + ] + } + ], + errors: [ + { + messageId: "always", + line: 7, + column: 17 + }, + { + messageId: "always", + line: 8, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { enforce: [{ blankLine: "always", prev: "*", next: "*" }] } + ], + errors: [ + { + messageId: "always", + line: 7, + column: 17 + }, + { + messageId: "always", + line: 8, + column: 17 + }, + { + messageId: "always", + line: 9, + column: 17 + }, + { + messageId: "always", + line: 10, + column: 17 + }, + { + messageId: "always", + line: 13, + column: 17 + } + ] + }, + + // enforce option - blankLine: "never" + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} +get area() { + return this.method1(); + } +method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "method", next: "method" } + ] + } + ], + errors: [ + { + messageId: "never", + line: 11, + column: 17 + }, + { + messageId: "never", + line: 15, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} +get area() { + return this.method1(); + } +method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "method", next: "method" } + ] + } + ], + errors: [ + { + messageId: "never", + line: 14, + column: 17 + }, + { + messageId: "never", + line: 18, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } +fieldA = 'Field A'; + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "method", next: "field" } + ] + } + ], + errors: [ + { + messageId: "never", + line: 8, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } +fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "method", next: "field" } + ] + } + ], + errors: [ + { + messageId: "never", + line: 8, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } +fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} +get area() { + return this.method1(); + } +method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "method", next: "*" } + ] + } + ], + errors: [ + { + messageId: "never", + line: 8, + column: 17 + }, + { + messageId: "never", + line: 14, + column: 17 + }, + { + messageId: "never", + line: 18, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; +method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "field", next: "method" } + ] + } + ], + errors: [ + { + messageId: "never", + line: 12, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; +#fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "field", next: "field" } + ] + } + ], + errors: [ + { + messageId: "never", + line: 10, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; +#fieldB = 'Field B'; +method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "field", next: "*" } + ] + } + ], + errors: [ + { + messageId: "never", + line: 10, + column: 17 + }, + { + messageId: "never", + line: 12, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; +method1() {} +get area() { + return this.method1(); + } +method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "*", next: "method" } + ] + } + ], + errors: [ + { + messageId: "never", + line: 12, + column: 17 + }, + { + messageId: "never", + line: 14, + column: 17 + }, + { + messageId: "never", + line: 18, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } +fieldA = 'Field A'; +#fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + method2() {} + } + `, + options: [ + { + enforce: [ + { blankLine: "never", prev: "*", next: "field" } + ] + } + ], + errors: [ + { + messageId: "never", + line: 8, + column: 17 + }, + { + messageId: "never", + line: 10, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } +fieldA = 'Field A'; +#fieldB = 'Field B'; +method1() {} +get area() { + return this.method1(); + } +method2() {} + } + `, + options: [ + { enforce: [{ blankLine: "never", prev: "*", next: "*" }] } + ], + errors: [ + { + messageId: "never", + line: 8, + column: 17 + }, + { + messageId: "never", + line: 10, + column: 17 + }, + { + messageId: "never", + line: 12, + column: 17 + }, + { + messageId: "never", + line: 14, + column: 17 + }, { + messageId: "never", + line: 18, + column: 17 + } + ] + }, + + // enforce option - multiple configurations + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; +#fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + + // requires blank lines around methods, disallows blank lines between fields + enforce: [ + { blankLine: "always", prev: "*", next: "method" }, + { blankLine: "always", prev: "method", next: "*" }, + { blankLine: "never", prev: "field", next: "field" } + ] + } + ], + errors: [ + { + messageId: "always", + line: 7, + column: 17 + }, + { + messageId: "never", + line: 9, + column: 17 + }, + { + messageId: "always", + line: 10, + column: 17 + }, + { + messageId: "always", + line: 11, + column: 17 + }, { + messageId: "always", + line: 14, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} +get area() { + return this.method1(); + } +method2() {} + } + `, + options: [ + { + + // requires blank lines around fields, disallows blank lines between methods + enforce: [ + { blankLine: "always", prev: "*", next: "field" }, + { blankLine: "always", prev: "field", next: "*" }, + { blankLine: "never", prev: "method", next: "method" } + ] + } + ], + errors: [ + { + messageId: "always", + line: 7, + column: 17 + }, + { + messageId: "always", + line: 8, + column: 17 + }, + { + messageId: "always", + line: 9, + column: 17 + }, + { + messageId: "never", + line: 11, + column: 17 + }, { + messageId: "never", + line: 15, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + + // requires blank lines around methods and fields + enforce: [ + { blankLine: "always", prev: "*", next: "method" }, + { blankLine: "always", prev: "method", next: "*" }, + { blankLine: "always", prev: "field", next: "field" } + ] + } + ], + errors: [ + { + messageId: "always", + line: 7, + column: 17 + }, + { + messageId: "always", + line: 8, + column: 17 + }, + { + messageId: "always", + line: 9, + column: 17 + }, + { + messageId: "always", + line: 10, + column: 17 + }, { + messageId: "always", + line: 13, + column: 17 + } + ] + }, + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + + #fieldB = 'Field B'; + + method1() {} + + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + + // requires blank lines around methods and fields + enforce: [ + { blankLine: "never", prev: "*", next: "method" }, + { blankLine: "never", prev: "method", next: "*" }, + { blankLine: "never", prev: "field", next: "field" }, + + // This should take precedence over the above + { blankLine: "always", prev: "*", next: "method" }, + { blankLine: "always", prev: "method", next: "*" }, + { blankLine: "always", prev: "field", next: "field" } + ] + } + ], + errors: [ + { + messageId: "always", + line: 7, + column: 17 + }, + { + messageId: "always", + line: 8, + column: 17 + }, + { + messageId: "always", + line: 9, + column: 17 + }, + { + messageId: "always", + line: 10, + column: 17 + }, { + messageId: "always", + line: 13, + column: 17 + } + ] + }, + + // enforce with exceptAfterSingleLine option + { + code: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + method2() {} + } + `, + output: ` + class MyClass { + constructor(height, width) { + this.height = height; + this.width = width; + } + + fieldA = 'Field A'; + #fieldB = 'Field B'; + method1() {} + get area() { + return this.method1(); + } + + method2() {} + } + `, + options: [ + { + + // requires blank lines around methods and fields + enforce: [ + { blankLine: "always", prev: "*", next: "method" }, + { blankLine: "always", prev: "method", next: "*" }, + { blankLine: "always", prev: "field", next: "field" } + ] + }, + { + exceptAfterSingleLine: true + } + ], + errors: [ + { + messageId: "always", + line: 7, + column: 17 + }, + { + messageId: "always", + line: 13, + column: 17 + } + ] } ] }); From 32b2327aafdd3b911fabab69ed75c9ff97658c60 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Sat, 2 Sep 2023 15:00:31 -0400 Subject: [PATCH 162/248] feat: Emit deprecation warnings in RuleTester (#17527) * feat: Emit deprecation warnings in RuleTester Emits deprecation warnings when using methods on `context` that are deprecated. Refs #17520 * Revert flat-rule-tester * Fix linting error --- lib/rule-tester/rule-tester.js | 62 +++++++++++++++++++++++++- tests/lib/rule-tester/rule-tester.js | 66 ++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index 030a986867f3..c9c18664528c 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -164,6 +164,30 @@ const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters const hasOwnProperty = Function.call.bind(Object.hasOwnProperty); +const DEPRECATED_SOURCECODE_PASSTHROUGHS = { + getSource: "getText", + getSourceLines: "getLines", + getAllComments: "getAllComments", + getNodeByRangeIndex: "getNodeByRangeIndex", + + // getComments: "getComments", -- already handled by a separate error + getCommentsBefore: "getCommentsBefore", + getCommentsAfter: "getCommentsAfter", + getCommentsInside: "getCommentsInside", + getJSDocComment: "getJSDocComment", + getFirstToken: "getFirstToken", + getFirstTokens: "getFirstTokens", + getLastToken: "getLastToken", + getLastTokens: "getLastTokens", + getTokenAfter: "getTokenAfter", + getTokenBefore: "getTokenBefore", + getTokenByRangeStart: "getTokenByRangeStart", + getTokens: "getTokens", + getTokensAfter: "getTokensAfter", + getTokensBefore: "getTokensBefore", + getTokensBetween: "getTokensBetween" +}; + /** * Clones a given value deeply. * Note: This ignores `parent` property. @@ -335,6 +359,22 @@ function emitMissingSchemaWarning(ruleName) { } } +/** + * Emit a deprecation warning if a rule uses a deprecated `context` method. + * @param {string} ruleName Name of the rule. + * @param {string} methodName The name of the method on `context` that was used. + * @returns {void} + */ +function emitDeprecatedContextMethodWarning(ruleName, methodName) { + if (!emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`]) { + emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`] = true; + process.emitWarning( + `"${ruleName}" rule is using \`context.${methodName}()\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.${DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]}()\` instead.`, + "DeprecationWarning" + ); + } +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -566,7 +606,27 @@ class RuleTester { freezeDeeply(context.settings); freezeDeeply(context.parserOptions); - return (typeof rule === "function" ? rule : rule.create)(context); + const newContext = Object.freeze( + Object.create( + context, + Object.fromEntries(Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).map(methodName => [ + methodName, + { + value(...args) { + + // emit deprecation warning + emitDeprecatedContextMethodWarning(ruleName, methodName); + + // call the original method + return context[methodName].call(this, ...args); + }, + enumerable: true + } + ])) + ) + ); + + return (typeof rule === "function" ? rule : rule.create)(newContext); } })); diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js index c607e4476a02..e3c3d5a0061b 100644 --- a/tests/lib/rule-tester/rule-tester.js +++ b/tests/lib/rule-tester/rule-tester.js @@ -2489,6 +2489,72 @@ describe("RuleTester", () => { assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); }); + + Object.entries({ + getSource: "getText", + getSourceLines: "getLines", + getAllComments: "getAllComments", + getNodeByRangeIndex: "getNodeByRangeIndex", + getCommentsBefore: "getCommentsBefore", + getCommentsAfter: "getCommentsAfter", + getCommentsInside: "getCommentsInside", + getJSDocComment: "getJSDocComment", + getFirstToken: "getFirstToken", + getFirstTokens: "getFirstTokens", + getLastToken: "getLastToken", + getLastTokens: "getLastTokens", + getTokenAfter: "getTokenAfter", + getTokenBefore: "getTokenBefore", + getTokenByRangeStart: "getTokenByRangeStart", + getTokens: "getTokens", + getTokensAfter: "getTokensAfter", + getTokensBefore: "getTokensBefore", + getTokensBetween: "getTokensBetween" + }).forEach(([methodName, replacementName]) => { + + + it(`should log a deprecation warning when calling \`context.${methodName}\``, () => { + const ruleToCheckDeprecation = { + meta: { + type: "problem", + schema: [] + }, + create(context) { + return { + Program(node) { + + // special case + if (methodName === "getTokensBetween") { + context[methodName](node, node); + } else { + context[methodName](node); + } + + context.report({ node, message: "bad" }); + } + }; + } + }; + + ruleTester.run("deprecated-method", ruleToCheckDeprecation, { + valid: [], + invalid: [ + { code: "var foo = bar;", options: [], errors: 1 } + ] + }); + + assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); + assert.deepStrictEqual( + processStub.getCall(0).args, + [ + `"deprecated-method" rule is using \`context.${methodName}()\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.${replacementName}()\` instead.`, + "DeprecationWarning" + ] + ); + }); + + }); + }); /** From 203a971c0abc3a95ae02ff74104a01e569707060 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 01:30:03 +0200 Subject: [PATCH 163/248] ci: bump actions/checkout from 3 to 4 (#17530) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/update-readme.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92e2a7eed35e..7602b8380378 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: name: Verify Files runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: 'lts/*' @@ -53,7 +53,7 @@ jobs: node: "lts/*" runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} @@ -68,7 +68,7 @@ jobs: name: Browser Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: '16' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e2995e66d404..637f06e2e517 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml index e6399920b51d..43b783d3fcad 100644 --- a/.github/workflows/update-readme.yml +++ b/.github/workflows/update-readme.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: token: ${{ secrets.WORKFLOW_PUSH_BOT_TOKEN }} From de86b3b2e58edd5826200c23255d8325abe375e1 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Wed, 6 Sep 2023 15:32:41 +0530 Subject: [PATCH 164/248] docs: update `no-promise-executor-return` examples (#17529) * docs: update `no-promise-executor-return` examples * docs: update example --- docs/src/rules/no-promise-executor-return.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/src/rules/no-promise-executor-return.md b/docs/src/rules/no-promise-executor-return.md index d82e44734771..f163d9ebf0b4 100644 --- a/docs/src/rules/no-promise-executor-return.md +++ b/docs/src/rules/no-promise-executor-return.md @@ -38,6 +38,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-promise-executor-return: "error"*/ +/*eslint-env es6*/ new Promise((resolve, reject) => { if (someCondition) { @@ -75,6 +76,7 @@ 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) => { @@ -123,6 +125,7 @@ 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) { From da09f4e641141f585ef611c6e9d63d4331054706 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 7 Sep 2023 18:02:32 -0400 Subject: [PATCH 165/248] feat: Implement onUnreachableCodePathStart/End (#17511) * feat: Implement onUnreachableCodePathStart/End refs # 17457 * Finish up onUnreachable* work * Refactor to account for out-of-order events * Update lib/rules/no-unreachable.js Co-authored-by: Milos Djermanovic * Update lib/rules/no-unreachable.js Co-authored-by: Milos Djermanovic * Update tests/lib/linter/code-path-analysis/code-path-analyzer.js Co-authored-by: Milos Djermanovic * Incorporate feedback * Clean up rules and docs * Update docs * Fix code example * Update docs/src/extend/code-path-analysis.md Co-authored-by: Milos Djermanovic * Update docs/src/extend/code-path-analysis.md Co-authored-by: Milos Djermanovic * Update docs/src/extend/code-path-analysis.md Co-authored-by: Milos Djermanovic * Update lib/rules/consistent-return.js Co-authored-by: Milos Djermanovic * Update lib/rules/no-this-before-super.js Co-authored-by: Milos Djermanovic * Fix examples * Add deprecation notices to RuleTester/FlatRuleTester * Update config * Add deprecation notices to RuleTester/FlatRuleTester * Fix lint warning * Update docs/src/extend/code-path-analysis.md Co-authored-by: Milos Djermanovic * Update docs/src/extend/code-path-analysis.md Co-authored-by: Milos Djermanovic * Update docs/src/extend/code-path-analysis.md Co-authored-by: Milos Djermanovic * Fix test --------- Co-authored-by: Milos Djermanovic --- docs/src/extend/code-path-analysis.md | 482 ++++++++++++------ eslint.config.js | 1 - .../code-path-analysis/code-path-analyzer.js | 56 +- lib/linter/code-path-analysis/code-path.js | 1 + lib/linter/linter.js | 1 + lib/rule-tester/flat-rule-tester.js | 29 +- lib/rule-tester/rule-tester.js | 27 +- lib/rules/array-callback-return.js | 47 +- lib/rules/consistent-return.js | 39 +- lib/rules/constructor-super.js | 51 +- lib/rules/getter-return.js | 41 +- lib/rules/no-fallthrough.js | 56 +- lib/rules/no-this-before-super.js | 49 +- lib/rules/no-unreachable-loop.js | 59 ++- lib/rules/no-unreachable.js | 49 +- lib/rules/no-useless-return.js | 39 +- lib/rules/require-atomic-updates.js | 28 +- .../code-path-analysis/code-path-analyzer.js | 158 ++++++ tests/lib/rule-tester/flat-rule-tester.js | 39 ++ tests/lib/rule-tester/rule-tester.js | 25 + 20 files changed, 990 insertions(+), 287 deletions(-) diff --git a/docs/src/extend/code-path-analysis.md b/docs/src/extend/code-path-analysis.md index 7344f8647adf..879119574907 100644 --- a/docs/src/extend/code-path-analysis.md +++ b/docs/src/extend/code-path-analysis.md @@ -37,7 +37,7 @@ This has references of both the initial segment and the final segments of a code * `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown. * `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned. * `thrownSegments` (`CodePathSegment[]`) - The final segments which includes only thrown. -* `currentSegments` (`CodePathSegment[]`) - Segments of the current position. +* `currentSegments` (`CodePathSegment[]`) - **Deprecated.** Segments of the current traversal position. * `upper` (`CodePath|null`) - The code path of the upper function/global scope. * `childCodePaths` (`CodePath[]`) - Code paths of functions this code path contains. @@ -56,77 +56,110 @@ Difference from doubly linked list is what there are forking and merging (the ne ## Events -There are five events related to code paths, and you can define event handlers in rules. +There are seven events related to code paths, and you can define event handlers by adding them alongside node visitors in the object exported from the `create()` method of your rule. ```js -module.exports = function(context) { - return { - /** - * This is called at the start of analyzing a code path. - * In this time, the code path object has only the initial segment. - * - * @param {CodePath} codePath - The new code path. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - "onCodePathStart": function(codePath, node) { - // do something with codePath - }, - - /** - * This is called at the end of analyzing a code path. - * In this time, the code path object is complete. - * - * @param {CodePath} codePath - The completed code path. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - "onCodePathEnd": function(codePath, node) { - // do something with codePath - }, - - /** - * This is called when a code path segment was created. - * It meant the code path is forked or merged. - * In this time, the segment has the previous segments and has been - * judged reachable or not. - * - * @param {CodePathSegment} segment - The new code path segment. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - "onCodePathSegmentStart": function(segment, node) { - // do something with segment - }, - - /** - * This is called when a code path segment was left. - * In this time, the segment does not have the next segments yet. - * - * @param {CodePathSegment} segment - The left code path segment. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - "onCodePathSegmentEnd": function(segment, node) { - // do something with segment - }, - - /** - * This is called when a code path segment was looped. - * Usually segments have each previous segments when created, - * but when looped, a segment is added as a new previous segment into a - * existing segment. - * - * @param {CodePathSegment} fromSegment - A code path segment of source. - * @param {CodePathSegment} toSegment - A code path segment of destination. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - "onCodePathSegmentLoop": function(fromSegment, toSegment, node) { - // do something with segment - } - }; -}; +module.exports = { + meta: { + // ... + }, + create(context) { + + return { + /** + * This is called at the start of analyzing a code path. + * In this time, the code path object has only the initial segment. + * + * @param {CodePath} codePath - The new code path. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathStart(codePath, node) { + // do something with codePath + }, + + /** + * This is called at the end of analyzing a code path. + * In this time, the code path object is complete. + * + * @param {CodePath} codePath - The completed code path. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathEnd(codePath, node) { + // do something with codePath + }, + + /** + * This is called when a reachable code path segment was created. + * It meant the code path is forked or merged. + * In this time, the segment has the previous segments and has been + * judged reachable or not. + * + * @param {CodePathSegment} segment - The new code path segment. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathSegmentStart(segment, node) { + // do something with segment + }, + + /** + * This is called when a reachable code path segment was left. + * In this time, the segment does not have the next segments yet. + * + * @param {CodePathSegment} segment - The left code path segment. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathSegmentEnd(segment, node) { + // do something with segment + }, + + /** + * This is called when an unreachable code path segment was created. + * It meant the code path is forked or merged. + * In this time, the segment has the previous segments and has been + * judged reachable or not. + * + * @param {CodePathSegment} segment - The new code path segment. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onUnreachableCodePathSegmentStart(segment, node) { + // do something with segment + }, + + /** + * This is called when an unreachable code path segment was left. + * In this time, the segment does not have the next segments yet. + * + * @param {CodePathSegment} segment - The left code path segment. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onUnreachableCodePathSegmentEnd(segment, node) { + // do something with segment + }, + + /** + * This is called when a code path segment was looped. + * Usually segments have each previous segments when created, + * but when looped, a segment is added as a new previous segment into a + * existing segment. + * + * @param {CodePathSegment} fromSegment - A code path segment of source. + * @param {CodePathSegment} toSegment - A code path segment of destination. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathSegmentLoop(fromSegment, toSegment, node) { + // do something with segment + } + }; + + } +} ``` ### About `onCodePathSegmentLoop` @@ -212,35 +245,134 @@ bar(); ## Usage Examples -### To check whether or not this is reachable +### Track current segment position + +To track the current code path segment position, you can define a rule like this: ```js -function isReachable(segment) { - return segment.reachable; -} +module.exports = { + meta: { + // ... + }, + create(context) { + + // tracks the code path we are currently in + let currentCodePath; + + // tracks the segments we've traversed in the current code path + let currentSegments; + + // tracks all current segments for all open paths + const allCurrentSegments = []; + + return { + + onCodePathStart(codePath) { + currentCodePath = codePath; + allCurrentSegments.push(currentSegments); + currentSegments = new Set(); + }, + + onCodePathEnd(codePath) { + currentCodePath = codePath.upper; + currentSegments = allCurrentSegments.pop(); + }, + + onCodePathSegmentStart(segment) { + currentSegments.add(segment); + }, + + onCodePathSegmentEnd(segment) { + currentSegments.delete(segment); + }, -module.exports = function(context) { - var codePathStack = []; - - return { - // Stores CodePath objects. - "onCodePathStart": function(codePath) { - codePathStack.push(codePath); - }, - "onCodePathEnd": function(codePath) { - codePathStack.pop(); - }, - - // Checks reachable or not. - "ExpressionStatement": function(node) { - var codePath = codePathStack[codePathStack.length - 1]; - - // Checks the current code path segments. - if (!codePath.currentSegments.some(isReachable)) { - context.report({message: "Unreachable!", node: node}); + onUnreachableCodePathSegmentStart(segment) { + currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + currentSegments.delete(segment); } + }; + + } +}; +``` + +In this example, the `currentCodePath` variable is used to access the code path that is currently being traversed and the `currentSegments` variable tracks the segments in that code path that have been traversed to that point. Note that `currentSegments` both starts and ends as an empty set, constantly being updated as the traversal progresses. + +Tracking the current segment position is helpful for analyzing the code path that led to a particular node, as in the next example. + +### Find an unreachable node + +To find an unreachable node, track the current segment position and then use a node visitor to check if any of the segments are reachable. For example, the following looks for any `ExpressionStatement` that is unreachable. + +```js +function areAnySegmentsReachable(segments) { + for (const segment of segments) { + if (segment.reachable) { + return true; } - }; + } + + return false; +} + +module.exports = { + meta: { + // ... + }, + create(context) { + + // tracks the code path we are currently in + let currentCodePath; + + // tracks the segments we've traversed in the current code path + let currentSegments; + + // tracks all current segments for all open paths + const allCurrentSegments = []; + + return { + + onCodePathStart(codePath) { + currentCodePath = codePath; + allCurrentSegments.push(currentSegments); + currentSegments = new Set(); + }, + + onCodePathEnd(codePath) { + currentCodePath = codePath.upper; + currentSegments = allCurrentSegments.pop(); + }, + + onCodePathSegmentStart(segment) { + currentSegments.add(segment); + }, + + onCodePathSegmentEnd(segment) { + currentSegments.delete(segment); + }, + + onUnreachableCodePathSegmentStart(segment) { + currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + currentSegments.delete(segment); + }, + + ExpressionStatement(node) { + + // check all the code path segments that led to this node + if (!areAnySegmentsReachable(currentSegments)) { + context.report({ message: "Unreachable!", node }); + } + } + + }; + + } }; ``` @@ -249,9 +381,9 @@ See Also: [no-fallthrough](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-fallthrough.js), [consistent-return](https://github.com/eslint/eslint/blob/HEAD/lib/rules/consistent-return.js) -### To check state of a code path +### Check if a function is called in every path -This example is checking whether or not the parameter `cb` is called in every path. +This example checks whether or not the parameter `cb` is called in every path. Instances of `CodePath` and `CodePathSegment` are shared to every rule. So a rule must not modify those instances. Please use a map of information instead. @@ -271,75 +403,101 @@ function isCbCalled(info) { return info.cbCalled; } -module.exports = function(context) { - var funcInfoStack = []; - var segmentInfoMap = Object.create(null); - - return { - // Checks `cb`. - "onCodePathStart": function(codePath, node) { - funcInfoStack.push({ - codePath: codePath, - hasCb: hasCb(node, context) - }); - }, - "onCodePathEnd": function(codePath, node) { - funcInfoStack.pop(); - - // Checks `cb` was called in every paths. - var cbCalled = codePath.finalSegments.every(function(segment) { - var info = segmentInfoMap[segment.id]; - return info.cbCalled; - }); - - if (!cbCalled) { - context.report({ - message: "`cb` should be called in every path.", - node: node +module.exports = { + meta: { + // ... + }, + create(context) { + + let funcInfo; + const funcInfoStack = []; + const segmentInfoMap = Object.create(null); + + return { + // Checks `cb`. + onCodePathStart(codePath, node) { + funcInfoStack.push(funcInfo); + + funcInfo = { + codePath: codePath, + hasCb: hasCb(node, context), + currentSegments: new Set() + }; + }, + + onCodePathEnd(codePath, node) { + funcInfo = funcInfoStack.pop(); + + // Checks `cb` was called in every paths. + const cbCalled = codePath.finalSegments.every(function(segment) { + const info = segmentInfoMap[segment.id]; + return info.cbCalled; }); - } - }, - - // Manages state of code paths. - "onCodePathSegmentStart": function(segment) { - var funcInfo = funcInfoStack[funcInfoStack.length - 1]; - // Ignores if `cb` doesn't exist. - if (!funcInfo.hasCb) { - return; + if (!cbCalled) { + context.report({ + message: "`cb` should be called in every path.", + node: node + }); + } + }, + + // Manages state of code paths and tracks traversed segments + onCodePathSegmentStart(segment) { + + funcInfo.currentSegments.add(segment); + + // Ignores if `cb` doesn't exist. + if (!funcInfo.hasCb) { + return; + } + + // Initialize state of this path. + const info = segmentInfoMap[segment.id] = { + cbCalled: false + }; + + // If there are the previous paths, merges state. + // Checks `cb` was called in every previous path. + if (segment.prevSegments.length > 0) { + info.cbCalled = segment.prevSegments.every(isCbCalled); + } + }, + + // Tracks unreachable segment traversal + onUnreachableCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + // Tracks reachable segment traversal + onCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + // Tracks unreachable segment traversal + onUnreachableCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + // Checks reachable or not. + CallExpression(node) { + + // Ignores if `cb` doesn't exist. + if (!funcInfo.hasCb) { + return; + } + + // Sets marks that `cb` was called. + const callee = node.callee; + if (callee.type === "Identifier" && callee.name === "cb") { + funcInfo.currentSegments.forEach(segment => { + const info = segmentInfoMap[segment.id]; + info.cbCalled = true; + }); + } } - - // Initialize state of this path. - var info = segmentInfoMap[segment.id] = { - cbCalled: false - }; - - // If there are the previous paths, merges state. - // Checks `cb` was called in every previous path. - if (segment.prevSegments.length > 0) { - info.cbCalled = segment.prevSegments.every(isCbCalled); - } - }, - - // Checks reachable or not. - "CallExpression": function(node) { - var funcInfo = funcInfoStack[funcInfoStack.length - 1]; - - // Ignores if `cb` doesn't exist. - if (!funcInfo.hasCb) { - return; - } - - // Sets marks that `cb` was called. - var callee = node.callee; - if (callee.type === "Identifier" && callee.name === "cb") { - funcInfo.codePath.currentSegments.forEach(function(segment) { - var info = segmentInfoMap[segment.id]; - info.cbCalled = true; - }); - } - } - }; + }; + } }; ``` diff --git a/eslint.config.js b/eslint.config.js index 214be713d4c9..40ebe8c08f7a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -226,7 +226,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/lib/linter/code-path-analysis/code-path-analyzer.js b/lib/linter/code-path-analysis/code-path-analyzer.js index 2dcc27348844..b60e55c16ded 100644 --- a/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/lib/linter/code-path-analysis/code-path-analyzer.js @@ -192,15 +192,18 @@ function forwardCurrentToHead(analyzer, node) { headSegment = headSegments[i]; if (currentSegment !== headSegment && currentSegment) { - debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`); - if (currentSegment.reachable) { - analyzer.emitter.emit( - "onCodePathSegmentEnd", - currentSegment, - node - ); - } + const eventName = currentSegment.reachable + ? "onCodePathSegmentEnd" + : "onUnreachableCodePathSegmentEnd"; + + debug.dump(`${eventName} ${currentSegment.id}`); + + analyzer.emitter.emit( + eventName, + currentSegment, + node + ); } } @@ -213,16 +216,19 @@ function forwardCurrentToHead(analyzer, node) { headSegment = headSegments[i]; if (currentSegment !== headSegment && headSegment) { - debug.dump(`onCodePathSegmentStart ${headSegment.id}`); + + const eventName = headSegment.reachable + ? "onCodePathSegmentStart" + : "onUnreachableCodePathSegmentStart"; + + debug.dump(`${eventName} ${headSegment.id}`); CodePathSegment.markUsed(headSegment); - if (headSegment.reachable) { - analyzer.emitter.emit( - "onCodePathSegmentStart", - headSegment, - node - ); - } + analyzer.emitter.emit( + eventName, + headSegment, + node + ); } } @@ -241,15 +247,17 @@ function leaveFromCurrentSegment(analyzer, node) { for (let i = 0; i < currentSegments.length; ++i) { const currentSegment = currentSegments[i]; + const eventName = currentSegment.reachable + ? "onCodePathSegmentEnd" + : "onUnreachableCodePathSegmentEnd"; - debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`); - if (currentSegment.reachable) { - analyzer.emitter.emit( - "onCodePathSegmentEnd", - currentSegment, - node - ); - } + debug.dump(`${eventName} ${currentSegment.id}`); + + analyzer.emitter.emit( + eventName, + currentSegment, + node + ); } state.currentSegments = []; diff --git a/lib/linter/code-path-analysis/code-path.js b/lib/linter/code-path-analysis/code-path.js index a028ca69481c..f6a88a00af92 100644 --- a/lib/linter/code-path-analysis/code-path.js +++ b/lib/linter/code-path-analysis/code-path.js @@ -117,6 +117,7 @@ class CodePath { /** * Current code path segments. * @type {CodePathSegment[]} + * @deprecated */ get currentSegments() { return this.internal.currentSegments; diff --git a/lib/linter/linter.js b/lib/linter/linter.js index 233cbed5b5cc..48b2bdbe5c39 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -898,6 +898,7 @@ const DEPRECATED_SOURCECODE_PASSTHROUGHS = { getTokensBetween: "getTokensBetween" }; + const BASE_TRAVERSAL_CONTEXT = Object.freeze( Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce( (contextInfo, methodName) => diff --git a/lib/rule-tester/flat-rule-tester.js b/lib/rule-tester/flat-rule-tester.js index 5a93be1cce8f..d5f5981e67ee 100644 --- a/lib/rule-tester/flat-rule-tester.js +++ b/lib/rule-tester/flat-rule-tester.js @@ -16,7 +16,9 @@ const equal = require("fast-deep-equal"), Traverser = require("../shared/traverser"), { getRuleOptionsSchema } = require("../config/flat-config-helpers"), - { Linter, SourceCodeFixer, interpolate } = require("../linter"); + { Linter, SourceCodeFixer, interpolate } = require("../linter"), + CodePath = require("../linter/code-path-analysis/code-path"); + const { FlatConfigArray } = require("../config/flat-config-array"); const { defaultConfig } = require("../config/default-config"); @@ -274,6 +276,21 @@ function getCommentsDeprecation() { ); } +/** + * Emit a deprecation warning if rule uses CodePath#currentSegments. + * @param {string} ruleName Name of the rule. + * @returns {void} + */ +function emitCodePathCurrentSegmentsWarning(ruleName) { + if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) { + emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true; + process.emitWarning( + `"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`, + "DeprecationWarning" + ); + } +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -664,6 +681,7 @@ class FlatRuleTester { // Verify the code. const { getComments } = SourceCode.prototype; + const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments"); let messages; // check for validation errors @@ -677,11 +695,20 @@ class FlatRuleTester { try { SourceCode.prototype.getComments = getCommentsDeprecation; + Object.defineProperty(CodePath.prototype, "currentSegments", { + get() { + emitCodePathCurrentSegmentsWarning(ruleName); + return originalCurrentSegments.get.call(this); + } + }); + messages = linter.verify(code, configs, filename); } finally { SourceCode.prototype.getComments = getComments; + Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments); } + const fatalErrorMessage = messages.find(m => m.fatal); assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`); diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index c9c18664528c..82d79790a316 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -48,7 +48,8 @@ const equal = require("fast-deep-equal"), Traverser = require("../../lib/shared/traverser"), { getRuleOptionsSchema, validate } = require("../shared/config-validator"), - { Linter, SourceCodeFixer, interpolate } = require("../linter"); + { Linter, SourceCodeFixer, interpolate } = require("../linter"), + CodePath = require("../linter/code-path-analysis/code-path"); const ajv = require("../shared/ajv")({ strictDefaults: true }); @@ -375,6 +376,21 @@ function emitDeprecatedContextMethodWarning(ruleName, methodName) { } } +/** + * Emit a deprecation warning if rule uses CodePath#currentSegments. + * @param {string} ruleName Name of the rule. + * @returns {void} + */ +function emitCodePathCurrentSegmentsWarning(ruleName) { + if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) { + emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true; + process.emitWarning( + `"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`, + "DeprecationWarning" + ); + } +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -746,13 +762,22 @@ class RuleTester { // Verify the code. const { getComments } = SourceCode.prototype; + const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments"); let messages; try { SourceCode.prototype.getComments = getCommentsDeprecation; + Object.defineProperty(CodePath.prototype, "currentSegments", { + get() { + emitCodePathCurrentSegmentsWarning(ruleName); + return originalCurrentSegments.get.call(this); + } + }); + messages = linter.verify(code, config, filename); } finally { SourceCode.prototype.getComments = getComments; + Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments); } const fatalErrorMessage = messages.find(m => m.fatal); diff --git a/lib/rules/array-callback-return.js b/lib/rules/array-callback-return.js index 05cd4ede9665..24a33d16c997 100644 --- a/lib/rules/array-callback-return.js +++ b/lib/rules/array-callback-return.js @@ -18,15 +18,6 @@ const astUtils = require("./utils/ast-utils"); const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u; const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort|toSorted)$/u; -/** - * Checks a given code path segment is reachable. - * @param {CodePathSegment} segment A segment to check. - * @returns {boolean} `true` if the segment is reachable. - */ -function isReachable(segment) { - return segment.reachable; -} - /** * Checks a given node is a member access which has the specified name's * property. @@ -38,6 +29,22 @@ function isTargetMethod(node) { return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS); } +/** + * 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; +} + /** * Returns a human-legible description of an array method * @param {string} arrayMethodName A method name to fully qualify @@ -205,7 +212,7 @@ module.exports = { messageId = "expectedNoReturnValue"; } } else { - if (node.body.type === "BlockStatement" && funcInfo.codePath.currentSegments.some(isReachable)) { + if (node.body.type === "BlockStatement" && isAnySegmentReachable(funcInfo.currentSegments)) { messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside"; } } @@ -242,7 +249,8 @@ module.exports = { methodName && !node.async && !node.generator, - node + node, + currentSegments: new Set() }; }, @@ -251,6 +259,23 @@ module.exports = { funcInfo = funcInfo.upper; }, + onUnreachableCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + onCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + // Checks the return statement is valid. ReturnStatement(node) { diff --git a/lib/rules/consistent-return.js b/lib/rules/consistent-return.js index e2d3f078270c..304e924b14a0 100644 --- a/lib/rules/consistent-return.js +++ b/lib/rules/consistent-return.js @@ -16,12 +16,19 @@ const { upperCaseFirst } = require("../shared/string-utils"); //------------------------------------------------------------------------------ /** - * Checks whether or not a given code path segment is unreachable. - * @param {CodePathSegment} segment A CodePathSegment to check. - * @returns {boolean} `true` if the segment is unreachable. + * Checks all segments in a set and returns true if all are unreachable. + * @param {Set} segments The segments to check. + * @returns {boolean} True if all segments are unreachable; false otherwise. */ -function isUnreachable(segment) { - return !segment.reachable; +function areAllSegmentsUnreachable(segments) { + + for (const segment of segments) { + if (segment.reachable) { + return false; + } + } + + return true; } /** @@ -88,7 +95,7 @@ module.exports = { * When unreachable, all paths are returned or thrown. */ if (!funcInfo.hasReturnValue || - funcInfo.codePath.currentSegments.every(isUnreachable) || + areAllSegmentsUnreachable(funcInfo.currentSegments) || astUtils.isES5Constructor(node) || isClassConstructor(node) ) { @@ -141,13 +148,31 @@ module.exports = { hasReturn: false, hasReturnValue: false, messageId: "", - node + node, + currentSegments: new Set() }; }, onCodePathEnd() { funcInfo = funcInfo.upper; }, + onUnreachableCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + onCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + // Reports a given return statement if it's inconsistent. ReturnStatement(node) { const argument = node.argument; diff --git a/lib/rules/constructor-super.js b/lib/rules/constructor-super.js index 5f4058812524..330be80f3865 100644 --- a/lib/rules/constructor-super.js +++ b/lib/rules/constructor-super.js @@ -10,12 +10,19 @@ //------------------------------------------------------------------------------ /** - * Checks whether a given code path segment is reachable or not. - * @param {CodePathSegment} segment A code path segment to check. - * @returns {boolean} `true` if the segment is reachable. + * 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 isReachable(segment) { - return segment.reachable; +function isAnySegmentReachable(segments) { + + for (const segment of segments) { + if (segment.reachable) { + return true; + } + } + + return false; } /** @@ -210,7 +217,8 @@ module.exports = { isConstructor: true, hasExtends: Boolean(superClass), superIsConstructor: isPossibleConstructor(superClass), - codePath + codePath, + currentSegments: new Set() }; } else { funcInfo = { @@ -218,7 +226,8 @@ module.exports = { isConstructor: false, hasExtends: false, superIsConstructor: false, - codePath + codePath, + currentSegments: new Set() }; } }, @@ -261,6 +270,9 @@ module.exports = { * @returns {void} */ onCodePathSegmentStart(segment) { + + funcInfo.currentSegments.add(segment); + if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) { return; } @@ -281,6 +293,19 @@ module.exports = { } }, + onUnreachableCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + onCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + /** * Update information of the code path segment when a code path was * looped. @@ -344,12 +369,11 @@ module.exports = { // Reports if needed. if (funcInfo.hasExtends) { - const segments = funcInfo.codePath.currentSegments; + const segments = funcInfo.currentSegments; let duplicate = false; let info = null; - for (let i = 0; i < segments.length; ++i) { - const segment = segments[i]; + for (const segment of segments) { if (segment.reachable) { info = segInfoMap[segment.id]; @@ -374,7 +398,7 @@ module.exports = { info.validNodes.push(node); } } - } else if (funcInfo.codePath.currentSegments.some(isReachable)) { + } else if (isAnySegmentReachable(funcInfo.currentSegments)) { context.report({ messageId: "unexpected", node @@ -398,10 +422,9 @@ module.exports = { } // Returning argument is a substitute of 'super()'. - const segments = funcInfo.codePath.currentSegments; + const segments = funcInfo.currentSegments; - for (let i = 0; i < segments.length; ++i) { - const segment = segments[i]; + for (const segment of segments) { if (segment.reachable) { const info = segInfoMap[segment.id]; diff --git a/lib/rules/getter-return.js b/lib/rules/getter-return.js index 622b6a7541cd..79ebf3e0902b 100644 --- a/lib/rules/getter-return.js +++ b/lib/rules/getter-return.js @@ -14,15 +14,23 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ + const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u; /** - * Checks a given code path segment is reachable. - * @param {CodePathSegment} segment A segment to check. - * @returns {boolean} `true` if the segment is reachable. + * 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 isReachable(segment) { - return segment.reachable; +function isAnySegmentReachable(segments) { + + for (const segment of segments) { + if (segment.reachable) { + return true; + } + } + + return false; } //------------------------------------------------------------------------------ @@ -71,7 +79,8 @@ module.exports = { codePath: null, hasReturn: false, shouldCheck: false, - node: null + node: null, + currentSegments: [] }; /** @@ -85,7 +94,7 @@ module.exports = { */ function checkLastSegment(node) { if (funcInfo.shouldCheck && - funcInfo.codePath.currentSegments.some(isReachable) + isAnySegmentReachable(funcInfo.currentSegments) ) { context.report({ node, @@ -144,7 +153,8 @@ module.exports = { codePath, hasReturn: false, shouldCheck: isGetter(node), - node + node, + currentSegments: new Set() }; }, @@ -152,6 +162,21 @@ module.exports = { onCodePathEnd() { funcInfo = funcInfo.upper; }, + onUnreachableCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + onCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, // Checks the return statement is valid. ReturnStatement(node) { diff --git a/lib/rules/no-fallthrough.js b/lib/rules/no-fallthrough.js index bd2ee9bbe2c8..91da12120222 100644 --- a/lib/rules/no-fallthrough.js +++ b/lib/rules/no-fallthrough.js @@ -16,6 +16,22 @@ const { directivesPattern } = require("../shared/directives"); const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu; +/** + * 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 comment string is really a fallthrough comment and not an ESLint directive. * @param {string} comment The comment string to check. @@ -51,15 +67,6 @@ function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, f return Boolean(comment && isFallThroughComment(comment.value, fallthroughCommentPattern)); } -/** - * Checks whether or not a given code path segment is reachable. - * @param {CodePathSegment} segment A CodePathSegment to check. - * @returns {boolean} `true` if the segment is reachable. - */ -function isReachable(segment) { - return segment.reachable; -} - /** * Checks whether a node and a token are separated by blank lines * @param {ASTNode} node The node to check @@ -109,7 +116,8 @@ module.exports = { create(context) { const options = context.options[0] || {}; - let currentCodePath = null; + const codePathSegments = []; + let currentCodePathSegments = new Set(); const sourceCode = context.sourceCode; const allowEmptyCase = options.allowEmptyCase || false; @@ -126,13 +134,33 @@ module.exports = { fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT; } return { - onCodePathStart(codePath) { - currentCodePath = codePath; + + onCodePathStart() { + codePathSegments.push(currentCodePathSegments); + currentCodePathSegments = new Set(); }, + onCodePathEnd() { - currentCodePath = currentCodePath.upper; + currentCodePathSegments = codePathSegments.pop(); + }, + + onUnreachableCodePathSegmentStart(segment) { + currentCodePathSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + currentCodePathSegments.delete(segment); + }, + + onCodePathSegmentStart(segment) { + currentCodePathSegments.add(segment); }, + onCodePathSegmentEnd(segment) { + currentCodePathSegments.delete(segment); + }, + + SwitchCase(node) { /* @@ -157,7 +185,7 @@ module.exports = { * `break`, `return`, or `throw` are unreachable. * And allows empty cases and the last case. */ - if (currentCodePath.currentSegments.some(isReachable) && + if (isAnySegmentReachable(currentCodePathSegments) && (node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) && node.parent.cases[node.parent.cases.length - 1] !== node) { fallthroughCase = node; diff --git a/lib/rules/no-this-before-super.js b/lib/rules/no-this-before-super.js index 139bb6649d13..f96d8ace81d2 100644 --- a/lib/rules/no-this-before-super.js +++ b/lib/rules/no-this-before-super.js @@ -90,6 +90,21 @@ module.exports = { return Boolean(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends); } + /** + * Determines if every segment in a set has been called. + * @param {Set} segments The segments to search. + * @returns {boolean} True if every segment has been called; false otherwise. + */ + function isEverySegmentCalled(segments) { + for (const segment of segments) { + if (!isCalled(segment)) { + return false; + } + } + + return true; + } + /** * Checks whether or not this is before `super()` is called. * @returns {boolean} `true` if this is before `super()` is called. @@ -97,7 +112,7 @@ module.exports = { function isBeforeCallOfSuper() { return ( isInConstructorOfDerivedClass() && - !funcInfo.codePath.currentSegments.every(isCalled) + !isEverySegmentCalled(funcInfo.currentSegments) ); } @@ -108,11 +123,9 @@ module.exports = { * @returns {void} */ function setInvalid(node) { - const segments = funcInfo.codePath.currentSegments; - - for (let i = 0; i < segments.length; ++i) { - const segment = segments[i]; + const segments = funcInfo.currentSegments; + for (const segment of segments) { if (segment.reachable) { segInfoMap[segment.id].invalidNodes.push(node); } @@ -124,11 +137,9 @@ module.exports = { * @returns {void} */ function setSuperCalled() { - const segments = funcInfo.codePath.currentSegments; - - for (let i = 0; i < segments.length; ++i) { - const segment = segments[i]; + const segments = funcInfo.currentSegments; + for (const segment of segments) { if (segment.reachable) { segInfoMap[segment.id].superCalled = true; } @@ -156,14 +167,16 @@ module.exports = { classNode.superClass && !astUtils.isNullOrUndefined(classNode.superClass) ), - codePath + codePath, + currentSegments: new Set() }; } else { funcInfo = { upper: funcInfo, isConstructor: false, hasExtends: false, - codePath + codePath, + currentSegments: new Set() }; } }, @@ -211,6 +224,8 @@ module.exports = { * @returns {void} */ onCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + if (!isInConstructorOfDerivedClass()) { return; } @@ -225,6 +240,18 @@ module.exports = { }; }, + onUnreachableCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + onCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + /** * Update information of the code path segment when a code path was * looped. diff --git a/lib/rules/no-unreachable-loop.js b/lib/rules/no-unreachable-loop.js index 1df764e17d87..577d39ac7c7a 100644 --- a/lib/rules/no-unreachable-loop.js +++ b/lib/rules/no-unreachable-loop.js @@ -11,6 +11,22 @@ const allLoopTypes = ["WhileStatement", "DoWhileStatement", "ForStatement", "ForInStatement", "ForOfStatement"]; +/** + * 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; +} + /** * Determines whether the given node is the first node in the code path to which a loop statement * 'loops' for the next iteration. @@ -90,29 +106,36 @@ module.exports = { loopsByTargetSegments = new Map(), loopsToReport = new Set(); - let currentCodePath = null; + const codePathSegments = []; + let currentCodePathSegments = new Set(); return { - onCodePathStart(codePath) { - currentCodePath = codePath; + + onCodePathStart() { + codePathSegments.push(currentCodePathSegments); + currentCodePathSegments = new Set(); }, onCodePathEnd() { - currentCodePath = currentCodePath.upper; + currentCodePathSegments = codePathSegments.pop(); }, - [loopSelector](node) { + onUnreachableCodePathSegmentStart(segment) { + currentCodePathSegments.add(segment); + }, - /** - * Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise. - * For unreachable segments, the code path analysis does not raise events required for this implementation. - */ - if (currentCodePath.currentSegments.some(segment => segment.reachable)) { - loopsToReport.add(node); - } + onUnreachableCodePathSegmentEnd(segment) { + currentCodePathSegments.delete(segment); + }, + + onCodePathSegmentEnd(segment) { + currentCodePathSegments.delete(segment); }, onCodePathSegmentStart(segment, node) { + + currentCodePathSegments.add(segment); + if (isLoopingTarget(node)) { const loop = node.parent; @@ -140,6 +163,18 @@ module.exports = { } }, + [loopSelector](node) { + + /** + * Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise. + * For unreachable segments, the code path analysis does not raise events required for this implementation. + */ + if (isAnySegmentReachable(currentCodePathSegments)) { + loopsToReport.add(node); + } + }, + + "Program:exit"() { loopsToReport.forEach( node => context.report({ node, messageId: "invalid" }) diff --git a/lib/rules/no-unreachable.js b/lib/rules/no-unreachable.js index 6216a73a2353..0cf750e4251e 100644 --- a/lib/rules/no-unreachable.js +++ b/lib/rules/no-unreachable.js @@ -24,12 +24,19 @@ function isInitialized(node) { } /** - * Checks whether or not a given code path segment is unreachable. - * @param {CodePathSegment} segment A CodePathSegment to check. - * @returns {boolean} `true` if the segment is unreachable. + * Checks all segments in a set and returns true if all are unreachable. + * @param {Set} segments The segments to check. + * @returns {boolean} True if all segments are unreachable; false otherwise. */ -function isUnreachable(segment) { - return !segment.reachable; +function areAllSegmentsUnreachable(segments) { + + for (const segment of segments) { + if (segment.reachable) { + return false; + } + } + + return true; } /** @@ -124,7 +131,6 @@ module.exports = { }, create(context) { - let currentCodePath = null; /** @type {ConstructorInfo | null} */ let constructorInfo = null; @@ -132,6 +138,12 @@ module.exports = { /** @type {ConsecutiveRange} */ const range = new ConsecutiveRange(context.sourceCode); + /** @type {Array>} */ + const codePathSegments = []; + + /** @type {Set} */ + let currentCodePathSegments = new Set(); + /** * Reports a given node if it's unreachable. * @param {ASTNode} node A statement node to report. @@ -140,7 +152,7 @@ module.exports = { function reportIfUnreachable(node) { let nextNode = null; - if (node && (node.type === "PropertyDefinition" || currentCodePath.currentSegments.every(isUnreachable))) { + if (node && (node.type === "PropertyDefinition" || areAllSegmentsUnreachable(currentCodePathSegments))) { // Store this statement to distinguish consecutive statements. if (range.isEmpty) { @@ -181,12 +193,29 @@ module.exports = { return { // Manages the current code path. - onCodePathStart(codePath) { - currentCodePath = codePath; + onCodePathStart() { + codePathSegments.push(currentCodePathSegments); + currentCodePathSegments = new Set(); }, onCodePathEnd() { - currentCodePath = currentCodePath.upper; + currentCodePathSegments = codePathSegments.pop(); + }, + + onUnreachableCodePathSegmentStart(segment) { + currentCodePathSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + currentCodePathSegments.delete(segment); + }, + + onCodePathSegmentEnd(segment) { + currentCodePathSegments.delete(segment); + }, + + onCodePathSegmentStart(segment) { + currentCodePathSegments.add(segment); }, // Registers for all statement nodes (excludes FunctionDeclaration). diff --git a/lib/rules/no-useless-return.js b/lib/rules/no-useless-return.js index f89523153d47..81d61051053e 100644 --- a/lib/rules/no-useless-return.js +++ b/lib/rules/no-useless-return.js @@ -57,6 +57,22 @@ function isInFinally(node) { return false; } +/** + * 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; +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -205,7 +221,6 @@ module.exports = { */ function markReturnStatementsOnCurrentSegmentsAsUsed() { scopeInfo - .codePath .currentSegments .forEach(segment => markReturnStatementsOnSegmentAsUsed(segment, new Set())); } @@ -222,7 +237,8 @@ module.exports = { upper: scopeInfo, uselessReturns: [], traversedTryBlockStatements: [], - codePath + codePath, + currentSegments: new Set() }; }, @@ -259,6 +275,9 @@ module.exports = { * NOTE: This event is notified for only reachable segments. */ onCodePathSegmentStart(segment) { + + scopeInfo.currentSegments.add(segment); + const info = { uselessReturns: getUselessReturns([], segment.allPrevSegments), returned: false @@ -268,6 +287,18 @@ module.exports = { segmentInfoMap.set(segment, info); }, + onUnreachableCodePathSegmentStart(segment) { + scopeInfo.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + scopeInfo.currentSegments.delete(segment); + }, + + onCodePathSegmentEnd(segment) { + scopeInfo.currentSegments.delete(segment); + }, + // Adds ReturnStatement node to check whether it's useless or not. ReturnStatement(node) { if (node.argument) { @@ -279,12 +310,12 @@ module.exports = { isInFinally(node) || // Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647). - !scopeInfo.codePath.currentSegments.some(s => s.reachable) + !isAnySegmentReachable(scopeInfo.currentSegments) ) { return; } - for (const segment of scopeInfo.codePath.currentSegments) { + for (const segment of scopeInfo.currentSegments) { const info = segmentInfoMap.get(segment); if (info) { diff --git a/lib/rules/require-atomic-updates.js b/lib/rules/require-atomic-updates.js index ba369a203e7d..7e397ceb1cfd 100644 --- a/lib/rules/require-atomic-updates.js +++ b/lib/rules/require-atomic-updates.js @@ -213,7 +213,8 @@ module.exports = { stack = { upper: stack, codePath, - referenceMap: shouldVerify ? createReferenceMap(scope) : null + referenceMap: shouldVerify ? createReferenceMap(scope) : null, + currentSegments: new Set() }; }, onCodePathEnd() { @@ -223,11 +224,25 @@ module.exports = { // Initialize the segment information. onCodePathSegmentStart(segment) { segmentInfo.initialize(segment); + stack.currentSegments.add(segment); }, + onUnreachableCodePathSegmentStart(segment) { + stack.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + stack.currentSegments.delete(segment); + }, + + onCodePathSegmentEnd(segment) { + stack.currentSegments.delete(segment); + }, + + // Handle references to prepare verification. Identifier(node) { - const { codePath, referenceMap } = stack; + const { referenceMap } = stack; const reference = referenceMap && referenceMap.get(node); // Ignore if this is not a valid variable reference. @@ -240,7 +255,7 @@ module.exports = { // Add a fresh read variable. if (reference.isRead() && !(writeExpr && writeExpr.parent.operator === "=")) { - segmentInfo.markAsRead(codePath.currentSegments, variable); + segmentInfo.markAsRead(stack.currentSegments, variable); } /* @@ -267,16 +282,15 @@ module.exports = { * If the reference exists in `outdatedReadVariables` list, report it. */ ":expression:exit"(node) { - const { codePath, referenceMap } = stack; // referenceMap exists if this is in a resumable function scope. - if (!referenceMap) { + if (!stack.referenceMap) { return; } // Mark the read variables on this code path as outdated. if (node.type === "AwaitExpression" || node.type === "YieldExpression") { - segmentInfo.makeOutdated(codePath.currentSegments); + segmentInfo.makeOutdated(stack.currentSegments); } // Verify. @@ -288,7 +302,7 @@ module.exports = { for (const reference of references) { const variable = reference.resolved; - if (segmentInfo.isOutdated(codePath.currentSegments, variable)) { + if (segmentInfo.isOutdated(stack.currentSegments, variable)) { if (node.parent.left === reference.identifier) { context.report({ node: node.parent, 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 cc2717a7ff8a..dbed9b46194f 100644 --- a/tests/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/tests/lib/linter/code-path-analysis/code-path-analyzer.js @@ -439,6 +439,164 @@ describe("CodePathAnalyzer", () => { }); }); + describe("onUnreachableCodePathSegmentStart", () => { + it("should be fired after a throw", () => { + let lastCodePathNodeType = null; + + linter.defineRule("test", { + create: () => ({ + onUnreachableCodePathSegmentStart(segment, node) { + lastCodePathNodeType = node.type; + + assert(segment instanceof CodePathSegment); + assert.strictEqual(node.type, "ExpressionStatement"); + }, + ExpressionStatement() { + assert.strictEqual(lastCodePathNodeType, "ExpressionStatement"); + } + }) + }); + linter.verify( + "throw 'boom'; foo();", + { rules: { test: 2 } } + ); + + }); + + it("should be fired after a return", () => { + let lastCodePathNodeType = null; + + linter.defineRule("test", { + create: () => ({ + onUnreachableCodePathSegmentStart(segment, node) { + lastCodePathNodeType = node.type; + + assert(segment instanceof CodePathSegment); + assert.strictEqual(node.type, "ExpressionStatement"); + }, + ExpressionStatement() { + assert.strictEqual(lastCodePathNodeType, "ExpressionStatement"); + } + }) + }); + linter.verify( + "function foo() { return; foo(); }", + { rules: { test: 2 } } + ); + + }); + }); + + describe("onUnreachableCodePathSegmentEnd", () => { + it("should be fired after a throw", () => { + let lastCodePathNodeType = null; + + linter.defineRule("test", { + create: () => ({ + onUnreachableCodePathSegmentEnd(segment, node) { + lastCodePathNodeType = node.type; + + assert(segment instanceof CodePathSegment); + assert.strictEqual(node.type, "Program"); + } + }) + }); + linter.verify( + "throw 'boom'; foo();", + { rules: { test: 2 } } + ); + + assert.strictEqual(lastCodePathNodeType, "Program"); + }); + + it("should be fired after a return", () => { + let lastCodePathNodeType = null; + + linter.defineRule("test", { + create: () => ({ + onUnreachableCodePathSegmentEnd(segment, node) { + lastCodePathNodeType = node.type; + assert(segment instanceof CodePathSegment); + assert.strictEqual(node.type, "FunctionDeclaration"); + }, + "Program:exit"() { + assert.strictEqual(lastCodePathNodeType, "FunctionDeclaration"); + } + }) + }); + linter.verify( + "function foo() { return; foo(); }", + { rules: { test: 2 } } + ); + + }); + + it("should be fired after a return inside of function and if statement", () => { + let lastCodePathNodeType = null; + + linter.defineRule("test", { + create: () => ({ + onUnreachableCodePathSegmentEnd(segment, node) { + lastCodePathNodeType = node.type; + assert(segment instanceof CodePathSegment); + assert.strictEqual(node.type, "BlockStatement"); + }, + "Program:exit"() { + assert.strictEqual(lastCodePathNodeType, "BlockStatement"); + } + }) + }); + linter.verify( + "function foo() { if (bar) { return; foo(); } else {} }", + { rules: { test: 2 } } + ); + + }); + + it("should be fired at the end of programs/functions for the final segment", () => { + let count = 0; + let lastNodeType = null; + + linter.defineRule("test", { + create: () => ({ + onUnreachableCodePathSegmentEnd(cp, node) { + count += 1; + + assert(cp instanceof CodePathSegment); + if (count === 4) { + assert(node.type === "Program"); + } else if (count === 1) { + assert(node.type === "FunctionDeclaration"); + } else if (count === 2) { + assert(node.type === "FunctionExpression"); + } else if (count === 3) { + assert(node.type === "ArrowFunctionExpression"); + } + assert(node.type === lastNodeType); + }, + "Program:exit"() { + lastNodeType = "Program"; + }, + "FunctionDeclaration:exit"() { + lastNodeType = "FunctionDeclaration"; + }, + "FunctionExpression:exit"() { + lastNodeType = "FunctionExpression"; + }, + "ArrowFunctionExpression:exit"() { + lastNodeType = "ArrowFunctionExpression"; + } + }) + }); + linter.verify( + "foo(); function foo() { return; } var foo = function() { return; }; var foo = () => { return; }; throw 'boom';", + { rules: { test: 2 }, env: { es6: true } } + ); + + assert(count === 4); + }); + }); + describe("onCodePathSegmentLoop", () => { it("should be fired in `while` loops", () => { let count = 0; diff --git a/tests/lib/rule-tester/flat-rule-tester.js b/tests/lib/rule-tester/flat-rule-tester.js index 8e1f83af6163..3e34c4c34bd2 100644 --- a/tests/lib/rule-tester/flat-rule-tester.js +++ b/tests/lib/rule-tester/flat-rule-tester.js @@ -2248,6 +2248,45 @@ describe("FlatRuleTester", () => { }); }); + describe("deprecations", () => { + let processStub; + + beforeEach(() => { + processStub = sinon.stub(process, "emitWarning"); + }); + + afterEach(() => { + processStub.restore(); + }); + + it("should emit a deprecation warning when CodePath#currentSegments is accessed", () => { + + const useCurrentSegmentsRule = { + create: () => ({ + onCodePathStart(codePath) { + codePath.currentSegments.forEach(() => { }); + } + }) + }; + + ruleTester.run("use-current-segments", useCurrentSegmentsRule, { + valid: ["foo"], + invalid: [] + }); + + assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); + assert.deepStrictEqual( + processStub.getCall(0).args, + [ + "\"use-current-segments\" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples", + "DeprecationWarning" + ] + ); + + }); + + }); + /** * Asserts that a particular value will be emitted from an EventEmitter. * @param {EventEmitter} emitter The emitter that should emit a value diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js index e3c3d5a0061b..68cf887adb94 100644 --- a/tests/lib/rule-tester/rule-tester.js +++ b/tests/lib/rule-tester/rule-tester.js @@ -2490,6 +2490,31 @@ describe("RuleTester", () => { assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); }); + it("should emit a deprecation warning when CodePath#currentSegments is accessed", () => { + + const useCurrentSegmentsRule = { + create: () => ({ + onCodePathStart(codePath) { + codePath.currentSegments.forEach(() => {}); + } + }) + }; + + ruleTester.run("use-current-segments", useCurrentSegmentsRule, { + valid: ["foo"], + invalid: [] + }); + + assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); + assert.deepStrictEqual( + processStub.getCall(0).args, + [ + "\"use-current-segments\" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples", + "DeprecationWarning" + ] + ); + }); + Object.entries({ getSource: "getText", getSourceLines: "getLines", From ecfb54ff4cdd18f28b4f9b78f0a78fb4cf80f1b8 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Fri, 8 Sep 2023 08:06:29 +0000 Subject: [PATCH 166/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 818a06e25ed6..01d50a0c9b28 100644 --- a/README.md +++ b/README.md @@ -288,7 +288,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

-

Sentry Liftoff American Express

Bronze Sponsors

+

Sentry Liftoff Siemens American Express

Bronze Sponsors

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8 Discord GitHub Transloadit Ignition HeroCoders QuickBooks Tool hub

From cd395082bffcb4b68efa09226d7c682cef56179e Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 8 Sep 2023 13:18:23 -0400 Subject: [PATCH 167/248] 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 965acdba0581..7bfcf6e6b60d 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "8.48.0", + "version": "8.49.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From cac45d04b890b0700dd8908927300608adad05fe Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 8 Sep 2023 19:39:00 +0200 Subject: [PATCH 168/248] chore: upgrade @eslint/js@8.49.0 (#17549) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c22a4a94f09..8679d11b3783 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.48.0", + "@eslint/js": "8.49.0", "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", From b7621c3b16cf7d5539f05336a827e1b32d95e6ac Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 8 Sep 2023 21:01:11 +0200 Subject: [PATCH 169/248] chore: remove browser test from `npm test` (#17550) --- Makefile.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile.js b/Makefile.js index 528fd5aa1e72..7978369c0b05 100644 --- a/Makefile.js +++ b/Makefile.js @@ -641,7 +641,9 @@ target.wdio = () => { target.test = function() { target.checkRuleFiles(); target.mocha(); - target.wdio(); + + // target.wdio(); // Temporarily disabled due to problems on Jenkins + target.fuzz({ amount: 150, fuzzBrokenAutofixes: false }); target.checkLicenses(); }; From d498a5197399499a2c0566d5124c3235a8976ef3 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 8 Sep 2023 16:22:13 -0400 Subject: [PATCH 170/248] Build: changelog update for 8.49.0 --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbbab930b601..7e541a3d313d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +v8.49.0 - September 8, 2023 + +* [`b7621c3`](https://github.com/eslint/eslint/commit/b7621c3b16cf7d5539f05336a827e1b32d95e6ac) chore: remove browser test from `npm test` (#17550) (Milos Djermanovic) +* [`cac45d0`](https://github.com/eslint/eslint/commit/cac45d04b890b0700dd8908927300608adad05fe) chore: upgrade @eslint/js@8.49.0 (#17549) (Milos Djermanovic) +* [`cd39508`](https://github.com/eslint/eslint/commit/cd395082bffcb4b68efa09226d7c682cef56179e) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`ecfb54f`](https://github.com/eslint/eslint/commit/ecfb54ff4cdd18f28b4f9b78f0a78fb4cf80f1b8) docs: Update README (GitHub Actions Bot) +* [`da09f4e`](https://github.com/eslint/eslint/commit/da09f4e641141f585ef611c6e9d63d4331054706) feat: Implement onUnreachableCodePathStart/End (#17511) (Nicholas C. Zakas) +* [`de86b3b`](https://github.com/eslint/eslint/commit/de86b3b2e58edd5826200c23255d8325abe375e1) docs: update `no-promise-executor-return` examples (#17529) (Nitin Kumar) +* [`203a971`](https://github.com/eslint/eslint/commit/203a971c0abc3a95ae02ff74104a01e569707060) ci: bump actions/checkout from 3 to 4 (#17530) (dependabot[bot]) +* [`32b2327`](https://github.com/eslint/eslint/commit/32b2327aafdd3b911fabab69ed75c9ff97658c60) feat: Emit deprecation warnings in RuleTester (#17527) (Nicholas C. Zakas) +* [`acb7df3`](https://github.com/eslint/eslint/commit/acb7df35b9a7485f26bc6b3e1f9083d1c585dce9) feat: add new `enforce` option to `lines-between-class-members` (#17462) (Nitin Kumar) +* [`032c4b1`](https://github.com/eslint/eslint/commit/032c4b1476a7b8cfd917a66772d2221950ea87eb) docs: add typescript template (#17500) (James) +* [`cd7da5c`](https://github.com/eslint/eslint/commit/cd7da5cc3154f86f7ca45fb58929d27a7af359ed) docs: Update README (GitHub Actions Bot) +* [`a40fa50`](https://github.com/eslint/eslint/commit/a40fa509922b36bb986eb1be9394591f84f62d9e) chore: use eslint-plugin-jsdoc's flat config (#17516) (Milos Djermanovic) +* [`926a286`](https://github.com/eslint/eslint/commit/926a28684282aeec37680bbc52a66973b8055f54) test: replace Karma with Webdriver.IO (#17126) (Christian Bromann) +* [`f591d2c`](https://github.com/eslint/eslint/commit/f591d2c88bf15af72e3a207b34fa872b4b90464b) chore: Upgrade config-array (#17512) (Nicholas C. Zakas) + v8.48.0 - August 25, 2023 * [`8dd3cec`](https://github.com/eslint/eslint/commit/8dd3cec90c97ed97d243a83b87ad4ea9e6b4781a) chore: upgrade @eslint/js@8.48.0 (#17501) (Milos Djermanovic) From 55c1685aab0fa75074ac1033abfa6e34cb7baaf6 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 8 Sep 2023 16:22:13 -0400 Subject: [PATCH 171/248] 8.49.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 15cab31348a8..e04a56f130c4 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.48.0", + "version": "8.49.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 16db7f6d6eb2..f1b577386053 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

- 9 problems (5 errors, 4 warnings) - Generated on Fri Aug 25 2023 16:01:24 GMT-0400 (Eastern Daylight Time) + 9 problems (5 errors, 4 warnings) - Generated on Fri Sep 08 2023 16:22:14 GMT-0400 (Eastern Daylight Time)
diff --git a/package.json b/package.json index 8679d11b3783..9fe892ea5e58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.48.0", + "version": "8.49.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From bd7a71fd6b7efb0445393304e2d48c5c06d46a45 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Sat, 16 Sep 2023 08:06:38 +0000 Subject: [PATCH 172/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 01d50a0c9b28..0b8296193241 100644 --- a/README.md +++ b/README.md @@ -289,7 +289,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Sentry Liftoff Siemens American Express

Bronze Sponsors

-

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8 Discord GitHub Transloadit Ignition HeroCoders QuickBooks Tool hub

+

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8 Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

## Technology Sponsors From 85a3d9e967b19cb4a0189746499d81ef2f93e14e Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Sun, 17 Sep 2023 20:38:04 +0530 Subject: [PATCH 173/248] feat: allowVoid option in array-callback-return (#17564) * feat: allowVoid option in array-callback-return Refs #17285 * feat: refactor code and add docs * feat: allow void in return-statement * feat: add more tests for allowVoid --- docs/src/rules/array-callback-return.md | 45 ++++++++++++++++++++++-- lib/rules/array-callback-return.js | 18 +++++++++- tests/lib/rules/array-callback-return.js | 21 +++++++++++ 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/docs/src/rules/array-callback-return.md b/docs/src/rules/array-callback-return.md index 965d18537af4..625cb14ced31 100644 --- a/docs/src/rules/array-callback-return.md +++ b/docs/src/rules/array-callback-return.md @@ -92,10 +92,13 @@ var bar = foo.map(node => node.getAttribute("id")); ## Options -This rule accepts a configuration object with two options: +This rule accepts a configuration object with three options: * `"allowImplicit": false` (default) When set to `true`, allows callbacks of methods that require a return value to implicitly return `undefined` with a `return` statement containing no expression. * `"checkForEach": false` (default) When set to `true`, rule will also report `forEach` callbacks that return a value. +* `"allowVoid": false` (default) When set to `true`, allows `void` in `forEach` callbacks, so rule will not report the return value with a `void` operator. + +**Note:** `{ "allowVoid": true }` works only if `checkForEach` option is set to `true`. ### allowImplicit @@ -122,7 +125,7 @@ Examples of **incorrect** code for the `{ "checkForEach": true }` option: /*eslint array-callback-return: ["error", { checkForEach: true }]*/ myArray.forEach(function(item) { - return handleItem(item) + return handleItem(item); }); myArray.forEach(function(item) { @@ -132,11 +135,24 @@ myArray.forEach(function(item) { handleItem(item); }); +myArray.forEach(function(item) { + if (item < 0) { + return void x; + } + handleItem(item); +}); + myArray.forEach(item => handleItem(item)); +myArray.forEach(item => void handleItem(item)); + myArray.forEach(item => { return handleItem(item); }); + +myArray.forEach(item => { + return void handleItem(item); +}); ``` ::: @@ -171,6 +187,31 @@ myArray.forEach(item => { ::: +### allowVoid + +Examples of **correct** code for the `{ "allowVoid": true }` option: + +:::correct + +```js +/*eslint array-callback-return: ["error", { checkForEach: true, allowVoid: true }]*/ + +myArray.forEach(item => void handleItem(item)); + +myArray.forEach(item => { + return void handleItem(item); +}); + +myArray.forEach(item => { + if (item < 0) { + return void x; + } + handleItem(item); +}); +``` + +::: + ## Known Limitations This rule checks callback functions of methods with the given names, *even if* the object which has the method is *not* an array. diff --git a/lib/rules/array-callback-return.js b/lib/rules/array-callback-return.js index 24a33d16c997..bda9ab139a67 100644 --- a/lib/rules/array-callback-return.js +++ b/lib/rules/array-callback-return.js @@ -162,6 +162,10 @@ module.exports = { checkForEach: { type: "boolean", default: false + }, + allowVoid: { + type: "boolean", + default: false } }, additionalProperties: false @@ -178,7 +182,7 @@ module.exports = { create(context) { - const options = context.options[0] || { allowImplicit: false, checkForEach: false }; + const options = context.options[0] || { allowImplicit: false, checkForEach: false, allowVoid: false }; const sourceCode = context.sourceCode; let funcInfo = { @@ -209,6 +213,12 @@ module.exports = { if (funcInfo.arrayMethodName === "forEach") { if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) { + if (options.allowVoid && + node.body.type === "UnaryExpression" && + node.body.operator === "void") { + return; + } + messageId = "expectedNoReturnValue"; } } else { @@ -291,6 +301,12 @@ module.exports = { // if checkForEach: true, returning a value at any path inside a forEach is not allowed if (options.checkForEach && node.argument) { + if (options.allowVoid && + node.argument.type === "UnaryExpression" && + node.argument.operator === "void") { + return; + } + messageId = "expectedNoReturnValue"; } } else { diff --git a/tests/lib/rules/array-callback-return.js b/tests/lib/rules/array-callback-return.js index 6343d3e4027e..bc2e85552663 100644 --- a/tests/lib/rules/array-callback-return.js +++ b/tests/lib/rules/array-callback-return.js @@ -24,6 +24,8 @@ const checkForEachOptions = [{ checkForEach: true }]; const allowImplicitCheckForEach = [{ allowImplicit: true, checkForEach: true }]; +const checkForEachAllowVoid = [{ checkForEach: true, allowVoid: true }]; + ruleTester.run("array-callback-return", rule, { valid: [ @@ -114,6 +116,13 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.every(function() { try { bar(); } finally { return 1; } })", options: checkForEachOptions }, { code: "foo.every(function() { return; })", options: allowImplicitCheckForEach }, + // options: { checkForEach: true, allowVoid: true } + { code: "foo.forEach((x) => void x)", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } }, + { code: "foo.forEach((x) => void bar(x))", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } }, + { code: "foo.forEach(function (x) { return void bar(x); })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } }, + { code: "foo.forEach((x) => { return void bar(x); })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } }, + { code: "foo.forEach((x) => { if (a === b) { return void a; } bar(x) })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 } }, + "Arrow.from(x, function() {})", "foo.abc(function() {})", "every(function() {})", @@ -217,6 +226,18 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.filter(function foo() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] }, { code: "foo.filter(function foo() { return; })", options: checkForEachOptions, errors: [{ messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] }, { code: "foo.every(cb || function() {})", options: checkForEachOptions, errors: ["Array.prototype.every() expects a return value from function."] }, + { code: "foo.forEach((x) => void x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, + { code: "foo.forEach((x) => void bar(x))", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, + { code: "foo.forEach((x) => { return void bar(x); })", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, + { code: "foo.forEach((x) => { if (a === b) { return void a; } bar(x) })", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, + + // options: { checkForEach: true, allowVoid: true } + { code: "foo.forEach((x) => x)", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, + { code: "foo.forEach((x) => !x)", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, + { code: "foo.forEach((x) => { return x; })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, + { code: "foo.forEach((x) => { return !x; })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, + { code: "foo.forEach((x) => { if (a === b) { return x; } })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, + { code: "foo.forEach((x) => { if (a === b) { return !x } })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, // full location tests { From 20893d48b9012f2b61bbbfeac8bee70d68d90e5e Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Sun, 17 Sep 2023 21:45:49 +0530 Subject: [PATCH 174/248] docs: fix incorrect tag's place (#17575) * docs: fix incorrect tag place --- docs/src/rules/logical-assignment-operators.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/rules/logical-assignment-operators.md b/docs/src/rules/logical-assignment-operators.md index 3f9350031f3f..058afcd3ad78 100644 --- a/docs/src/rules/logical-assignment-operators.md +++ b/docs/src/rules/logical-assignment-operators.md @@ -96,10 +96,10 @@ a = a ?? b This option checks for additional patterns with if statements which could be expressed with the logical assignment operator. -::: incorrect - Examples of **incorrect** code for this rule with the `["always", { enforceForIfStatements: true }]` option: +::: incorrect + ```js /*eslint logical-assignment-operators: ["error", "always", { enforceForIfStatements: true }]*/ From aa1b657a9febcd03e9298c03ae2888762795e322 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Mon, 18 Sep 2023 15:47:09 +0900 Subject: [PATCH 175/248] fix: wrong suggestion and message in `no-misleading-character-class` (#17571) * fix: wrong suggest and message in `no-misleading-character-class` * Apply suggestions from code review Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- lib/rules/no-misleading-character-class.js | 80 +++++++++++++++---- .../rules/no-misleading-character-class.js | 28 +++++++ 2 files changed, 93 insertions(+), 15 deletions(-) diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index 99aa9404868f..20591df2cc99 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -13,27 +13,34 @@ const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); // Helpers //------------------------------------------------------------------------------ +/** + * @typedef {import('@eslint-community/regexpp').AST.Character} Character + * @typedef {import('@eslint-community/regexpp').AST.CharacterClassElement} CharacterClassElement + */ + /** * Iterate character sequences of a given nodes. * * CharacterClassRange syntax can steal a part of character sequence, * so this function reverts CharacterClassRange syntax and restore the sequence. - * @param {import('@eslint-community/regexpp').AST.CharacterClassElement[]} nodes The node list to iterate character sequences. - * @returns {IterableIterator} The list of character sequences. + * @param {CharacterClassElement[]} nodes The node list to iterate character sequences. + * @returns {IterableIterator} The list of character sequences. */ function *iterateCharacterSequence(nodes) { + + /** @type {Character[]} */ let seq = []; for (const node of nodes) { switch (node.type) { case "Character": - seq.push(node.value); + seq.push(node); break; case "CharacterClassRange": - seq.push(node.min.value); + seq.push(node.min); yield seq; - seq = [node.max.value]; + seq = [node.max]; break; case "CharacterSet": @@ -55,32 +62,74 @@ function *iterateCharacterSequence(nodes) { } } + +/** + * Checks whether the given character node is a Unicode code point escape or not. + * @param {Character} char the character node to check. + * @returns {boolean} `true` if the character node is a Unicode code point escape. + */ +function isUnicodeCodePointEscape(char) { + return /^\\u\{[\da-f]+\}$/iu.test(char.raw); +} + +/** + * Each function returns `true` if it detects that kind of problem. + * @type {Record boolean>} + */ const hasCharacterSequence = { surrogatePairWithoutUFlag(chars) { - return chars.some((c, i) => i !== 0 && isSurrogatePair(chars[i - 1], c)); + return chars.some((c, i) => { + if (i === 0) { + return false; + } + const c1 = chars[i - 1]; + + return ( + isSurrogatePair(c1.value, c.value) && + !isUnicodeCodePointEscape(c1) && + !isUnicodeCodePointEscape(c) + ); + }); + }, + + surrogatePair(chars) { + return chars.some((c, i) => { + if (i === 0) { + return false; + } + const c1 = chars[i - 1]; + + return ( + isSurrogatePair(c1.value, c.value) && + ( + isUnicodeCodePointEscape(c1) || + isUnicodeCodePointEscape(c) + ) + ); + }); }, combiningClass(chars) { return chars.some((c, i) => ( i !== 0 && - isCombiningCharacter(c) && - !isCombiningCharacter(chars[i - 1]) + isCombiningCharacter(c.value) && + !isCombiningCharacter(chars[i - 1].value) )); }, emojiModifier(chars) { return chars.some((c, i) => ( i !== 0 && - isEmojiModifier(c) && - !isEmojiModifier(chars[i - 1]) + isEmojiModifier(c.value) && + !isEmojiModifier(chars[i - 1].value) )); }, regionalIndicatorSymbol(chars) { return chars.some((c, i) => ( i !== 0 && - isRegionalIndicatorSymbol(c) && - isRegionalIndicatorSymbol(chars[i - 1]) + isRegionalIndicatorSymbol(c.value) && + isRegionalIndicatorSymbol(chars[i - 1].value) )); }, @@ -90,9 +139,9 @@ const hasCharacterSequence = { return chars.some((c, i) => ( i !== 0 && i !== lastIndex && - c === 0x200d && - chars[i - 1] !== 0x200d && - chars[i + 1] !== 0x200d + c.value === 0x200d && + chars[i - 1].value !== 0x200d && + chars[i + 1].value !== 0x200d )); } }; @@ -120,6 +169,7 @@ module.exports = { messages: { surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.", + surrogatePair: "Unexpected surrogate pair in character class.", combiningClass: "Unexpected combined character in character class.", emojiModifier: "Unexpected modified Emoji in character class.", regionalIndicatorSymbol: "Unexpected national flag in character class.", diff --git a/tests/lib/rules/no-misleading-character-class.js b/tests/lib/rules/no-misleading-character-class.js index 54c971493131..9ee29a4631d4 100644 --- a/tests/lib/rules/no-misleading-character-class.js +++ b/tests/lib/rules/no-misleading-character-class.js @@ -628,6 +628,34 @@ ruleTester.run("no-misleading-character-class", rule, { suggestions: null }] }, + { + code: String.raw`/[\ud83d\u{dc4d}]/u`, + errors: [{ + messageId: "surrogatePair", + suggestions: null + }] + }, + { + code: String.raw`/[\u{d83d}\udc4d]/u`, + errors: [{ + messageId: "surrogatePair", + suggestions: null + }] + }, + { + code: String.raw`/[\u{d83d}\u{dc4d}]/u`, + errors: [{ + messageId: "surrogatePair", + suggestions: null + }] + }, + { + code: String.raw`/[\uD83D\u{DC4d}]/u`, + errors: [{ + messageId: "surrogatePair", + suggestions: null + }] + }, // ES2024 From 48a44a73ac456739bdee348bbaf1840d2b1e4830 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Tue, 19 Sep 2023 09:16:44 +0200 Subject: [PATCH 176/248] docs: Add correct/incorrect tags to `prefer-arrow-callback` (#17589) Add correct/incorrect tags --- docs/src/rules/prefer-arrow-callback.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/src/rules/prefer-arrow-callback.md b/docs/src/rules/prefer-arrow-callback.md index 0d7249384474..d3e9000561c0 100644 --- a/docs/src/rules/prefer-arrow-callback.md +++ b/docs/src/rules/prefer-arrow-callback.md @@ -23,6 +23,8 @@ This rule locates function expressions used as callbacks or function arguments. The following examples **will** be flagged: +::: incorrect + ```js /* eslint prefer-arrow-callback: "error" */ @@ -33,10 +35,14 @@ foo(function() { return this.a; }.bind(this)); // ERROR // prefer: foo(() => this.a) ``` +::: + Instances where an arrow function would not produce identical results will be ignored. The following examples **will not** be flagged: +::: correct + ```js /* eslint prefer-arrow-callback: "error" */ /* eslint-env es6 */ @@ -57,6 +63,8 @@ foo(function() { return this.a; }); // OK foo(function bar(n) { return n && n + bar(n - 1); }); // OK ``` +::: + ## Options Access further control over this rule's behavior via an options object. @@ -71,12 +79,16 @@ Changing this value to `true` will reverse this option's behavior by allowing us `{ "allowNamedFunctions": true }` **will not** flag the following example: +::: correct + ```js /* eslint prefer-arrow-callback: [ "error", { "allowNamedFunctions": true } ] */ foo(function bar() {}); ``` +::: + ### allowUnboundThis By default `{ "allowUnboundThis": true }`, this `boolean` option allows function expressions containing `this` to be used as callbacks, as long as the function in question has not been explicitly bound. @@ -85,6 +97,8 @@ When set to `false` this option prohibits the use of function expressions as cal `{ "allowUnboundThis": false }` **will** flag the following examples: +::: incorrect + ```js /* eslint prefer-arrow-callback: [ "error", { "allowUnboundThis": false } ] */ /* eslint-env es6 */ @@ -96,6 +110,8 @@ foo(function() { (() => this); }); someArray.map(function(item) { return this.doSomething(item); }, someObject); ``` +::: + ## When Not To Use It * In environments that have not yet adopted ES6 language features (ES3/5). From 22a558228ff98f478fa308c9ecde361acc4caf20 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Tue, 19 Sep 2023 14:27:40 +0200 Subject: [PATCH 177/248] feat: add rule `no-object-constructor`, deprecate `no-new-object` (#17576) * Deprecate `no-new-object` * Add rule `no-object-constructor` * Apply suggestions * mark no-new-object deprecated in v8.50.0 Co-authored-by: Francesco Trotta --------- Co-authored-by: Milos Djermanovic --- docs/src/_data/rules.json | 20 ++-- docs/src/_data/rules_meta.json | 15 ++- docs/src/rules/no-array-constructor.md | 2 +- docs/src/rules/no-new-object.md | 9 +- docs/src/rules/no-new-wrappers.md | 2 +- docs/src/rules/no-object-constructor.md | 50 ++++++++++ lib/rules/index.js | 1 + lib/rules/no-new-object.js | 7 ++ lib/rules/no-object-constructor.js | 118 +++++++++++++++++++++++ packages/js/src/configs/eslint-all.js | 2 +- tests/lib/rules/no-object-constructor.js | 109 +++++++++++++++++++++ tools/rule-types.json | 1 + 12 files changed, 323 insertions(+), 13 deletions(-) create mode 100644 docs/src/rules/no-object-constructor.md create mode 100644 lib/rules/no-object-constructor.js create mode 100644 tests/lib/rules/no-object-constructor.js diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 0f3893f14f20..7a49dbe3e1e0 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -950,13 +950,6 @@ "fixable": false, "hasSuggestions": false }, - { - "name": "no-new-object", - "description": "Disallow `Object` constructors", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, { "name": "no-new-wrappers", "description": "Disallow `new` operators with the `String`, `Number`, and `Boolean` objects", @@ -971,6 +964,13 @@ "fixable": false, "hasSuggestions": true }, + { + "name": "no-object-constructor", + "description": "Disallow calls to the `Object` constructor without an argument", + "recommended": false, + "fixable": false, + "hasSuggestions": true + }, { "name": "no-octal", "description": "Disallow octal literals", @@ -1956,6 +1956,12 @@ "no-unsafe-negation" ] }, + { + "name": "no-new-object", + "replacedBy": [ + "no-object-constructor" + ] + }, { "name": "no-new-require", "replacedBy": [] diff --git a/docs/src/_data/rules_meta.json b/docs/src/_data/rules_meta.json index 7113de13bdb5..862f013d3fa6 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -1364,7 +1364,11 @@ "description": "Disallow `Object` constructors", "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-new-object" - } + }, + "deprecated": true, + "replacedBy": [ + "no-object-constructor" + ] }, "no-new-require": { "deprecated": true, @@ -1409,6 +1413,15 @@ "url": "https://eslint.org/docs/latest/rules/no-obj-calls" } }, + "no-object-constructor": { + "type": "suggestion", + "docs": { + "description": "Disallow calls to the `Object` constructor without an argument", + "recommended": false, + "url": "https://eslint.org/docs/latest/rules/no-object-constructor" + }, + "hasSuggestions": true + }, "no-octal": { "type": "suggestion", "docs": { diff --git a/docs/src/rules/no-array-constructor.md b/docs/src/rules/no-array-constructor.md index 7a22df0e68a8..aa7c079baa24 100644 --- a/docs/src/rules/no-array-constructor.md +++ b/docs/src/rules/no-array-constructor.md @@ -2,8 +2,8 @@ title: no-array-constructor rule_type: suggestion related_rules: -- no-new-object - no-new-wrappers +- no-object-constructor --- diff --git a/docs/src/rules/no-new-object.md b/docs/src/rules/no-new-object.md index 41b02f87406d..bdd577411984 100644 --- a/docs/src/rules/no-new-object.md +++ b/docs/src/rules/no-new-object.md @@ -6,6 +6,7 @@ related_rules: - no-new-wrappers --- +This rule was **deprecated** in ESLint v8.50.0 and replaced by the [no-object-constructor](no-object-constructor) rule. The new rule flags more situations where object literal syntax can be used, and it does not report a problem when the `Object` constructor is invoked with an argument. The `Object` constructor is used to create new generic objects in JavaScript, such as: @@ -25,7 +26,7 @@ While there are no performance differences between the two approaches, the byte ## Rule Details -This rule disallows `Object` constructors. +This rule disallows calling the `Object` constructor with `new`. Examples of **incorrect** code for this rule: @@ -37,6 +38,8 @@ Examples of **incorrect** code for this rule: var myObject = new Object(); new Object(); + +var foo = new Object("foo"); ``` ::: @@ -54,10 +57,12 @@ var myObject = {}; var Object = function Object() {}; new Object(); + +var foo = Object("foo"); ``` ::: ## When Not To Use It -If you wish to allow the use of the `Object` constructor, you can safely turn this rule off. +If you wish to allow the use of the `Object` constructor with `new`, you can safely turn this rule off. diff --git a/docs/src/rules/no-new-wrappers.md b/docs/src/rules/no-new-wrappers.md index da0a860ca154..fe11e3af7ee0 100644 --- a/docs/src/rules/no-new-wrappers.md +++ b/docs/src/rules/no-new-wrappers.md @@ -3,7 +3,7 @@ title: no-new-wrappers rule_type: suggestion related_rules: - no-array-constructor -- no-new-object +- no-object-constructor further_reading: - https://www.inkling.com/read/javascript-definitive-guide-david-flanagan-6th/chapter-3/wrapper-objects --- diff --git a/docs/src/rules/no-object-constructor.md b/docs/src/rules/no-object-constructor.md new file mode 100644 index 000000000000..e2f7015cdabb --- /dev/null +++ b/docs/src/rules/no-object-constructor.md @@ -0,0 +1,50 @@ +--- +title: no-object-constructor +rule_type: suggestion +related_rules: +- no-array-constructor +- no-new-wrappers +--- + +Use of the `Object` constructor to construct a new empty object is generally discouraged in favor of object literal notation because of conciseness and because the `Object` global may be redefined. +The exception is when the `Object` constructor is used to intentionally wrap a specified value which is passed as an argument. + +## Rule Details + +This rule disallows calling the `Object` constructor without an argument. + +Examples of **incorrect** code for this rule: + +:::incorrect + +```js +/*eslint no-object-constructor: "error"*/ + +Object(); + +new Object(); +``` + +::: + +Examples of **correct** code for this rule: + +:::correct + +```js +/*eslint no-object-constructor: "error"*/ + +Object("foo"); + +const obj = { a: 1, b: 2 }; + +const isObject = value => value === Object(value); + +const createObject = Object => new Object(); +``` + +::: + +## When Not To Use It + +If you wish to allow the use of the `Object` constructor, you can safely turn this rule off. diff --git a/lib/rules/index.js b/lib/rules/index.js index e42639656f73..840abe73b0fb 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -175,6 +175,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "no-new-wrappers": () => require("./no-new-wrappers"), "no-nonoctal-decimal-escape": () => require("./no-nonoctal-decimal-escape"), "no-obj-calls": () => require("./no-obj-calls"), + "no-object-constructor": () => require("./no-object-constructor"), "no-octal": () => require("./no-octal"), "no-octal-escape": () => require("./no-octal-escape"), "no-param-reassign": () => require("./no-param-reassign"), diff --git a/lib/rules/no-new-object.js b/lib/rules/no-new-object.js index 08a482be7150..06275f471251 100644 --- a/lib/rules/no-new-object.js +++ b/lib/rules/no-new-object.js @@ -1,6 +1,7 @@ /** * @fileoverview A rule to disallow calls to the Object constructor * @author Matt DuVall + * @deprecated in ESLint v8.50.0 */ "use strict"; @@ -26,6 +27,12 @@ module.exports = { url: "https://eslint.org/docs/latest/rules/no-new-object" }, + deprecated: true, + + replacedBy: [ + "no-object-constructor" + ], + schema: [], messages: { diff --git a/lib/rules/no-object-constructor.js b/lib/rules/no-object-constructor.js new file mode 100644 index 000000000000..1299779f7ec8 --- /dev/null +++ b/lib/rules/no-object-constructor.js @@ -0,0 +1,118 @@ +/** + * @fileoverview Rule to disallow calls to the `Object` constructor without an argument + * @author Francesco Trotta + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { getVariableByName, isArrowToken } = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Tests if a node appears at the beginning of an ancestor ExpressionStatement node. + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether the node appears at the beginning of an ancestor ExpressionStatement node. + */ +function isStartOfExpressionStatement(node) { + const start = node.range[0]; + let ancestor = node; + + while ((ancestor = ancestor.parent) && ancestor.range[0] === start) { + if (ancestor.type === "ExpressionStatement") { + return true; + } + } + return false; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +/** @type {import('../shared/types').Rule} */ +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "Disallow calls to the `Object` constructor without an argument", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-object-constructor" + }, + + hasSuggestions: true, + + schema: [], + + messages: { + preferLiteral: "The object literal notation {} is preferable.", + useLiteral: "Replace with '{{replacement}}'." + } + }, + + create(context) { + + const sourceCode = context.sourceCode; + + /** + * Determines whether or not an object literal that replaces a specified node needs to be enclosed in parentheses. + * @param {ASTNode} node The node to be replaced. + * @returns {boolean} Whether or not parentheses around the object literal are required. + */ + function needsParentheses(node) { + if (isStartOfExpressionStatement(node)) { + return true; + } + + const prevToken = sourceCode.getTokenBefore(node); + + if (prevToken && isArrowToken(prevToken)) { + return true; + } + + return false; + } + + /** + * Reports on nodes where the `Object` constructor is called without arguments. + * @param {ASTNode} node The node to evaluate. + * @returns {void} + */ + function check(node) { + if (node.callee.type !== "Identifier" || node.callee.name !== "Object" || node.arguments.length) { + return; + } + + const variable = getVariableByName(sourceCode.getScope(node), "Object"); + + if (variable && variable.identifiers.length === 0) { + const replacement = needsParentheses(node) ? "({})" : "{}"; + + context.report({ + node, + messageId: "preferLiteral", + suggest: [ + { + messageId: "useLiteral", + data: { replacement }, + fix: fixer => fixer.replaceText(node, replacement) + } + ] + }); + } + } + + return { + CallExpression: check, + NewExpression: check + }; + + } +}; diff --git a/packages/js/src/configs/eslint-all.js b/packages/js/src/configs/eslint-all.js index 177ff92f3164..52f580035a85 100644 --- a/packages/js/src/configs/eslint-all.js +++ b/packages/js/src/configs/eslint-all.js @@ -152,11 +152,11 @@ module.exports = Object.freeze({ "no-new": "error", "no-new-func": "error", "no-new-native-nonconstructor": "error", - "no-new-object": "error", "no-new-symbol": "error", "no-new-wrappers": "error", "no-nonoctal-decimal-escape": "error", "no-obj-calls": "error", + "no-object-constructor": "error", "no-octal": "error", "no-octal-escape": "error", "no-param-reassign": "error", diff --git a/tests/lib/rules/no-object-constructor.js b/tests/lib/rules/no-object-constructor.js new file mode 100644 index 000000000000..789b9e84cb7e --- /dev/null +++ b/tests/lib/rules/no-object-constructor.js @@ -0,0 +1,109 @@ +/** + * @fileoverview Tests for the no-object-constructor rule + * @author Francesco Trotta + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/no-object-constructor"), + { RuleTester } = require("../../../lib/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: "latest" } }); + +ruleTester.run("no-object-constructor", rule, { + valid: [ + "new Object(x)", + "Object(x)", + "new globalThis.Object", + "const createObject = Object => new Object()", + "var Object; new Object;", + { + code: "new Object()", + globals: { + Object: "off" + } + } + ], + invalid: [ + { + code: "new Object", + errors: [{ + messageId: "preferLiteral", + type: "NewExpression", + suggestions: [{ + desc: "Replace with '({})'.", + messageId: "useLiteral", + output: "({})" + }] + }] + }, + { + code: "Object()", + errors: [{ + messageId: "preferLiteral", + type: "CallExpression", + suggestions: [{ + desc: "Replace with '({})'.", + messageId: "useLiteral", + output: "({})" + }] + }] + }, + { + code: "const fn = () => Object();", + errors: [{ + messageId: "preferLiteral", + type: "CallExpression", + suggestions: [{ + desc: "Replace with '({})'.", + messageId: "useLiteral", + output: "const fn = () => ({});" + }] + }] + }, + { + code: "Object() instanceof Object;", + errors: [{ + messageId: "preferLiteral", + type: "CallExpression", + suggestions: [{ + desc: "Replace with '({})'.", + messageId: "useLiteral", + output: "({}) instanceof Object;" + }] + }] + }, + { + code: "const obj = Object?.();", + errors: [{ + messageId: "preferLiteral", + type: "CallExpression", + suggestions: [{ + desc: "Replace with '{}'.", + messageId: "useLiteral", + output: "const obj = {};" + }] + }] + }, + { + code: "(new Object() instanceof Object);", + errors: [{ + messageId: "preferLiteral", + type: "NewExpression", + suggestions: [{ + desc: "Replace with '{}'.", + messageId: "useLiteral", + output: "({} instanceof Object);" + }] + }] + } + ] +}); diff --git a/tools/rule-types.json b/tools/rule-types.json index f3fe8f80cd16..35895f3838a1 100644 --- a/tools/rule-types.json +++ b/tools/rule-types.json @@ -162,6 +162,7 @@ "no-new-wrappers": "suggestion", "no-nonoctal-decimal-escape": "suggestion", "no-obj-calls": "problem", + "no-object-constructor": "suggestion", "no-octal": "suggestion", "no-octal-escape": "suggestion", "no-param-reassign": "suggestion", From 180053759c6cf05a326c710353b4717fbf289ee0 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Wed, 20 Sep 2023 15:45:50 +0200 Subject: [PATCH 178/248] docs: Fix and standardize JSX code examples (#17591) Fix and standardize JSX code examples --- docs/src/rules/jsx-quotes.md | 16 ++++++++-------- docs/src/rules/keyword-spacing.md | 8 ++++---- docs/src/rules/no-extra-parens.md | 20 ++++++++++---------- docs/src/rules/no-inline-comments.md | 8 ++++---- docs/src/rules/no-irregular-whitespace.md | 4 ++-- docs/src/rules/no-unused-expressions.md | 4 ++-- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/src/rules/jsx-quotes.md b/docs/src/rules/jsx-quotes.md index 4f0d78ba78a8..ddb20002b8a0 100644 --- a/docs/src/rules/jsx-quotes.md +++ b/docs/src/rules/jsx-quotes.md @@ -37,9 +37,9 @@ This rule has a string option: Examples of **incorrect** code for this rule with the default `"prefer-double"` option: -:::incorrect +:::incorrect { "ecmaFeatures": { "jsx": true } } -```xml +```jsx /*eslint jsx-quotes: ["error", "prefer-double"]*/ @@ -49,9 +49,9 @@ Examples of **incorrect** code for this rule with the default `"prefer-double"` Examples of **correct** code for this rule with the default `"prefer-double"` option: -:::correct +:::correct { "ecmaFeatures": { "jsx": true } } -```xml +```jsx /*eslint jsx-quotes: ["error", "prefer-double"]*/ @@ -64,9 +64,9 @@ Examples of **correct** code for this rule with the default `"prefer-double"` op Examples of **incorrect** code for this rule with the `"prefer-single"` option: -:::incorrect +:::incorrect { "ecmaFeatures": { "jsx": true } } -```xml +```jsx /*eslint jsx-quotes: ["error", "prefer-single"]*/ @@ -76,9 +76,9 @@ Examples of **incorrect** code for this rule with the `"prefer-single"` option: Examples of **correct** code for this rule with the `"prefer-single"` option: -:::correct +:::correct { "ecmaFeatures": { "jsx": true } } -```xml +```jsx /*eslint jsx-quotes: ["error", "prefer-single"]*/ diff --git a/docs/src/rules/keyword-spacing.md b/docs/src/rules/keyword-spacing.md index a77e3ddc4205..9c1ca55fc380 100644 --- a/docs/src/rules/keyword-spacing.md +++ b/docs/src/rules/keyword-spacing.md @@ -58,9 +58,9 @@ if (foo) { Examples of **correct** code for this rule with the default `{ "before": true }` option: -::: correct +::: correct { "ecmaFeatures": { "jsx": true } } -```js +```jsx /*eslint keyword-spacing: ["error", { "before": true }]*/ /*eslint-env es6*/ @@ -173,9 +173,9 @@ if(foo) { Examples of **correct** code for this rule with the default `{ "after": true }` option: -::: correct +::: correct { "ecmaFeatures": { "jsx": true } } -```js +```jsx /*eslint keyword-spacing: ["error", { "after": true }]*/ if (foo) { diff --git a/docs/src/rules/no-extra-parens.md b/docs/src/rules/no-extra-parens.md index 0476e628010f..77bef24a7286 100644 --- a/docs/src/rules/no-extra-parens.md +++ b/docs/src/rules/no-extra-parens.md @@ -214,9 +214,9 @@ foo ? bar : (baz || qux); Examples of **correct** code for this rule with the `all` and `{ "ignoreJSX": "all" }` options: -::: correct +::: correct { "ecmaFeatures": { "jsx": true } } -```js +```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "all" }] */ const Component = (
) const Component = ( @@ -230,9 +230,9 @@ const Component = ( Examples of **incorrect** code for this rule with the `all` and `{ "ignoreJSX": "multi-line" }` options: -::: incorrect +::: incorrect { "ecmaFeatures": { "jsx": true } } -```js +```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "multi-line" }] */ const Component = (
) const Component = (

) @@ -242,9 +242,9 @@ const Component = (

) Examples of **correct** code for this rule with the `all` and `{ "ignoreJSX": "multi-line" }` options: -::: correct +::: correct { "ecmaFeatures": { "jsx": true } } -```js +```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "multi-line" }] */ const Component = (
@@ -262,9 +262,9 @@ const Component = ( Examples of **incorrect** code for this rule with the `all` and `{ "ignoreJSX": "single-line" }` options: -::: incorrect +::: incorrect { "ecmaFeatures": { "jsx": true } } -```js +```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "single-line" }] */ const Component = (
@@ -282,9 +282,9 @@ const Component = ( Examples of **correct** code for this rule with the `all` and `{ "ignoreJSX": "single-line" }` options: -::: correct +::: correct { "ecmaFeatures": { "jsx": true } } -```js +```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "single-line" }] */ const Component = (
) const Component = (

) diff --git a/docs/src/rules/no-inline-comments.md b/docs/src/rules/no-inline-comments.md index 687464e91c70..d6a32db4e47b 100644 --- a/docs/src/rules/no-inline-comments.md +++ b/docs/src/rules/no-inline-comments.md @@ -54,9 +54,9 @@ Comments inside the curly braces in JSX are allowed to be on the same line as th Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "ecmaFeatures": { "jsx": true } } -```js +```jsx /*eslint no-inline-comments: "error"*/ var foo =
{ /* On the same line with other code */ }

Some heading

; @@ -74,9 +74,9 @@ var bar = ( Examples of **correct** code for this rule: -::: correct +::: correct { "ecmaFeatures": { "jsx": true } } -```js +```jsx /*eslint no-inline-comments: "error"*/ var foo = ( diff --git a/docs/src/rules/no-irregular-whitespace.md b/docs/src/rules/no-irregular-whitespace.md index cb2c62bda58c..137c588d9b15 100644 --- a/docs/src/rules/no-irregular-whitespace.md +++ b/docs/src/rules/no-irregular-whitespace.md @@ -197,9 +197,9 @@ function thing() { Examples of additional **correct** code for this rule with the `{ "skipJSXText": true }` option: -::: correct +::: correct { "ecmaFeatures": { "jsx": true } } -```js +```jsx /*eslint no-irregular-whitespace: ["error", { "skipJSXText": true }]*/ /*eslint-env es6*/ diff --git a/docs/src/rules/no-unused-expressions.md b/docs/src/rules/no-unused-expressions.md index c67a2a617065..614ea5b11b79 100644 --- a/docs/src/rules/no-unused-expressions.md +++ b/docs/src/rules/no-unused-expressions.md @@ -251,7 +251,7 @@ JSX is most-commonly used in the React ecosystem, where it is compiled to `React Examples of **incorrect** code for the `{ "enforceForJSX": true }` option: -::: incorrect +::: incorrect { "ecmaFeatures": { "jsx": true } } ```jsx /*eslint no-unused-expressions: ["error", { "enforceForJSX": true }]*/ @@ -265,7 +265,7 @@ Examples of **incorrect** code for the `{ "enforceForJSX": true }` option: Examples of **correct** code for the `{ "enforceForJSX": true }` option: -::: correct +::: correct { "ecmaFeatures": { "jsx": true } } ```jsx /*eslint no-unused-expressions: ["error", { "enforceForJSX": true }]*/ From 1ea4cfb585dcb52ac3cb1522a32f25cfe507121b Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 20 Sep 2023 11:05:48 -0400 Subject: [PATCH 179/248] fix: Ensure all RuleTester tests all deprecated context methods (#17587) --- lib/rule-tester/rule-tester.js | 7 ++++++- tests/lib/rule-tester/rule-tester.js | 7 +++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index 82d79790a316..a180ee570ae4 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -186,7 +186,12 @@ const DEPRECATED_SOURCECODE_PASSTHROUGHS = { getTokens: "getTokens", getTokensAfter: "getTokensAfter", getTokensBefore: "getTokensBefore", - getTokensBetween: "getTokensBetween" + getTokensBetween: "getTokensBetween", + + getScope: "getScope", + getAncestors: "getAncestors", + getDeclaredVariables: "getDeclaredVariables", + markVariableAsUsed: "markVariableAsUsed" }; /** diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js index 68cf887adb94..756444e4ea90 100644 --- a/tests/lib/rule-tester/rule-tester.js +++ b/tests/lib/rule-tester/rule-tester.js @@ -2534,10 +2534,13 @@ describe("RuleTester", () => { getTokens: "getTokens", getTokensAfter: "getTokensAfter", getTokensBefore: "getTokensBefore", - getTokensBetween: "getTokensBetween" + getTokensBetween: "getTokensBetween", + getScope: "getScope", + getAncestors: "getAncestors", + getDeclaredVariables: "getDeclaredVariables", + markVariableAsUsed: "markVariableAsUsed" }).forEach(([methodName, replacementName]) => { - it(`should log a deprecation warning when calling \`context.${methodName}\``, () => { const ruleToCheckDeprecation = { meta: { From cc4d26b5a59d510f2c878e973fd245e8eff27c2a Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 22 Sep 2023 11:46:38 -0400 Subject: [PATCH 180/248] fix: Ensure deprecated context.parserServices warns (#17593) * fix: Ensure deprecated context.parserServices warns Updated RuleTester to emit a deprecation warning whenever `context.parserServices` is used. * Update lib/rule-tester/rule-tester.js Co-authored-by: Milos Djermanovic * Update lib/rule-tester/rule-tester.js Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- lib/rule-tester/rule-tester.js | 63 ++++++++++++++++------- tests/lib/rule-tester/rule-tester.js | 76 ++++++++++++++++------------ 2 files changed, 88 insertions(+), 51 deletions(-) diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index a180ee570ae4..d9f1a354b695 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -396,6 +396,22 @@ function emitCodePathCurrentSegmentsWarning(ruleName) { } } +/** + * Emit a deprecation warning if `context.parserServices` is used. + * @param {string} ruleName Name of the rule. + * @returns {void} + */ +function emitParserServicesWarning(ruleName) { + if (!emitParserServicesWarning[`warned-${ruleName}`]) { + emitParserServicesWarning[`warned-${ruleName}`] = true; + process.emitWarning( + `"${ruleName}" rule is using \`context.parserServices\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.parserServices\` instead.`, + "DeprecationWarning" + ); + } +} + + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -627,26 +643,37 @@ class RuleTester { freezeDeeply(context.settings); freezeDeeply(context.parserOptions); - const newContext = Object.freeze( - Object.create( - context, - Object.fromEntries(Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).map(methodName => [ - methodName, - { - value(...args) { - - // emit deprecation warning - emitDeprecatedContextMethodWarning(ruleName, methodName); - - // call the original method - return context[methodName].call(this, ...args); - }, - enumerable: true - } - ])) - ) + // wrap all deprecated methods + const newContext = Object.create( + context, + Object.fromEntries(Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).map(methodName => [ + methodName, + { + value(...args) { + + // emit deprecation warning + emitDeprecatedContextMethodWarning(ruleName, methodName); + + // call the original method + return context[methodName].call(this, ...args); + }, + enumerable: true + } + ])) ); + // emit warning about context.parserServices + const parserServices = context.parserServices; + + Object.defineProperty(newContext, "parserServices", { + get() { + emitParserServicesWarning(ruleName); + return parserServices; + } + }); + + Object.freeze(newContext); + return (typeof rule === "function" ? rule : rule.create)(newContext); } })); diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js index 756444e4ea90..3e5c893cf710 100644 --- a/tests/lib/rule-tester/rule-tester.js +++ b/tests/lib/rule-tester/rule-tester.js @@ -1243,39 +1243,6 @@ describe("RuleTester", () => { }); }); - it("should pass-through services from parseForESLint to the rule", () => { - const enhancedParserPath = require.resolve("../../fixtures/parsers/enhanced-parser"); - const disallowHiRule = { - create: context => ({ - Literal(node) { - assert.strictEqual(context.parserServices, context.sourceCode.parserServices); - - const disallowed = context.sourceCode.parserServices.test.getMessage(); // returns "Hi!" - - if (node.value === disallowed) { - context.report({ node, message: `Don't use '${disallowed}'` }); - } - } - }) - }; - - ruleTester.run("no-hi", disallowHiRule, { - valid: [ - { - code: "'Hello!'", - parser: enhancedParserPath - } - ], - invalid: [ - { - code: "'Hi!'", - parser: enhancedParserPath, - errors: [{ message: "Don't use 'Hi!'" }] - } - ] - }); - }); - it("should prevent invalid options schemas", () => { assert.throws(() => { ruleTester.run("no-invalid-schema", require("../../fixtures/testers/rule-tester/no-invalid-schema"), { @@ -2515,6 +2482,48 @@ describe("RuleTester", () => { ); }); + it("should pass-through services from parseForESLint to the rule and log deprecation notice", () => { + const enhancedParserPath = require.resolve("../../fixtures/parsers/enhanced-parser"); + const disallowHiRule = { + create: context => ({ + Literal(node) { + assert.strictEqual(context.parserServices, context.sourceCode.parserServices); + + const disallowed = context.sourceCode.parserServices.test.getMessage(); // returns "Hi!" + + if (node.value === disallowed) { + context.report({ node, message: `Don't use '${disallowed}'` }); + } + } + }) + }; + + ruleTester.run("no-hi", disallowHiRule, { + valid: [ + { + code: "'Hello!'", + parser: enhancedParserPath + } + ], + invalid: [ + { + code: "'Hi!'", + parser: enhancedParserPath, + errors: [{ message: "Don't use 'Hi!'" }] + } + ] + }); + + assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); + assert.deepStrictEqual( + processStub.getCall(0).args, + [ + "\"no-hi\" rule is using `context.parserServices`, which is deprecated and will be removed in ESLint v9. Please use `sourceCode.parserServices` instead.", + "DeprecationWarning" + ] + ); + + }); Object.entries({ getSource: "getText", getSourceLines: "getLines", @@ -2583,6 +2592,7 @@ describe("RuleTester", () => { }); + }); /** From 83914adbfd5fce7d11b33d095ba6d6a39be0dbbc Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 22 Sep 2023 12:33:56 -0400 Subject: [PATCH 181/248] feat: Implement SourceCode#applyInlineConfig() (#17351) * Implement SourceCode#applyInlineConfig() * Fix lint errors * Add tests for forbidden methods * Clean up naming * Add more tests * Fix last test * Fixing some bugs -- still tests failing * Further refactoring and bug fixes * Fix test for Node.js 19 * Forgot to save the file * Remove proxy; update RuleTester/FlatRuleTester * Clean up * Use WeakSet to track method calls * Update tests/lib/rule-tester/rule-tester.js Co-authored-by: Milos Djermanovic * Update tests/lib/rule-tester/rule-tester.js Co-authored-by: Milos Djermanovic * Re-add tests for FlatRuleTester * Apply feedback * Cleanup tests * Update lib/source-code/source-code.js Co-authored-by: Milos Djermanovic * Update lib/linter/linter.js Co-authored-by: Milos Djermanovic * Fix inline config problems in flat config mode * Update JSON parse error message * Fix JSON parse message again * Update lib/source-code/source-code.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/linter/linter.js Co-authored-by: Milos Djermanovic * Apply feedback --------- Co-authored-by: Milos Djermanovic --- lib/config/flat-config-schema.js | 12 +- lib/linter/linter.js | 229 ++++++++--- lib/rule-tester/flat-rule-tester.js | 54 ++- lib/rule-tester/rule-tester.js | 28 +- lib/source-code/source-code.js | 353 ++++++++++++++++- tests/lib/eslint/flat-eslint.js | 2 +- tests/lib/linter/linter.js | 452 +++++++++++++++++++++- tests/lib/rule-tester/flat-rule-tester.js | 43 ++ tests/lib/rule-tester/rule-tester.js | 44 +++ tests/lib/source-code/source-code.js | 254 ++++++++++++ 10 files changed, 1395 insertions(+), 76 deletions(-) diff --git a/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js index 3922e8a94fe7..a79c02d663b5 100644 --- a/lib/config/flat-config-schema.js +++ b/lib/config/flat-config-schema.js @@ -507,7 +507,7 @@ const eslintrcKeys = [ // Full schema //----------------------------------------------------------------------------- -exports.flatConfigSchema = { +const flatConfigSchema = { // eslintrc-style keys that should always error ...Object.fromEntries(eslintrcKeys.map(key => [key, createEslintrcErrorSchema(key)])), @@ -533,3 +533,13 @@ exports.flatConfigSchema = { plugins: pluginsSchema, rules: rulesSchema }; + +//----------------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------------- + +module.exports = { + flatConfigSchema, + assertIsRuleSeverity, + assertIsRuleOptions +}; diff --git a/lib/linter/linter.js b/lib/linter/linter.js index 48b2bdbe5c39..e195812e513a 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -42,7 +42,8 @@ const ruleReplacements = require("../../conf/replacements.json"); const { getRuleFromConfig } = require("../config/flat-config-helpers"); const { FlatConfigArray } = require("../config/flat-config-array"); - +const { RuleValidator } = require("../config/rule-validator"); +const { assertIsRuleOptions, assertIsRuleSeverity } = require("../config/flat-config-schema"); const debug = require("debug")("eslint:linter"); const MAX_AUTOFIX_PASSES = 10; const DEFAULT_PARSER_NAME = "espree"; @@ -50,7 +51,6 @@ const DEFAULT_ECMA_VERSION = 5; const commentParser = new ConfigCommentParser(); const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }; const parserSymbol = Symbol.for("eslint.RuleTester.parser"); -const globals = require("../../conf/globals"); //------------------------------------------------------------------------------ // Typedefs @@ -145,29 +145,6 @@ function isEspree(parser) { return !!(parser === espree || parser[parserSymbol] === espree); } -/** - * Retrieves globals for the given ecmaVersion. - * @param {number} ecmaVersion The version to retrieve globals for. - * @returns {Object} The globals for the given ecmaVersion. - */ -function getGlobalsForEcmaVersion(ecmaVersion) { - - switch (ecmaVersion) { - case 3: - return globals.es3; - - case 5: - return globals.es5; - - default: - if (ecmaVersion < 2015) { - return globals[`es${ecmaVersion + 2009}`]; - } - - return globals[`es${ecmaVersion}`]; - } -} - /** * Ensures that variables representing built-in properties of the Global Object, * and any globals declared by special block comments, are present in the global @@ -361,13 +338,13 @@ function extractDirectiveComment(value) { * Parses comments in file to extract file-specific config of rules, globals * and environments and merges them with global config; also code blocks * where reporting is disabled or enabled and merges them with reporting config. - * @param {ASTNode} ast The top node of the AST. + * @param {SourceCode} sourceCode The SourceCode object to get comments from. * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules * @param {string|null} warnInlineConfig If a string then it should warn directive comments as disabled. The string value is the config name what the setting came from. * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: LintMessage[], disableDirectives: DisableDirective[]}} * A collection of the directive comments that were found, along with any problems that occurred when parsing */ -function getDirectiveComments(ast, ruleMapper, warnInlineConfig) { +function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) { const configuredRules = {}; const enabledGlobals = Object.create(null); const exportedVariables = {}; @@ -377,7 +354,7 @@ function getDirectiveComments(ast, ruleMapper, warnInlineConfig) { builtInRules: Rules }); - ast.comments.filter(token => token.type !== "Shebang").forEach(comment => { + sourceCode.getInlineConfigNodes().filter(token => token.type !== "Shebang").forEach(comment => { const { directivePart, justificationPart } = extractDirectiveComment(comment.value); const match = directivesPattern.exec(directivePart); @@ -511,6 +488,69 @@ function getDirectiveComments(ast, ruleMapper, warnInlineConfig) { }; } +/** + * Parses comments in file to extract disable directives. + * @param {SourceCode} sourceCode The SourceCode object to get comments from. + * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules + * @returns {{problems: LintMessage[], disableDirectives: DisableDirective[]}} + * 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 = []; + + sourceCode.getInlineConfigNodes().filter(token => token.type !== "Shebang").forEach(comment => { + const { directivePart, justificationPart } = 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); + + 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); + + disableDirectives.push(...directives); + problems.push(...directiveProblems); + break; + } + + // no default + } + }); + + return { + problems, + disableDirectives + }; +} + /** * Normalize ECMAScript version from the initial config * @param {Parser} parser The parser which uses this options. @@ -1313,7 +1353,7 @@ class Linter { const sourceCode = slots.lastSourceCode; const commentDirectives = options.allowInlineConfig - ? getDirectiveComments(sourceCode.ast, ruleId => getRule(slots, ruleId), options.warnInlineConfig) + ? getDirectiveComments(sourceCode, ruleId => getRule(slots, ruleId), options.warnInlineConfig) : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] }; // augment global scope with declared global variables @@ -1324,7 +1364,6 @@ class Linter { ); const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules); - let lintingProblems; try { @@ -1540,19 +1579,6 @@ class Linter { languageOptions.ecmaVersion ); - /* - * add configured globals and language globals - * - * using Object.assign instead of object spread for performance reasons - * https://github.com/eslint/eslint/issues/16302 - */ - const configuredGlobals = Object.assign( - {}, - getGlobalsForEcmaVersion(languageOptions.ecmaVersion), - languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0, - languageOptions.globals - ); - // double check that there is a parser to avoid mysterious error messages if (!languageOptions.parser) { throw new TypeError(`No parser specified for ${options.filename}`); @@ -1608,25 +1634,113 @@ class Linter { } const sourceCode = slots.lastSourceCode; - const commentDirectives = options.allowInlineConfig - ? getDirectiveComments( - sourceCode.ast, - ruleId => getRuleFromConfig(ruleId, config), - options.warnInlineConfig - ) - : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] }; - // augment global scope with declared global variables - addDeclaredGlobals( - sourceCode.scopeManager.scopes[0], - configuredGlobals, - { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals } - ); + /* + * Make adjustments based on the language options. For JavaScript, + * this is primarily about adding variables into the global scope + * to account for ecmaVersion and configured globals. + */ + sourceCode.applyLanguageOptions(languageOptions); - const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules); + const mergedInlineConfig = { + rules: {} + }; + const inlineConfigProblems = []; + + /* + * Inline config can be either enabled or disabled. If disabled, it's possible + * to detect the inline config and emit a warning (though this is not required). + * So we first check to see if inline config is allowed at all, and if so, we + * need to check if it's a warning or not. + */ + if (options.allowInlineConfig) { + + // if inline config should warn then add the warnings + if (options.warnInlineConfig) { + sourceCode.getInlineConfigNodes().forEach(node => { + inlineConfigProblems.push(createLintingProblem({ + ruleId: null, + message: `'${sourceCode.text.slice(node.range[0], node.range[1])}' has no effect because you have 'noInlineConfig' setting in ${options.warnInlineConfig}.`, + loc: node.loc, + severity: 1 + })); + + }); + } else { + const inlineConfigResult = sourceCode.applyInlineConfig(); + + inlineConfigProblems.push( + ...inlineConfigResult.problems + .map(createLintingProblem) + .map(problem => { + problem.fatal = true; + return problem; + }) + ); + + // next we need to verify information about the specified rules + const ruleValidator = new RuleValidator(); + + for (const { config: inlineConfig, node } of inlineConfigResult.configs) { + + Object.keys(inlineConfig.rules).forEach(ruleId => { + const rule = getRuleFromConfig(ruleId, config); + const ruleValue = inlineConfig.rules[ruleId]; + + if (!rule) { + inlineConfigProblems.push(createLintingProblem({ ruleId, loc: node.loc })); + return; + } + + try { + + const ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue]; + + assertIsRuleOptions(ruleId, ruleValue); + assertIsRuleSeverity(ruleId, ruleOptions[0]); + + ruleValidator.validate({ + plugins: config.plugins, + rules: { + [ruleId]: ruleOptions + } + }); + mergedInlineConfig.rules[ruleId] = ruleValue; + } catch (err) { + + let baseMessage = err.message.slice( + err.message.startsWith("Key \"rules\":") + ? err.message.indexOf(":", 12) + 1 + : err.message.indexOf(":") + 1 + ).trim(); + + if (err.messageTemplate) { + baseMessage += ` You passed "${ruleValue}".`; + } + + inlineConfigProblems.push(createLintingProblem({ + ruleId, + message: `Inline configuration for rule "${ruleId}" is invalid:\n\t${baseMessage}\n`, + loc: node.loc + })); + } + }); + } + } + } + const commentDirectives = options.allowInlineConfig && !options.warnInlineConfig + ? getDirectiveCommentsForFlatConfig( + sourceCode, + ruleId => getRuleFromConfig(ruleId, config) + ) + : { problems: [], disableDirectives: [] }; + + const configuredRules = Object.assign({}, config.rules, mergedInlineConfig.rules); let lintingProblems; + sourceCode.finalize(); + try { lintingProblems = runRules( sourceCode, @@ -1667,6 +1781,7 @@ class Linter { disableFixes: options.disableFixes, problems: lintingProblems .concat(commentDirectives.problems) + .concat(inlineConfigProblems) .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column), reportUnusedDisableDirectives: options.reportUnusedDisableDirectives }); diff --git a/lib/rule-tester/flat-rule-tester.js b/lib/rule-tester/flat-rule-tester.js index d5f5981e67ee..300bcb7447e5 100644 --- a/lib/rule-tester/flat-rule-tester.js +++ b/lib/rule-tester/flat-rule-tester.js @@ -133,6 +133,15 @@ const suggestionObjectParameters = new Set([ ]); const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`; +const forbiddenMethods = [ + "applyInlineConfig", + "applyLanguageOptions", + "finalize" +]; + +/** @type {Map} */ +const forbiddenMethodCalls = new Map(forbiddenMethods.map(methodName => ([methodName, new WeakSet()]))); + const hasOwnProperty = Function.call.bind(Object.hasOwnProperty); /** @@ -291,6 +300,34 @@ function emitCodePathCurrentSegmentsWarning(ruleName) { } } +/** + * Function to replace forbidden `SourceCode` methods. Allows just one call per method. + * @param {string} methodName The name of the method to forbid. + * @param {Function} prototype The prototype with the original method to call. + * @returns {Function} The function that throws the error. + */ +function throwForbiddenMethodError(methodName, prototype) { + + const original = prototype[methodName]; + + return function(...args) { + + const called = forbiddenMethodCalls.get(methodName); + + /* eslint-disable no-invalid-this -- needed to operate as a method. */ + if (!called.has(this)) { + called.add(this); + + return original.apply(this, args); + } + /* eslint-enable no-invalid-this -- not needed past this point */ + + throw new Error( + `\`SourceCode#${methodName}()\` cannot be called inside a rule.` + ); + }; +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -679,11 +716,6 @@ class FlatRuleTester { } } - // Verify the code. - const { getComments } = SourceCode.prototype; - const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments"); - let messages; - // check for validation errors try { configs.normalizeSync(); @@ -693,6 +725,11 @@ class FlatRuleTester { throw error; } + // Verify the code. + const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype; + const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments"); + let messages; + try { SourceCode.prototype.getComments = getCommentsDeprecation; Object.defineProperty(CodePath.prototype, "currentSegments", { @@ -702,10 +739,17 @@ class FlatRuleTester { } }); + forbiddenMethods.forEach(methodName => { + SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype); + }); + messages = linter.verify(code, configs, filename); } finally { SourceCode.prototype.getComments = getComments; Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments); + SourceCode.prototype.applyInlineConfig = applyInlineConfig; + SourceCode.prototype.applyLanguageOptions = applyLanguageOptions; + SourceCode.prototype.finalize = finalize; } diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index d9f1a354b695..3bc80ab1837a 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -163,6 +163,12 @@ const suggestionObjectParameters = new Set([ ]); const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`; +const forbiddenMethods = [ + "applyInlineConfig", + "applyLanguageOptions", + "finalize" +]; + const hasOwnProperty = Function.call.bind(Object.hasOwnProperty); const DEPRECATED_SOURCECODE_PASSTHROUGHS = { @@ -335,6 +341,19 @@ function getCommentsDeprecation() { ); } +/** + * Function to replace forbidden `SourceCode` methods. + * @param {string} methodName The name of the method to forbid. + * @returns {Function} The function that throws the error. + */ +function throwForbiddenMethodError(methodName) { + return () => { + throw new Error( + `\`SourceCode#${methodName}()\` cannot be called inside a rule.` + ); + }; +} + /** * Emit a deprecation warning if function-style format is being used. * @param {string} ruleName Name of the rule. @@ -793,7 +812,7 @@ class RuleTester { validate(config, "rule-tester", id => (id === ruleName ? rule : null)); // Verify the code. - const { getComments } = SourceCode.prototype; + const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype; const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments"); let messages; @@ -806,10 +825,17 @@ class RuleTester { } }); + forbiddenMethods.forEach(methodName => { + SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName); + }); + messages = linter.verify(code, config, filename); } finally { SourceCode.prototype.getComments = getComments; Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments); + SourceCode.prototype.applyInlineConfig = applyInlineConfig; + SourceCode.prototype.applyLanguageOptions = applyLanguageOptions; + SourceCode.prototype.finalize = finalize; } const fatalErrorMessage = messages.find(m => m.fatal); diff --git a/lib/source-code/source-code.js b/lib/source-code/source-code.js index 07c0d2948299..4bbd5ae3a5cb 100644 --- a/lib/source-code/source-code.js +++ b/lib/source-code/source-code.js @@ -12,7 +12,15 @@ const { isCommentToken } = require("@eslint-community/eslint-utils"), TokenStore = require("./token-store"), astUtils = require("../shared/ast-utils"), - Traverser = require("../shared/traverser"); + Traverser = require("../shared/traverser"), + globals = require("../../conf/globals"), + { + directivesPattern + } = require("../shared/directives"), + + /* eslint-disable-next-line n/no-restricted-require -- Too messy to figure out right now. */ + ConfigCommentParser = require("../linter/config-comment-parser"), + eslintScope = require("eslint-scope"); //------------------------------------------------------------------------------ // Type Definitions @@ -24,6 +32,8 @@ const // Private //------------------------------------------------------------------------------ +const commentParser = new ConfigCommentParser(); + /** * Validates that the given AST has the required information. * @param {ASTNode} ast The Program node of the AST to check. @@ -49,6 +59,29 @@ function validate(ast) { } } +/** + * Retrieves globals for the given ecmaVersion. + * @param {number} ecmaVersion The version to retrieve globals for. + * @returns {Object} The globals for the given ecmaVersion. + */ +function getGlobalsForEcmaVersion(ecmaVersion) { + + switch (ecmaVersion) { + case 3: + return globals.es3; + + case 5: + return globals.es5; + + default: + if (ecmaVersion < 2015) { + return globals[`es${ecmaVersion + 2009}`]; + } + + return globals[`es${ecmaVersion}`]; + } +} + /** * Check to see if its a ES6 export declaration. * @param {ASTNode} astNode An AST node. @@ -83,6 +116,36 @@ function sortedMerge(tokens, comments) { return result; } +/** + * Normalizes a value for a global in a config + * @param {(boolean|string|null)} configuredValue The value given for a global in configuration or in + * a global directive comment + * @returns {("readable"|"writeable"|"off")} The value normalized as a string + * @throws Error if global value is invalid + */ +function normalizeConfigGlobal(configuredValue) { + switch (configuredValue) { + case "off": + return "off"; + + case true: + case "true": + case "writeable": + case "writable": + return "writable"; + + case null: + case false: + case "false": + case "readable": + case "readonly": + return "readonly"; + + default: + throw new Error(`'${configuredValue}' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')`); + } +} + /** * Determines if two nodes or tokens overlap. * @param {ASTNode|Token} first The first node or token to check. @@ -145,6 +208,116 @@ function isSpaceBetween(sourceCode, first, second, checkInsideOfJSXText) { return false; } +//----------------------------------------------------------------------------- +// Directive Comments +//----------------------------------------------------------------------------- + +/** + * Extract the directive and the justification from a given directive comment and trim them. + * @param {string} value The comment text to extract. + * @returns {{directivePart: string, justificationPart: string}} The extracted directive and justification. + */ +function extractDirectiveComment(value) { + const match = /\s-{2,}\s/u.exec(value); + + if (!match) { + return { directivePart: value.trim(), justificationPart: "" }; + } + + const directive = value.slice(0, match.index).trim(); + const justification = value.slice(match.index + match[0].length).trim(); + + return { directivePart: directive, justificationPart: justification }; +} + +/** + * Ensures that variables representing built-in properties of the Global Object, + * and any globals declared by special block comments, are present in the global + * scope. + * @param {Scope} globalScope The global scope. + * @param {Object|undefined} configGlobals The globals declared in configuration + * @param {Object|undefined} inlineGlobals The globals declared in the source code + * @returns {void} + */ +function addDeclaredGlobals(globalScope, configGlobals = {}, inlineGlobals = {}) { + + // Define configured global variables. + for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(inlineGlobals)])) { + + /* + * `normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would + * typically be caught when validating a config anyway (validity for inline global comments is checked separately). + */ + const configValue = configGlobals[id] === void 0 ? void 0 : normalizeConfigGlobal(configGlobals[id]); + const commentValue = inlineGlobals[id] && inlineGlobals[id].value; + const value = commentValue || configValue; + const sourceComments = inlineGlobals[id] && inlineGlobals[id].comments; + + if (value === "off") { + continue; + } + + let variable = globalScope.set.get(id); + + if (!variable) { + variable = new eslintScope.Variable(id, globalScope); + + globalScope.variables.push(variable); + globalScope.set.set(id, variable); + } + + variable.eslintImplicitGlobalSetting = configValue; + variable.eslintExplicitGlobal = sourceComments !== void 0; + variable.eslintExplicitGlobalComments = sourceComments; + variable.writeable = (value === "writable"); + } + + /* + * "through" contains all references which definitions cannot be found. + * Since we augment the global scope using configuration, we need to update + * references and remove the ones that were added by configuration. + */ + globalScope.through = globalScope.through.filter(reference => { + const name = reference.identifier.name; + const variable = globalScope.set.get(name); + + if (variable) { + + /* + * Links the variable and the reference. + * And this reference is removed from `Scope#through`. + */ + reference.resolved = variable; + variable.references.push(reference); + + return false; + } + + return true; + }); +} + +/** + * Sets the given variable names as exported so they won't be triggered by + * the `no-unused-vars` rule. + * @param {eslint.Scope} globalScope The global scope to define exports in. + * @param {Record} variables An object whose keys are the variable + * names to export. + * @returns {void} + */ +function markExportedVariables(globalScope, variables) { + + Object.keys(variables).forEach(name => { + const variable = globalScope.set.get(name); + + if (variable) { + variable.eslintUsed = true; + variable.eslintExported = true; + } + }); + +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -187,7 +360,9 @@ class SourceCode extends TokenStore { * General purpose caching for the class. */ this[caches] = new Map([ - ["scopes", new WeakMap()] + ["scopes", new WeakMap()], + ["vars", new Map()], + ["configNodes", void 0] ]); /** @@ -266,7 +441,7 @@ class SourceCode extends TokenStore { // Cache for comments found using getComments(). this._commentCache = new WeakMap(); - // don't allow modification of this object + // don't allow further modification of this object Object.freeze(this); Object.freeze(this.lines); } @@ -724,6 +899,178 @@ class SourceCode extends TokenStore { } + /** + * Returns an array of all inline configuration nodes found in the + * source code. + * @returns {Array} An array of all inline configuration nodes. + */ + getInlineConfigNodes() { + + // check the cache first + let configNodes = this[caches].get("configNodes"); + + if (configNodes) { + return configNodes; + } + + // calculate fresh config nodes + configNodes = this.ast.comments.filter(comment => { + + // shebang comments are never directives + if (comment.type === "Shebang") { + return false; + } + + const { directivePart } = extractDirectiveComment(comment.value); + + const directiveMatch = directivesPattern.exec(directivePart); + + if (!directiveMatch) { + return false; + } + + // only certain comment types are supported as line comments + return comment.type !== "Line" || !!/^eslint-disable-(next-)?line$/u.test(directiveMatch[1]); + }); + + this[caches].set("configNodes", configNodes); + + return configNodes; + } + + /** + * Applies language options sent in from the core. + * @param {Object} languageOptions The language options for this run. + * @returns {void} + */ + applyLanguageOptions(languageOptions) { + + /* + * Add configured globals and language globals + * + * Using Object.assign instead of object spread for performance reasons + * https://github.com/eslint/eslint/issues/16302 + */ + const configGlobals = Object.assign( + {}, + getGlobalsForEcmaVersion(languageOptions.ecmaVersion), + languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0, + languageOptions.globals + ); + const varsCache = this[caches].get("vars"); + + varsCache.set("configGlobals", configGlobals); + } + + /** + * Applies configuration found inside of the source code. This method is only + * called when ESLint is running with inline configuration allowed. + * @returns {{problems:Array,configs:{config:FlatConfigArray,node:ASTNode}}} Information + * that ESLint needs to further process the inline configuration. + */ + applyInlineConfig() { + + const problems = []; + const configs = []; + const exportedVariables = {}; + const inlineGlobals = Object.create(null); + + this.getInlineConfigNodes().forEach(comment => { + + const { directivePart } = extractDirectiveComment(comment.value); + const match = directivesPattern.exec(directivePart); + const directiveText = match[1]; + const directiveValue = directivePart.slice(match.index + directiveText.length); + + switch (directiveText) { + case "exported": + Object.assign(exportedVariables, commentParser.parseStringConfig(directiveValue, comment)); + break; + + case "globals": + case "global": + for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) { + let normalizedValue; + + try { + normalizedValue = normalizeConfigGlobal(value); + } catch (err) { + problems.push({ + ruleId: null, + loc: comment.loc, + message: err.message + }); + continue; + } + + if (inlineGlobals[id]) { + inlineGlobals[id].comments.push(comment); + inlineGlobals[id].value = normalizedValue; + } else { + inlineGlobals[id] = { + comments: [comment], + value: normalizedValue + }; + } + } + break; + + case "eslint": { + const parseResult = commentParser.parseJsonConfig(directiveValue, comment.loc); + + if (parseResult.success) { + configs.push({ + config: { + rules: parseResult.config + }, + node: comment + }); + } else { + problems.push(parseResult.error); + } + + break; + } + + // no default + } + }); + + // save all the new variables for later + const varsCache = this[caches].get("vars"); + + varsCache.set("inlineGlobals", inlineGlobals); + varsCache.set("exportedVariables", exportedVariables); + + return { + configs, + problems + }; + } + + /** + * Called by ESLint core to indicate that it has finished providing + * information. We now add in all the missing variables and ensure that + * state-changing methods cannot be called by rules. + * @returns {void} + */ + finalize() { + + // Step 1: ensure that all of the necessary variables are up to date + const varsCache = this[caches].get("vars"); + const globalScope = this.scopeManager.scopes[0]; + const configGlobals = varsCache.get("configGlobals"); + const inlineGlobals = varsCache.get("inlineGlobals"); + const exportedVariables = varsCache.get("exportedVariables"); + + addDeclaredGlobals(globalScope, configGlobals, inlineGlobals); + + if (exportedVariables) { + markExportedVariables(globalScope, exportedVariables); + } + + } + } module.exports = SourceCode; diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index 9e0ca12458f5..f67e62e5c63c 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -3531,7 +3531,7 @@ describe("FlatESLint", () => { const messages = results[0].messages; assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config."); + assert.strictEqual(messages[0].message, "'/* globals foo */' has no effect because you have 'noInlineConfig' setting in your config."); }); }); diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index 65957f82a756..5b3d06826126 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -2023,6 +2023,44 @@ describe("Linter", () => { assert.strictEqual(suppressedMessages.length, 0); }); + + it("should apply valid configuration even if there is an invalid configuration present", () => { + const code = [ + "/* eslint no-unused-vars: [ */ // <-- this one is invalid JSON", + "/* eslint no-undef: [\"error\"] */ // <-- this one is fine, and thus should apply", + "foo(); // <-- expected no-undef error here" + ].join("\n"); + + const messages = linter.verify(code); + const suppressedMessages = linter.getSuppressedMessages(); + + // different engines have different JSON parsing error messages + assert.match(messages[0].message, /Failed to parse JSON from ' "no-unused-vars": \['/u); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.isNull(messages[0].ruleId); + assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages[0].column, 1); + assert.isNull(messages[0].nodeType); + + assert.deepStrictEqual( + messages[1], + { + severity: 2, + ruleId: "no-undef", + message: "'foo' is not defined.", + messageId: "undef", + line: 3, + column: 1, + endLine: 3, + endColumn: 4, + nodeType: "Identifier" + } + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); describe("when evaluating code with comments to disable rules", () => { @@ -4779,6 +4817,182 @@ var a = "test2"; }); }); + describe("config.noInlineConfig + options.allowInlineConfig", () => { + + it("should report both a rule violation and a warning about inline config", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error" + ].join("\n"); + const config = { + rules: { + "no-undef": 2 + }, + noInlineConfig: true + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.deepStrictEqual( + messages, + [ + { + ruleId: null, + message: "'/*eslint-disable*/' has no effect because you have 'noInlineConfig' setting in your config.", + line: 1, + column: 1, + endLine: 1, + endColumn: 21, + severity: 1, + nodeType: null + }, + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier" + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report both a rule violation without warning about inline config when noInlineConfig is true and allowInlineConfig is false", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error" + ].join("\n"); + const config = { + rules: { + "no-undef": 2 + }, + noInlineConfig: true + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual( + messages, + [ + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier" + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report both a rule violation without warning about inline config when both are false", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error" + ].join("\n"); + const config = { + rules: { + "no-undef": 2 + }, + noInlineConfig: false + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual( + messages, + [ + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier" + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report one suppresed problem when noInlineConfig is false and allowInlineConfig is true", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error" + ].join("\n"); + const config = { + rules: { + "no-undef": 2 + }, + noInlineConfig: false + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 1); + assert.deepStrictEqual( + suppressedMessages, + [ + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier", + suppressions: [ + { + justification: "", + kind: "directive" + } + ] + } + ] + ); + + }); + }); + + describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { it("should not report a violation", () => { const code = [ @@ -11673,6 +11887,7 @@ describe("Linter with FlatConfigArray", () => { column: 1, endLine: 1, endColumn: 39, + fatal: true, nodeType: null }, { @@ -11875,12 +12090,12 @@ describe("Linter with FlatConfigArray", () => { const messages = linter.verify(code, config, filename); const suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1); + assert.strictEqual(messages.length, 1, "Incorrect message length"); assert.strictEqual(messages[0].ruleId, "no-alert"); assert.strictEqual(messages[0].message, "Unexpected alert."); assert.include(messages[0].nodeType, "CallExpression"); - assert.strictEqual(suppressedMessages.length, 0); + assert.strictEqual(suppressedMessages.length, 0, "Incorrect suppressed message length"); }); it("rules should not change initial config", () => { @@ -11978,7 +12193,7 @@ describe("Linter with FlatConfigArray", () => { { severity: 2, ruleId: "no-alert", - message: "Configuration for rule \"no-alert\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed 'true').\n", + message: "Inline configuration for rule \"no-alert\" is invalid:\n\tExpected severity of \"off\", 0, \"warn\", 1, \"error\", or 2. You passed \"true\".\n", line: 1, column: 1, endLine: 1, @@ -12001,7 +12216,7 @@ describe("Linter with FlatConfigArray", () => { { severity: 2, ruleId: "no-alert", - message: "Configuration for rule \"no-alert\" is invalid:\n\tValue [{\"nonExistentPropertyName\":true}] should NOT have more than 0 items.\n", + message: "Inline configuration for rule \"no-alert\" is invalid:\n\tValue [{\"nonExistentPropertyName\":true}] should NOT have more than 0 items.\n", line: 1, column: 1, endLine: 1, @@ -12013,6 +12228,44 @@ describe("Linter with FlatConfigArray", () => { assert.strictEqual(suppressedMessages.length, 0); }); + + it("should apply valid configuration even if there is an invalid configuration present", () => { + const code = [ + "/* eslint no-unused-vars: [ */ // <-- this one is invalid JSON", + "/* eslint no-undef: [\"error\"] */ // <-- this one is fine, and thus should apply", + "foo(); // <-- expected no-undef error here" + ].join("\n"); + + const messages = linter.verify(code, {}); + const suppressedMessages = linter.getSuppressedMessages(); + + // different engines have different JSON parsing error messages + assert.match(messages[0].message, /Failed to parse JSON from ' "no-unused-vars": \['/u); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.isNull(messages[0].ruleId); + assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages[0].column, 1); + assert.isNull(messages[0].nodeType); + + assert.deepStrictEqual( + messages[1], + { + severity: 2, + ruleId: "no-undef", + message: "'foo' is not defined.", + messageId: "undef", + line: 3, + column: 1, + endLine: 3, + endColumn: 4, + nodeType: "Identifier" + } + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); describe("when evaluating code with comments to disable rules", () => { @@ -14025,7 +14278,7 @@ var a = "test2"; assert.deepStrictEqual(messages[0].fatal, void 0); assert.deepStrictEqual(messages[0].ruleId, null); assert.deepStrictEqual(messages[0].severity, 1); - assert.deepStrictEqual(messages[0].message, `'/*${directive.split(" ")[0]}*/' has no effect because you have 'noInlineConfig' setting in your config.`); + assert.deepStrictEqual(messages[0].message, `'/* ${directive} */' has no effect because you have 'noInlineConfig' setting in your config.`); assert.strictEqual(suppressedMessages.length, 0); }); @@ -14048,7 +14301,7 @@ var a = "test2"; assert.deepStrictEqual(messages[0].fatal, void 0); assert.deepStrictEqual(messages[0].ruleId, null); assert.deepStrictEqual(messages[0].severity, 1); - assert.deepStrictEqual(messages[0].message, `'//${directive.split(" ")[0]}' has no effect because you have 'noInlineConfig' setting in your config.`); + assert.deepStrictEqual(messages[0].message, `'// ${directive}' has no effect because you have 'noInlineConfig' setting in your config.`); assert.strictEqual(suppressedMessages.length, 0); }); @@ -14093,6 +14346,191 @@ var a = "test2"; }); + + describe("config.noInlineConfig + options.allowInlineConfig", () => { + + it("should report both a rule violation and a warning about inline config", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error" + ].join("\n"); + const config = { + rules: { + "no-undef": 2 + }, + linterOptions: { + noInlineConfig: true + } + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.deepStrictEqual( + messages, + [ + { + ruleId: null, + message: "'/* eslint-disable */' has no effect because you have 'noInlineConfig' setting in your config.", + line: 1, + column: 1, + endLine: 1, + endColumn: 21, + severity: 1, + nodeType: null + }, + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier" + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + + it("should report both a rule violation without warning about inline config when noInlineConfig is true and allowInlineConfig is false", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error" + ].join("\n"); + const config = { + rules: { + "no-undef": 2 + }, + linterOptions: { + noInlineConfig: true + } + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual( + messages, + [ + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier" + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report both a rule violation without warning about inline config when both are false", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error" + ].join("\n"); + const config = { + rules: { + "no-undef": 2 + }, + linterOptions: { + noInlineConfig: false + } + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual( + messages, + [ + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier" + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report one suppresed problem when noInlineConfig is false and allowInlineConfig is true", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error" + ].join("\n"); + const config = { + rules: { + "no-undef": 2 + }, + linterOptions: { + noInlineConfig: false + } + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 1); + assert.deepStrictEqual( + suppressedMessages, + [ + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier", + suppressions: [ + { + justification: "", + kind: "directive" + } + ] + } + ] + ); + + }); + }); + describe("reportUnusedDisableDirectives option", () => { it("reports problems for unused eslint-disable comments", () => { const messages = linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: true }); @@ -15184,7 +15622,6 @@ var a = "test2"; }); }); - describe("Error Conditions", () => { describe("when evaluating broken code", () => { const code = BROKEN_TEST_CODE; @@ -16258,7 +16695,6 @@ var a = "test2"; assert(spy.calledOnce); }); - describe("when evaluating an empty string", () => { it("runs rules", () => { diff --git a/tests/lib/rule-tester/flat-rule-tester.js b/tests/lib/rule-tester/flat-rule-tester.js index 3e34c4c34bd2..a3be696d0a25 100644 --- a/tests/lib/rule-tester/flat-rule-tester.js +++ b/tests/lib/rule-tester/flat-rule-tester.js @@ -2620,6 +2620,49 @@ describe("FlatRuleTester", () => { }); }); + describe("SourceCode forbidden methods", () => { + + [ + "applyInlineConfig", + "applyLanguageOptions", + "finalize" + ].forEach(methodName => { + + const useForbiddenMethodRule = { + create: context => ({ + Program() { + const sourceCode = context.sourceCode; + + sourceCode[methodName](); + } + }) + }; + + it(`should throw if ${methodName} is called from a valid test case`, () => { + assert.throws(() => { + ruleTester.run("use-forbidden-method", useForbiddenMethodRule, { + valid: [""], + invalid: [] + }); + }, `\`SourceCode#${methodName}()\` cannot be called inside a rule.`); + }); + + it(`should throw if ${methodName} is called from an invalid test case`, () => { + assert.throws(() => { + ruleTester.run("use-forbidden-method", useForbiddenMethodRule, { + valid: [], + invalid: [{ + code: "", + errors: [{}] + }] + }); + }, `\`SourceCode#${methodName}()\` cannot be called inside a rule.`); + }); + + }); + + }); + describe("Subclassing", () => { it("should allow subclasses to set the describe/it/itOnly statics and should correctly use those values", () => { const assertionDescribe = assertEmitted(ruleTesterTestEmitter, "custom describe", "this-is-a-rule-name"); diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js index 3e5c893cf710..a28b345501f2 100644 --- a/tests/lib/rule-tester/rule-tester.js +++ b/tests/lib/rule-tester/rule-tester.js @@ -2928,6 +2928,50 @@ describe("RuleTester", () => { }); }); + + describe("SourceCode forbidden methods", () => { + + [ + "applyInlineConfig", + "applyLanguageOptions", + "finalize" + ].forEach(methodName => { + + const useForbiddenMethodRule = { + create: context => ({ + Program() { + const sourceCode = context.sourceCode; + + sourceCode[methodName](); + } + }) + }; + + it(`should throw if ${methodName} is called from a valid test case`, () => { + assert.throws(() => { + ruleTester.run("use-forbidden-method", useForbiddenMethodRule, { + valid: [""], + invalid: [] + }); + }, `\`SourceCode#${methodName}()\` cannot be called inside a rule.`); + }); + + it(`should throw if ${methodName} is called from an invalid test case`, () => { + assert.throws(() => { + ruleTester.run("use-forbidden-method", useForbiddenMethodRule, { + valid: [], + invalid: [{ + code: "", + errors: [{}] + }] + }); + }, `\`SourceCode#${methodName}()\` cannot be called inside a rule.`); + }); + + }); + + }); + describe("Subclassing", () => { it("should allow subclasses to set the describe/it/itOnly statics and should correctly use those values", () => { diff --git a/tests/lib/source-code/source-code.js b/tests/lib/source-code/source-code.js index dae9bcf4c3d6..763db27bc465 100644 --- a/tests/lib/source-code/source-code.js +++ b/tests/lib/source-code/source-code.js @@ -12,6 +12,7 @@ const fs = require("fs"), path = require("path"), assert = require("chai").assert, espree = require("espree"), + eslintScope = require("eslint-scope"), sinon = require("sinon"), { Linter } = require("../../../lib/linter"), SourceCode = require("../../../lib/source-code/source-code"), @@ -3787,4 +3788,257 @@ describe("SourceCode", () => { }); }); + + describe("getInlineConfigNodes()", () => { + + it("should return inline config comments", () => { + + const code = "/*eslint foo: 1*/ foo; /* non-config comment*/ /* eslint-disable bar */ bar; /* eslint-enable bar */"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const sourceCode = new SourceCode(code, ast); + const configComments = sourceCode.getInlineConfigNodes(); + + // not sure why but without the JSON parse/stringify Chai won't see these as equal + assert.deepStrictEqual(JSON.parse(JSON.stringify(configComments)), [ + { + type: "Block", + value: "eslint foo: 1", + start: 0, + end: 17, + range: [ + 0, + 17 + ], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 17 + } + } + }, + { + type: "Block", + value: " eslint-disable bar ", + start: 47, + end: 71, + range: [ + 47, + 71 + ], + loc: { + start: { + line: 1, + column: 47 + }, + end: { + line: 1, + column: 71 + } + } + }, + { + type: "Block", + value: " eslint-enable bar ", + start: 77, + end: 100, + range: [ + 77, + 100 + ], + loc: { + start: { + line: 1, + column: 77 + }, + end: { + line: 1, + column: 100 + } + } + } + ]); + + }); + + }); + + describe("applyLanguageOptions()", () => { + + it("should add ES6 globals", () => { + + const code = "foo"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + ecmaVersion: 6 + }); + const sourceCode = new SourceCode({ text: code, ast, scopeManager }); + + sourceCode.applyLanguageOptions({ + ecmaVersion: 2015 + }); + + sourceCode.finalize(); + + const globalScope = sourceCode.scopeManager.scopes[0]; + const variable = globalScope.set.get("Promise"); + + assert.isDefined(variable); + + }); + + it("should add custom globals", () => { + + const code = "foo"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + ecmaVersion: 6 + }); + const sourceCode = new SourceCode({ text: code, ast, scopeManager }); + + sourceCode.applyLanguageOptions({ + ecmaVersion: 2015, + globals: { + FOO: true + } + }); + + sourceCode.finalize(); + + const globalScope = sourceCode.scopeManager.scopes[0]; + const variable = globalScope.set.get("FOO"); + + assert.isDefined(variable); + assert.isTrue(variable.writeable); + }); + + it("should add commonjs globals", () => { + + const code = "foo"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + nodejsScope: true, + ecmaVersion: 6 + }); + const sourceCode = new SourceCode({ text: code, ast, scopeManager }); + + sourceCode.applyLanguageOptions({ + ecmaVersion: 2015, + sourceType: "commonjs" + }); + + sourceCode.finalize(); + + const globalScope = sourceCode.scopeManager.scopes[0]; + const variable = globalScope.set.get("require"); + + assert.isDefined(variable); + + }); + + }); + + describe("applyInlineConfig()", () => { + + it("should add inline globals", () => { + + const code = "/*global bar: true */ foo"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + ecmaVersion: 6 + }); + const sourceCode = new SourceCode({ text: code, ast, scopeManager }); + + sourceCode.applyInlineConfig(); + sourceCode.finalize(); + + const globalScope = sourceCode.scopeManager.scopes[0]; + const variable = globalScope.set.get("bar"); + + assert.isDefined(variable); + assert.isTrue(variable.writeable); + }); + + + it("should mark exported variables", () => { + + const code = "/*exported foo */ var foo;"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + ecmaVersion: 6 + }); + const sourceCode = new SourceCode({ text: code, ast, scopeManager }); + + sourceCode.applyInlineConfig(); + sourceCode.finalize(); + + const globalScope = sourceCode.scopeManager.scopes[0]; + const variable = globalScope.set.get("foo"); + + assert.isDefined(variable); + assert.isTrue(variable.eslintUsed); + assert.isTrue(variable.eslintExported); + }); + + it("should extract rule configuration", () => { + + const code = "/*eslint some-rule: 2 */ var foo;"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const sourceCode = new SourceCode(code, ast); + const result = sourceCode.applyInlineConfig(); + + assert.strictEqual(result.configs.length, 1); + assert.strictEqual(result.configs[0].config.rules["some-rule"], 2); + }); + + it("should extract multiple rule configurations", () => { + + const code = "/*eslint some-rule: 2, other-rule: [\"error\", { skip: true }] */ var foo;"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const sourceCode = new SourceCode(code, ast); + const result = sourceCode.applyInlineConfig(); + + assert.strictEqual(result.configs.length, 1); + assert.strictEqual(result.configs[0].config.rules["some-rule"], 2); + assert.deepStrictEqual(result.configs[0].config.rules["other-rule"], ["error", { skip: true }]); + }); + + it("should extract multiple comments into multiple configurations", () => { + + const code = "/*eslint some-rule: 2*/ /*eslint other-rule: [\"error\", { skip: true }] */ var foo;"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const sourceCode = new SourceCode(code, ast); + const result = sourceCode.applyInlineConfig(); + + assert.strictEqual(result.configs.length, 2); + assert.strictEqual(result.configs[0].config.rules["some-rule"], 2); + assert.deepStrictEqual(result.configs[1].config.rules["other-rule"], ["error", { skip: true }]); + }); + + it("should report problem with rule configuration parsing", () => { + + const code = "/*eslint some-rule::, */ var foo;"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const sourceCode = new SourceCode(code, ast); + const result = sourceCode.applyInlineConfig(); + const problem = result.problems[0]; + + // Node.js 19 changes the JSON parsing error format, so we need to check each field separately to use a regex + assert.strictEqual(problem.column, 1); + assert.strictEqual(problem.line, 1); + assert.isTrue(problem.fatal); + assert.match(problem.message, /Failed to parse JSON from ' "some-rule"::,': Unexpected token '?:'?/u); + assert.isNull(problem.nodeType); + assert.isNull(problem.ruleId); + assert.strictEqual(problem.severity, 2); + }); + }); }); From f9082ff3f3956a0a5a7d7659de63640a21c4de0f Mon Sep 17 00:00:00 2001 From: fnx Date: Fri, 22 Sep 2023 19:06:55 +0200 Subject: [PATCH 182/248] feat: flat-rule-tester make sure default config always matches (#17585) * feat: flat-rule-tester make sure default config always matches * fix: flat rule tester default config also matches paths without file extension * fix: actually test for extensionless paths * fix: use base config to make sure that every file is included * fix: add missing extensionless path tests --- lib/rule-tester/flat-rule-tester.js | 1 + tests/lib/rule-tester/flat-rule-tester.js | 78 +++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/lib/rule-tester/flat-rule-tester.js b/lib/rule-tester/flat-rule-tester.js index 300bcb7447e5..51cb73b5f802 100644 --- a/lib/rule-tester/flat-rule-tester.js +++ b/lib/rule-tester/flat-rule-tester.js @@ -535,6 +535,7 @@ class FlatRuleTester { } const baseConfig = [ + { files: ["**"] }, // Make sure the default config matches for all files { plugins: { diff --git a/tests/lib/rule-tester/flat-rule-tester.js b/tests/lib/rule-tester/flat-rule-tester.js index a3be696d0a25..679a87b99dae 100644 --- a/tests/lib/rule-tester/flat-rule-tester.js +++ b/tests/lib/rule-tester/flat-rule-tester.js @@ -1116,6 +1116,84 @@ describe("FlatRuleTester", () => { }()); }); + it("should allow setting the filename to a non-JavaScript file", () => { + ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), { + valid: [ + { + code: "var foo = 'bar'", + filename: "somefile.ts" + } + ], + invalid: [] + }); + }); + + it("should allow setting the filename to a file path without extension", () => { + ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), { + valid: [ + { + code: "var foo = 'bar'", + filename: "somefile" + }, + { + code: "var foo = 'bar'", + filename: "path/to/somefile" + } + ], + invalid: [] + }); + }); + + it("should allow setting the filename to a file path with extension", () => { + ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), { + valid: [ + { + code: "var foo = 'bar'", + filename: "path/to/somefile.js" + }, + { + code: "var foo = 'bar'", + filename: "src/somefile.ts" + }, + { + code: "var foo = 'bar'", + filename: "components/Component.vue" + } + ], + invalid: [] + }); + }); + + it("should allow setting the filename to a file path without extension", () => { + ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), { + valid: [ + { + code: "var foo = 'bar'", + filename: "path/to/somefile" + }, + { + code: "var foo = 'bar'", + filename: "src/somefile" + } + ], + invalid: [] + }); + }); + + it("should keep allowing non-JavaScript files if the default config does not specify files", () => { + FlatRuleTester.setDefaultConfig({ rules: {} }); + ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), { + valid: [ + { + code: "var foo = 'bar'", + filename: "somefile.ts" + } + ], + invalid: [] + }); + FlatRuleTester.resetDefaultConfig(); + }); + it("should pass-through the options to the rule", () => { ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), { valid: [ From 27d5a9e57ad347982a68fcd0e75eafee42d344f0 Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Sat, 23 Sep 2023 01:35:03 +0530 Subject: [PATCH 183/248] feat: add suggestions to array-callback-return (#17590) * feat: add suggestions to array-callback-return * fix: spacing errors * fix: spacing errors * feat: change meta type to problem again * feat: disable eslint-plugin/require-meta-has-suggestions * feat: update tests --- lib/rules/array-callback-return.js | 155 ++++++++++++++--- tests/lib/rules/array-callback-return.js | 202 +++++++++++++++++++++-- 2 files changed, 320 insertions(+), 37 deletions(-) diff --git a/lib/rules/array-callback-return.js b/lib/rules/array-callback-return.js index bda9ab139a67..6d8f258fa140 100644 --- a/lib/rules/array-callback-return.js +++ b/lib/rules/array-callback-return.js @@ -136,6 +136,76 @@ function getArrayMethodName(node) { return null; } +/** + * Checks if the given node is a void expression. + * @param {ASTNode} node The node to check. + * @returns {boolean} - `true` if the node is a void expression + */ +function isExpressionVoid(node) { + return node.type === "UnaryExpression" && node.operator === "void"; +} + +/** + * Fixes the linting error by prepending "void " to the given node + * @param {Object} sourceCode context given by context.sourceCode + * @param {ASTNode} node The node to fix. + * @param {Object} fixer The fixer object provided by ESLint. + * @returns {Array} - An array of fix objects to apply to the node. + */ +function voidPrependFixer(sourceCode, node, fixer) { + + const requiresParens = + + // prepending `void ` will fail if the node has a lower precedence than void + astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression", operator: "void" }) && + + // check if there are parentheses around the node to avoid redundant parentheses + !astUtils.isParenthesised(sourceCode, node); + + // avoid parentheses issues + const returnOrArrowToken = sourceCode.getTokenBefore( + node, + node.parent.type === "ArrowFunctionExpression" + ? astUtils.isArrowToken + + // isReturnToken + : token => token.type === "Keyword" && token.value === "return" + ); + + const firstToken = sourceCode.getTokenAfter(returnOrArrowToken); + + const prependSpace = + + // is return token, as => allows void to be adjacent + returnOrArrowToken.value === "return" && + + // If two tokens (return and "(") are adjacent + returnOrArrowToken.range[1] === firstToken.range[0]; + + return [ + fixer.insertTextBefore(firstToken, `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`), + fixer.insertTextAfter(node, requiresParens ? ")" : "") + ]; +} + +/** + * Fixes the linting error by `wrapping {}` around the given node's body. + * @param {Object} sourceCode context given by context.sourceCode + * @param {ASTNode} node The node to fix. + * @param {Object} fixer The fixer object provided by ESLint. + * @returns {Array} - An array of fix objects to apply to the node. + */ +function curlyWrapFixer(sourceCode, node, fixer) { + const arrowToken = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken); + const firstToken = sourceCode.getTokenAfter(arrowToken); + const lastToken = sourceCode.getLastToken(node); + + return [ + fixer.insertTextBefore(firstToken, "{"), + fixer.insertTextAfter(lastToken, "}") + ]; +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -151,6 +221,9 @@ module.exports = { url: "https://eslint.org/docs/latest/rules/array-callback-return" }, + // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- false positive + hasSuggestions: true, + schema: [ { type: "object", @@ -176,7 +249,9 @@ module.exports = { expectedAtEnd: "{{arrayMethodName}}() expects a value to be returned at the end of {{name}}.", expectedInside: "{{arrayMethodName}}() expects a return value from {{name}}.", expectedReturnValue: "{{arrayMethodName}}() expects a return value from {{name}}.", - expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}." + expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}.", + wrapBraces: "Wrap the expression in `{}`.", + prependVoid: "Prepend `void` to the expression." } }, @@ -209,32 +284,56 @@ module.exports = { return; } - let messageId = null; + const messageAndSuggestions = { messageId: "", suggest: [] }; if (funcInfo.arrayMethodName === "forEach") { if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) { - if (options.allowVoid && - node.body.type === "UnaryExpression" && - node.body.operator === "void") { - return; - } - messageId = "expectedNoReturnValue"; + if (options.allowVoid) { + if (isExpressionVoid(node.body)) { + return; + } + + messageAndSuggestions.messageId = "expectedNoReturnValue"; + messageAndSuggestions.suggest = [ + { + messageId: "wrapBraces", + fix(fixer) { + return curlyWrapFixer(sourceCode, node, fixer); + } + }, + { + messageId: "prependVoid", + fix(fixer) { + return voidPrependFixer(sourceCode, node.body, fixer); + } + } + ]; + } else { + messageAndSuggestions.messageId = "expectedNoReturnValue"; + messageAndSuggestions.suggest = [{ + messageId: "wrapBraces", + fix(fixer) { + return curlyWrapFixer(sourceCode, node, fixer); + } + }]; + } } } else { if (node.body.type === "BlockStatement" && isAnySegmentReachable(funcInfo.currentSegments)) { - messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside"; + messageAndSuggestions.messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside"; } } - if (messageId) { + if (messageAndSuggestions.messageId) { const name = astUtils.getFunctionNameWithKind(node); context.report({ node, loc: astUtils.getFunctionHeadLoc(node, sourceCode), - messageId, - data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) } + messageId: messageAndSuggestions.messageId, + data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) }, + suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null }); } } @@ -295,36 +394,46 @@ module.exports = { funcInfo.hasReturn = true; - let messageId = null; + const messageAndSuggestions = { messageId: "", suggest: [] }; if (funcInfo.arrayMethodName === "forEach") { // if checkForEach: true, returning a value at any path inside a forEach is not allowed if (options.checkForEach && node.argument) { - if (options.allowVoid && - node.argument.type === "UnaryExpression" && - node.argument.operator === "void") { - return; - } - messageId = "expectedNoReturnValue"; + if (options.allowVoid) { + if (isExpressionVoid(node.argument)) { + return; + } + + messageAndSuggestions.messageId = "expectedNoReturnValue"; + messageAndSuggestions.suggest = [{ + messageId: "prependVoid", + fix(fixer) { + return voidPrependFixer(sourceCode, node.argument, fixer); + } + }]; + } else { + messageAndSuggestions.messageId = "expectedNoReturnValue"; + } } } else { // if allowImplicit: false, should also check node.argument if (!options.allowImplicit && !node.argument) { - messageId = "expectedReturnValue"; + messageAndSuggestions.messageId = "expectedReturnValue"; } } - if (messageId) { + if (messageAndSuggestions.messageId) { context.report({ node, - messageId, + messageId: messageAndSuggestions.messageId, data: { name: astUtils.getFunctionNameWithKind(funcInfo.node), arrayMethodName: fullMethodName(funcInfo.arrayMethodName) - } + }, + suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null }); } }, diff --git a/tests/lib/rules/array-callback-return.js b/tests/lib/rules/array-callback-return.js index bc2e85552663..39114dbc582b 100644 --- a/tests/lib/rules/array-callback-return.js +++ b/tests/lib/rules/array-callback-return.js @@ -209,10 +209,66 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.forEach(function bar(x) { return x;})", options: allowImplicitCheckForEach, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] }, // // options: { checkForEach: true } - { code: "foo.forEach(x => x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "foo.forEach(val => y += val)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "[\"foo\",\"bar\"].forEach(x => ++x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "foo.bar().forEach(x => x === y)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, + { + code: "foo.forEach(x => x)", + options: checkForEachOptions, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "foo.forEach(x => {x})", messageId: "wrapBraces" } + ] + }] + }, + { + code: "foo.forEach(x => (x))", + options: checkForEachOptions, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "foo.forEach(x => {(x)})", messageId: "wrapBraces" } + ] + }] + }, + { + code: "foo.forEach(val => y += val)", + options: checkForEachOptions, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "foo.forEach(val => {y += val})", messageId: "wrapBraces" } + ] + }] + }, + { + code: "[\"foo\",\"bar\"].forEach(x => ++x)", + options: checkForEachOptions, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "[\"foo\",\"bar\"].forEach(x => {++x})", messageId: "wrapBraces" } + ] + }] + }, + { + code: "foo.bar().forEach(x => x === y)", + options: checkForEachOptions, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "foo.bar().forEach(x => {x === y})", messageId: "wrapBraces" } + ] + }] + }, { code: "foo.forEach(function() {return function() { if (a == b) { return a; }}}())", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] }, { code: "foo.forEach(function(x) { if (a == b) {return x;}})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] }, { code: "foo.forEach(function(x) { if (a == b) {return undefined;}})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] }, @@ -226,18 +282,136 @@ ruleTester.run("array-callback-return", rule, { { code: "foo.filter(function foo() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] }, { code: "foo.filter(function foo() { return; })", options: checkForEachOptions, errors: [{ messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] }, { code: "foo.every(cb || function() {})", options: checkForEachOptions, errors: ["Array.prototype.every() expects a return value from function."] }, - { code: "foo.forEach((x) => void x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, - { code: "foo.forEach((x) => void bar(x))", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, - { code: "foo.forEach((x) => { return void bar(x); })", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, - { code: "foo.forEach((x) => { if (a === b) { return void a; } bar(x) })", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, + { code: "foo.forEach((x) => void x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, + { code: "foo.forEach((x) => void bar(x))", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, + { code: "foo.forEach((x) => { return void bar(x); })", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, + { code: "foo.forEach((x) => { if (a === b) { return void a; } bar(x) })", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, // options: { checkForEach: true, allowVoid: true } - { code: "foo.forEach((x) => x)", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, - { code: "foo.forEach((x) => !x)", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, - { code: "foo.forEach((x) => { return x; })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, - { code: "foo.forEach((x) => { return !x; })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, - { code: "foo.forEach((x) => { if (a === b) { return x; } })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, - { code: "foo.forEach((x) => { if (a === b) { return !x } })", options: checkForEachAllowVoid, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue" }] }, + + { + code: "foo.forEach(x => x)", + options: checkForEachAllowVoid, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "foo.forEach(x => {x})", messageId: "wrapBraces" }, + { output: "foo.forEach(x => void x)", messageId: "prependVoid" } + ] + }] + }, + { + code: "foo.forEach(x => !x)", + options: checkForEachAllowVoid, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "foo.forEach(x => {!x})", messageId: "wrapBraces" }, + { output: "foo.forEach(x => void !x)", messageId: "prependVoid" } + ] + }] + }, + { + code: "foo.forEach(x => (x))", + options: checkForEachAllowVoid, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "foo.forEach(x => {(x)})", messageId: "wrapBraces" }, + { output: "foo.forEach(x => void (x))", messageId: "prependVoid" } + ] + }] + }, + { + code: "foo.forEach((x) => { return x; })", + options: checkForEachAllowVoid, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "foo.forEach((x) => { return void x; })", messageId: "prependVoid" } + ] + }] + }, + { + code: "foo.forEach((x) => { return !x; })", + options: checkForEachAllowVoid, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "foo.forEach((x) => { return void !x; })", messageId: "prependVoid" } + ] + }] + }, + { + code: "foo.forEach((x) => { return(x); })", + options: checkForEachAllowVoid, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "foo.forEach((x) => { return void (x); })", messageId: "prependVoid" } + ] + }] + }, + { + code: "foo.forEach((x) => { return (x + 1); })", + options: checkForEachAllowVoid, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "foo.forEach((x) => { return void (x + 1); })", messageId: "prependVoid" } + ] + }] + }, + { + code: "foo.forEach((x) => { if (a === b) { return x; } })", + options: checkForEachAllowVoid, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "foo.forEach((x) => { if (a === b) { return void x; } })", messageId: "prependVoid" } + ] + }] + }, + { + code: "foo.forEach((x) => { if (a === b) { return !x; } })", + options: checkForEachAllowVoid, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "foo.forEach((x) => { if (a === b) { return void !x; } })", messageId: "prependVoid" } + ] + }] + }, + { + code: "foo.forEach((x) => { if (a === b) { return (x + a); } })", + options: checkForEachAllowVoid, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "expectedNoReturnValue", + data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, + suggestions: [ + { output: "foo.forEach((x) => { if (a === b) { return void (x + a); } })", messageId: "prependVoid" } + ] + }] + }, // full location tests { From 38ada6df8f4a0313b7d0739b28f0af6b4897b8ce Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 22 Sep 2023 16:25:24 -0400 Subject: [PATCH 184/248] 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 7bfcf6e6b60d..fc5fb50fcb9a 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "8.49.0", + "version": "8.50.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From f8a8a2d6b45c82f94a574623759b6e3d2af193f3 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 22 Sep 2023 22:47:20 +0200 Subject: [PATCH 185/248] chore: upgrade @eslint/js@8.50.0 (#17599) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9fe892ea5e58..8bc8d546b579 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.49.0", + "@eslint/js": "8.50.0", "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", From 212687c36a9e2682b84f5de2b683b025182d9777 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 22 Sep 2023 17:03:28 -0400 Subject: [PATCH 186/248] Build: changelog update for 8.50.0 --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e541a3d313d..efb9cc93035a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +v8.50.0 - September 22, 2023 + +* [`f8a8a2d`](https://github.com/eslint/eslint/commit/f8a8a2d6b45c82f94a574623759b6e3d2af193f3) chore: upgrade @eslint/js@8.50.0 (#17599) (Milos Djermanovic) +* [`38ada6d`](https://github.com/eslint/eslint/commit/38ada6df8f4a0313b7d0739b28f0af6b4897b8ce) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`27d5a9e`](https://github.com/eslint/eslint/commit/27d5a9e57ad347982a68fcd0e75eafee42d344f0) feat: add suggestions to array-callback-return (#17590) (Tanuj Kanti) +* [`f9082ff`](https://github.com/eslint/eslint/commit/f9082ff3f3956a0a5a7d7659de63640a21c4de0f) feat: flat-rule-tester make sure default config always matches (#17585) (fnx) +* [`83914ad`](https://github.com/eslint/eslint/commit/83914adbfd5fce7d11b33d095ba6d6a39be0dbbc) feat: Implement SourceCode#applyInlineConfig() (#17351) (Nicholas C. Zakas) +* [`cc4d26b`](https://github.com/eslint/eslint/commit/cc4d26b5a59d510f2c878e973fd245e8eff27c2a) fix: Ensure deprecated context.parserServices warns (#17593) (Nicholas C. Zakas) +* [`1ea4cfb`](https://github.com/eslint/eslint/commit/1ea4cfb585dcb52ac3cb1522a32f25cfe507121b) fix: Ensure all RuleTester tests all deprecated context methods (#17587) (Nicholas C. Zakas) +* [`1800537`](https://github.com/eslint/eslint/commit/180053759c6cf05a326c710353b4717fbf289ee0) docs: Fix and standardize JSX code examples (#17591) (Francesco Trotta) +* [`22a5582`](https://github.com/eslint/eslint/commit/22a558228ff98f478fa308c9ecde361acc4caf20) feat: add rule `no-object-constructor`, deprecate `no-new-object` (#17576) (Francesco Trotta) +* [`48a44a7`](https://github.com/eslint/eslint/commit/48a44a73ac456739bdee348bbaf1840d2b1e4830) docs: Add correct/incorrect tags to `prefer-arrow-callback` (#17589) (Francesco Trotta) +* [`aa1b657`](https://github.com/eslint/eslint/commit/aa1b657a9febcd03e9298c03ae2888762795e322) fix: wrong suggestion and message in `no-misleading-character-class` (#17571) (Yosuke Ota) +* [`20893d4`](https://github.com/eslint/eslint/commit/20893d48b9012f2b61bbbfeac8bee70d68d90e5e) docs: fix incorrect tag's place (#17575) (Tanuj Kanti) +* [`85a3d9e`](https://github.com/eslint/eslint/commit/85a3d9e967b19cb4a0189746499d81ef2f93e14e) feat: allowVoid option in array-callback-return (#17564) (Tanuj Kanti) +* [`bd7a71f`](https://github.com/eslint/eslint/commit/bd7a71fd6b7efb0445393304e2d48c5c06d46a45) docs: Update README (GitHub Actions Bot) + v8.49.0 - September 8, 2023 * [`b7621c3`](https://github.com/eslint/eslint/commit/b7621c3b16cf7d5539f05336a827e1b32d95e6ac) chore: remove browser test from `npm test` (#17550) (Milos Djermanovic) From 299bfae1254f22bd4b3bccd897d6355f63e0d3b6 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 22 Sep 2023 17:03:29 -0400 Subject: [PATCH 187/248] 8.50.0 --- docs/package.json | 2 +- docs/src/_data/rule_versions.json | 3 ++- docs/src/_data/rules.json | 2 +- docs/src/_data/rules_meta.json | 3 ++- docs/src/use/formatters/html-formatter-example.html | 2 +- package.json | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/package.json b/docs/package.json index e04a56f130c4..120aa91c60c3 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.49.0", + "version": "8.50.0", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/_data/rule_versions.json b/docs/src/_data/rule_versions.json index 3e0b0ad761d7..14e6d858ac02 100644 --- a/docs/src/_data/rule_versions.json +++ b/docs/src/_data/rule_versions.json @@ -307,7 +307,8 @@ "yoda": "0.7.1", "logical-assignment-operators": "8.24.0", "no-empty-static-block": "8.27.0", - "no-new-native-nonconstructor": "8.27.0" + "no-new-native-nonconstructor": "8.27.0", + "no-object-constructor": "8.50.0" }, "removed": { "generator-star": "1.0.0-rc-1", diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 7a49dbe3e1e0..2ac3df8c535c 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -10,7 +10,7 @@ "description": "Enforce `return` statements in callbacks of array methods", "recommended": false, "fixable": false, - "hasSuggestions": false + "hasSuggestions": true }, { "name": "constructor-super", diff --git a/docs/src/_data/rules_meta.json b/docs/src/_data/rules_meta.json index 862f013d3fa6..f40044241240 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -31,7 +31,8 @@ "description": "Enforce `return` statements in callbacks of array methods", "recommended": false, "url": "https://eslint.org/docs/latest/rules/array-callback-return" - } + }, + "hasSuggestions": true }, "array-element-newline": { "type": "layout", diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index f1b577386053..9545c0914c4b 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

- 9 problems (5 errors, 4 warnings) - Generated on Fri Sep 08 2023 16:22:14 GMT-0400 (Eastern Daylight Time) + 9 problems (5 errors, 4 warnings) - Generated on Fri Sep 22 2023 17:03:30 GMT-0400 (Eastern Daylight Time)
diff --git a/package.json b/package.json index 8bc8d546b579..989e3528a479 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.49.0", + "version": "8.50.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 24e1f140ec68659e55c1ace0d7500addb135a2b4 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 25 Sep 2023 16:18:35 -0400 Subject: [PATCH 188/248] chore: Refactor and document CodePath (#17558) * chore: Refactor and document CodePath * Clarify docs --- lib/linter/code-path-analysis/code-path.js | 160 ++++++++++++++++----- 1 file changed, 127 insertions(+), 33 deletions(-) diff --git a/lib/linter/code-path-analysis/code-path.js b/lib/linter/code-path-analysis/code-path.js index f6a88a00af92..3bf570d754bf 100644 --- a/lib/linter/code-path-analysis/code-path.js +++ b/lib/linter/code-path-analysis/code-path.js @@ -80,7 +80,9 @@ class CodePath { } /** - * The initial code path segment. + * The initial code path segment. This is the segment that is at the head + * of the code path. + * This is a passthrough to the underlying `CodePathState`. * @type {CodePathSegment} */ get initialSegment() { @@ -88,8 +90,10 @@ class CodePath { } /** - * Final code path segments. - * This array is a mix of `returnedSegments` and `thrownSegments`. + * Final code path segments. These are the terminal (tail) segments in the + * code path, which is the combination of `returnedSegments` and `thrownSegments`. + * All segments in this array are reachable. + * This is a passthrough to the underlying `CodePathState`. * @type {CodePathSegment[]} */ get finalSegments() { @@ -97,9 +101,14 @@ class CodePath { } /** - * Final code path segments which is with `return` statements. - * This array contains the last path segment if it's reachable. - * Since the reachable last path returns `undefined`. + * Final code path segments that represent normal completion of the code path. + * For functions, this means both explicit `return` statements and implicit returns, + * such as the last reachable segment in a function that does not have an + * explicit `return` as this implicitly returns `undefined`. For scripts, + * modules, class field initializers, and class static blocks, this means + * all lines of code have been executed. + * These segments are also present in `finalSegments`. + * This is a passthrough to the underlying `CodePathState`. * @type {CodePathSegment[]} */ get returnedSegments() { @@ -107,7 +116,9 @@ class CodePath { } /** - * Final code path segments which is with `throw` statements. + * Final code path segments that represent `throw` statements. + * This is a passthrough to the underlying `CodePathState`. + * These segments are also present in `finalSegments`. * @type {CodePathSegment[]} */ get thrownSegments() { @@ -115,7 +126,12 @@ class CodePath { } /** - * Current code path segments. + * Tracks the traversal of the code path through each segment. This array + * starts empty and segments are added or removed as the code path is + * traversed. This array always ends up empty at the end of a code path + * traversal. The `CodePathState` uses this to track its progress through + * the code path. + * This is a passthrough to the underlying `CodePathState`. * @type {CodePathSegment[]} * @deprecated */ @@ -126,46 +142,70 @@ class CodePath { /** * Traverses all segments in this code path. * - * codePath.traverseSegments(function(segment, controller) { + * codePath.traverseSegments((segment, controller) => { * // do something. * }); * * This method enumerates segments in order from the head. * - * The `controller` object has two methods. + * The `controller` argument has two methods: * - * - `controller.skip()` - Skip the following segments in this branch. - * - `controller.break()` - Skip all following segments. - * @param {Object} [options] Omittable. - * @param {CodePathSegment} [options.first] The first segment to traverse. - * @param {CodePathSegment} [options.last] The last segment to traverse. + * - `skip()` - skips the following segments in this branch + * - `break()` - skips all following segments in the traversal + * + * A note on the parameters: the `options` argument is optional. This means + * the first argument might be an options object or the callback function. + * @param {Object} [optionsOrCallback] Optional first and last segments to traverse. + * @param {CodePathSegment} [optionsOrCallback.first] The first segment to traverse. + * @param {CodePathSegment} [optionsOrCallback.last] The last segment to traverse. * @param {Function} callback A callback function. * @returns {void} */ - traverseSegments(options, callback) { + traverseSegments(optionsOrCallback, callback) { + + // normalize the arguments into a callback and options let resolvedOptions; let resolvedCallback; - if (typeof options === "function") { - resolvedCallback = options; + if (typeof optionsOrCallback === "function") { + resolvedCallback = optionsOrCallback; resolvedOptions = {}; } else { - resolvedOptions = options || {}; + resolvedOptions = optionsOrCallback || {}; resolvedCallback = callback; } + // determine where to start traversing from based on the options const startSegment = resolvedOptions.first || this.internal.initialSegment; const lastSegment = resolvedOptions.last; - let item = null; + // set up initial location information + let record = null; let index = 0; let end = 0; let segment = null; - const visited = Object.create(null); + + // segments that have already been visited during traversal + const visited = new Set(); + + // tracks the traversal steps const stack = [[startSegment, 0]]; + + // tracks the last skipped segment during traversal let skippedSegment = null; + + // indicates if we exited early from the traversal let broken = false; + + /** + * Maintains traversal state. + */ const controller = { + + /** + * Skip the following segments in this branch. + * @returns {void} + */ skip() { if (stack.length <= 1) { broken = true; @@ -173,32 +213,52 @@ class CodePath { skippedSegment = stack[stack.length - 2][0]; } }, + + /** + * Stop traversal completely - do not traverse to any + * other segments. + * @returns {void} + */ break() { broken = true; } }; /** - * Checks a given previous segment has been visited. + * Checks if a given previous segment has been visited. * @param {CodePathSegment} prevSegment A previous segment to check. * @returns {boolean} `true` if the segment has been visited. */ function isVisited(prevSegment) { return ( - visited[prevSegment.id] || + visited.has(prevSegment) || segment.isLoopedPrevSegment(prevSegment) ); } + // the traversal while (stack.length > 0) { - item = stack[stack.length - 1]; - segment = item[0]; - index = item[1]; + + /* + * This isn't a pure stack. We use the top record all the time + * but don't always pop it off. The record is popped only if + * one of the following is true: + * + * 1) We have already visited the segment. + * 2) We have not visited *all* of the previous segments. + * 3) We have traversed past the available next segments. + * + * Otherwise, we just read the value and sometimes modify the + * record as we traverse. + */ + record = stack[stack.length - 1]; + segment = record[0]; + index = record[1]; if (index === 0) { // Skip if this segment has been visited already. - if (visited[segment.id]) { + if (visited.has(segment)) { stack.pop(); continue; } @@ -212,18 +272,29 @@ class CodePath { continue; } - // Reset the flag of skipping if all branches have been skipped. + // Reset the skipping flag if all branches have been skipped. if (skippedSegment && segment.prevSegments.includes(skippedSegment)) { skippedSegment = null; } - visited[segment.id] = true; + visited.add(segment); - // Call the callback when the first time. + /* + * If the most recent segment hasn't been skipped, then we call + * the callback, passing in the segment and the controller. + */ if (!skippedSegment) { resolvedCallback.call(this, segment, controller); + + // exit if we're at the last segment if (segment === lastSegment) { controller.skip(); } + + /* + * If the previous statement was executed, or if the callback + * called a method on the controller, we might need to exit the + * loop, so check for that and break accordingly. + */ if (broken) { break; } @@ -233,12 +304,35 @@ class CodePath { // Update the stack. end = segment.nextSegments.length - 1; if (index < end) { - item[1] += 1; + + /* + * If we haven't yet visited all of the next segments, update + * the current top record on the stack to the next index to visit + * and then push a record for the current segment on top. + * + * Setting the current top record's index lets us know how many + * times we've been here and ensures that the segment won't be + * reprocessed (because we only process segments with an index + * of 0). + */ + record[1] += 1; stack.push([segment.nextSegments[index], 0]); } else if (index === end) { - item[0] = segment.nextSegments[index]; - item[1] = 0; + + /* + * If we are at the last next segment, then reset the top record + * in the stack to next segment and set its index to 0 so it will + * be processed next. + */ + record[0] = segment.nextSegments[index]; + record[1] = 0; } else { + + /* + * If index > end, that means we have no more segments that need + * processing. So, we pop that record off of the stack in order to + * continue traversing at the next level up. + */ stack.pop(); } } From bc77c9af12539f350ef19e30611a153a5b869c6b Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 26 Sep 2023 06:12:38 -0400 Subject: [PATCH 189/248] chore: Document and refactor ForkContext (#17566) * chore: Document and refactor ForkContext" * Update lib/linter/code-path-analysis/fork-context.js Co-authored-by: Francesco Trotta * Update lib/linter/code-path-analysis/fork-context.js Co-authored-by: Francesco Trotta * Update lib/linter/code-path-analysis/fork-context.js Co-authored-by: Francesco Trotta * Clean up comments --------- Co-authored-by: Francesco Trotta --- lib/linter/code-path-analysis/fork-context.js | 245 +++++++++++++----- 1 file changed, 173 insertions(+), 72 deletions(-) diff --git a/lib/linter/code-path-analysis/fork-context.js b/lib/linter/code-path-analysis/fork-context.js index 04c59b5e4172..33140272f53b 100644 --- a/lib/linter/code-path-analysis/fork-context.js +++ b/lib/linter/code-path-analysis/fork-context.js @@ -21,8 +21,8 @@ const assert = require("assert"), //------------------------------------------------------------------------------ /** - * Gets whether or not a given segment is reachable. - * @param {CodePathSegment} segment A segment to get. + * Determines whether or not a given segment is reachable. + * @param {CodePathSegment} segment The segment to check. * @returns {boolean} `true` if the segment is reachable. */ function isReachable(segment) { @@ -30,32 +30,64 @@ function isReachable(segment) { } /** - * Creates new segments from the specific range of `context.segmentsList`. + * Creates a new segment for each fork in the given context and appends it + * to the end of the specified range of segments. Ultimately, this ends up calling + * `new CodePathSegment()` for each of the forks using the `create` argument + * as a wrapper around special behavior. + * + * The `startIndex` and `endIndex` arguments specify a range of segments in + * `context` that should become `allPrevSegments` for the newly created + * `CodePathSegment` objects. * * When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and - * `end` is `-1`, this creates `[g, h]`. This `g` is from `a`, `c`, and `e`. - * This `h` is from `b`, `d`, and `f`. - * @param {ForkContext} context An instance. - * @param {number} begin The first index of the previous segments. - * @param {number} end The last index of the previous segments. - * @param {Function} create A factory function of new segments. - * @returns {CodePathSegment[]} New segments. + * `end` is `-1`, this creates two new segments, `[g, h]`. This `g` is appended to + * the end of the path from `a`, `c`, and `e`. This `h` is appended to the end of + * `b`, `d`, and `f`. + * @param {ForkContext} context An instance from which the previous segments + * will be obtained. + * @param {number} startIndex The index of the first segment in the context + * that should be specified as previous segments for the newly created segments. + * @param {number} endIndex The index of the last segment in the context + * that should be specified as previous segments for the newly created segments. + * @param {Function} create A function that creates new `CodePathSegment` + * instances in a particular way. See the `CodePathSegment.new*` methods. + * @returns {Array} An array of the newly-created segments. */ -function makeSegments(context, begin, end, create) { +function createSegments(context, startIndex, endIndex, create) { + + /** @type {Array>} */ const list = context.segmentsList; - const normalizedBegin = begin >= 0 ? begin : list.length + begin; - const normalizedEnd = end >= 0 ? end : list.length + end; + /* + * Both `startIndex` and `endIndex` work the same way: if the number is zero + * or more, then the number is used as-is. If the number is negative, + * then that number is added to the length of the segments list to + * determine the index to use. That means -1 for either argument + * is the last element, -2 is the second to last, and so on. + * + * So if `startIndex` is 0, `endIndex` is -1, and `list.length` is 3, the + * effective `startIndex` is 0 and the effective `endIndex` is 2, so this function + * will include items at indices 0, 1, and 2. + * + * Therefore, if `startIndex` is -1 and `endIndex` is -1, that means we'll only + * be using the last segment in `list`. + */ + const normalizedBegin = startIndex >= 0 ? startIndex : list.length + startIndex; + const normalizedEnd = endIndex >= 0 ? endIndex : list.length + endIndex; + /** @type {Array} */ const segments = []; for (let i = 0; i < context.count; ++i) { + + // this is passed into `new CodePathSegment` to add to code path. const allPrevSegments = []; for (let j = normalizedBegin; j <= normalizedEnd; ++j) { allPrevSegments.push(list[j][i]); } + // note: `create` is just a wrapper that augments `new CodePathSegment`. segments.push(create(context.idGenerator.next(), allPrevSegments)); } @@ -63,28 +95,57 @@ function makeSegments(context, begin, end, create) { } /** - * `segments` becomes doubly in a `finally` block. Then if a code path exits by a - * control statement (such as `break`, `continue`) from the `finally` block, the - * destination's segments may be half of the source segments. In that case, this - * merges segments. - * @param {ForkContext} context An instance. - * @param {CodePathSegment[]} segments Segments to merge. - * @returns {CodePathSegment[]} The merged segments. + * Inside of a `finally` block we end up with two parallel paths. If the code path + * exits by a control statement (such as `break` or `continue`) from the `finally` + * block, then we need to merge the remaining parallel paths back into one. + * @param {ForkContext} context The fork context to work on. + * @param {Array} segments Segments to merge. + * @returns {Array} The merged segments. */ function mergeExtraSegments(context, segments) { let currentSegments = segments; + /* + * We need to ensure that the array returned from this function contains no more + * than the number of segments that the context allows. `context.count` indicates + * how many items should be in the returned array to ensure that the new segment + * entries will line up with the already existing segment entries. + */ while (currentSegments.length > context.count) { const merged = []; - for (let i = 0, length = currentSegments.length / 2 | 0; i < length; ++i) { + /* + * Because `context.count` is a factor of 2 inside of a `finally` block, + * we can divide the segment count by 2 to merge the paths together. + * This loops through each segment in the list and creates a new `CodePathSegment` + * that has the segment and the segment two slots away as previous segments. + * + * If `currentSegments` is [a,b,c,d], this will create new segments e and f, such + * that: + * + * When `i` is 0: + * a->e + * c->e + * + * When `i` is 1: + * b->f + * d->f + */ + for (let i = 0, length = Math.floor(currentSegments.length / 2); i < length; ++i) { merged.push(CodePathSegment.newNext( context.idGenerator.next(), [currentSegments[i], currentSegments[i + length]] )); } + + /* + * Go through the loop condition one more time to see if we have the + * number of segments for the context. If not, we'll keep merging paths + * of the merged segments until we get there. + */ currentSegments = merged; } + return currentSegments; } @@ -93,25 +154,55 @@ function mergeExtraSegments(context, segments) { //------------------------------------------------------------------------------ /** - * A class to manage forking. + * Manages the forking of code paths. */ class ForkContext { /** + * Creates a new instance. * @param {IdGenerator} idGenerator An identifier generator for segments. - * @param {ForkContext|null} upper An upper fork context. - * @param {number} count A number of parallel segments. + * @param {ForkContext|null} upper The preceding fork context. + * @param {number} count The number of parallel segments in each element + * of `segmentsList`. */ constructor(idGenerator, upper, count) { + + /** + * The ID generator that will generate segment IDs for any new + * segments that are created. + * @type {IdGenerator} + */ this.idGenerator = idGenerator; + + /** + * The preceding fork context. + * @type {ForkContext|null} + */ this.upper = upper; + + /** + * The number of elements in each element of `segmentsList`. In most + * cases, this is 1 but can be 2 when there is a `finally` present, + * which forks the code path outside of normal flow. In the case of nested + * `finally` blocks, this can be a multiple of 2. + * @type {number} + */ this.count = count; + + /** + * The segments within this context. Each element in this array has + * `count` elements that represent one step in each fork. For example, + * when `segmentsList` is `[[a, b], [c, d], [e, f]]`, there is one path + * a->c->e and one path b->d->f, and `count` is 2 because each element + * is an array with two elements. + * @type {Array>} + */ this.segmentsList = []; } /** - * The head segments. - * @type {CodePathSegment[]} + * The segments that begin this fork context. + * @type {Array} */ get head() { const list = this.segmentsList; @@ -120,7 +211,7 @@ class ForkContext { } /** - * A flag which shows empty. + * Indicates if the context contains no segments. * @type {boolean} */ get empty() { @@ -128,7 +219,7 @@ class ForkContext { } /** - * A flag which shows reachable. + * Indicates if there are any segments that are reachable. * @type {boolean} */ get reachable() { @@ -138,75 +229,82 @@ class ForkContext { } /** - * Creates new segments from this context. - * @param {number} begin The first index of previous segments. - * @param {number} end The last index of previous segments. - * @returns {CodePathSegment[]} New segments. + * Creates new segments in this context and appends them to the end of the + * already existing `CodePathSegment`s specified by `startIndex` and + * `endIndex`. + * @param {number} startIndex The index of the first segment in the context + * that should be specified as previous segments for the newly created segments. + * @param {number} endIndex The index of the last segment in the context + * that should be specified as previous segments for the newly created segments. + * @returns {Array} An array of the newly created segments. */ - makeNext(begin, end) { - return makeSegments(this, begin, end, CodePathSegment.newNext); + makeNext(startIndex, endIndex) { + return createSegments(this, startIndex, endIndex, CodePathSegment.newNext); } /** - * Creates new segments from this context. - * The new segments is always unreachable. - * @param {number} begin The first index of previous segments. - * @param {number} end The last index of previous segments. - * @returns {CodePathSegment[]} New segments. + * Creates new unreachable segments in this context and appends them to the end of the + * already existing `CodePathSegment`s specified by `startIndex` and + * `endIndex`. + * @param {number} startIndex The index of the first segment in the context + * that should be specified as previous segments for the newly created segments. + * @param {number} endIndex The index of the last segment in the context + * that should be specified as previous segments for the newly created segments. + * @returns {Array} An array of the newly created segments. */ - makeUnreachable(begin, end) { - return makeSegments(this, begin, end, CodePathSegment.newUnreachable); + makeUnreachable(startIndex, endIndex) { + return createSegments(this, startIndex, endIndex, CodePathSegment.newUnreachable); } /** - * Creates new segments from this context. - * The new segments don't have connections for previous segments. - * But these inherit the reachable flag from this context. - * @param {number} begin The first index of previous segments. - * @param {number} end The last index of previous segments. - * @returns {CodePathSegment[]} New segments. + * Creates new segments in this context and does not append them to the end + * of the already existing `CodePathSegment`s specified by `startIndex` and + * `endIndex`. The `startIndex` and `endIndex` are only used to determine if + * the new segments should be reachable. If any of the segments in this range + * are reachable then the new segments are also reachable; otherwise, the new + * segments are unreachable. + * @param {number} startIndex The index of the first segment in the context + * that should be considered for reachability. + * @param {number} endIndex The index of the last segment in the context + * that should be considered for reachability. + * @returns {Array} An array of the newly created segments. */ - makeDisconnected(begin, end) { - return makeSegments(this, begin, end, CodePathSegment.newDisconnected); + makeDisconnected(startIndex, endIndex) { + return createSegments(this, startIndex, endIndex, CodePathSegment.newDisconnected); } /** - * Adds segments into this context. - * The added segments become the head. - * @param {CodePathSegment[]} segments Segments to add. + * Adds segments to the head of this context. + * @param {Array} segments The segments to add. * @returns {void} */ add(segments) { assert(segments.length >= this.count, `${segments.length} >= ${this.count}`); - this.segmentsList.push(mergeExtraSegments(this, segments)); } /** - * Replaces the head segments with given segments. + * Replaces the head segments with the given segments. * The current head segments are removed. - * @param {CodePathSegment[]} segments Segments to add. + * @param {Array} replacementHeadSegments The new head segments. * @returns {void} */ - replaceHead(segments) { - assert(segments.length >= this.count, `${segments.length} >= ${this.count}`); - - this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments)); + replaceHead(replacementHeadSegments) { + assert( + replacementHeadSegments.length >= this.count, + `${replacementHeadSegments.length} >= ${this.count}` + ); + this.segmentsList.splice(-1, 1, mergeExtraSegments(this, replacementHeadSegments)); } /** * Adds all segments of a given fork context into this context. - * @param {ForkContext} context A fork context to add. + * @param {ForkContext} otherForkContext The fork context to add from. * @returns {void} */ - addAll(context) { - assert(context.count === this.count); - - const source = context.segmentsList; - - for (let i = 0; i < source.length; ++i) { - this.segmentsList.push(source[i]); - } + addAll(otherForkContext) { + assert(otherForkContext.count === this.count); + this.segmentsList.push(...otherForkContext.segmentsList); } /** @@ -218,7 +316,8 @@ class ForkContext { } /** - * Creates the root fork context. + * Creates a new root context, meaning that there are no parent + * fork contexts. * @param {IdGenerator} idGenerator An identifier generator for segments. * @returns {ForkContext} New fork context. */ @@ -233,14 +332,16 @@ class ForkContext { /** * Creates an empty fork context preceded by a given context. * @param {ForkContext} parentContext The parent fork context. - * @param {boolean} forkLeavingPath A flag which shows inside of `finally` block. + * @param {boolean} shouldForkLeavingPath Indicates that we are inside of + * a `finally` block and should therefore fork the path that leaves + * `finally`. * @returns {ForkContext} New fork context. */ - static newEmpty(parentContext, forkLeavingPath) { + static newEmpty(parentContext, shouldForkLeavingPath) { return new ForkContext( parentContext.idGenerator, parentContext, - (forkLeavingPath ? 2 : 1) * parentContext.count + (shouldForkLeavingPath ? 2 : 1) * parentContext.count ); } } From 977e67ec274a05cb7391665b5e3453e7f72f72b2 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Wed, 27 Sep 2023 22:32:22 +0900 Subject: [PATCH 190/248] feat: logical-assignment-operators to report expressions with 3 operands (#17600) * fix: logical-assignment-operators to report expressions with 3 operands * Apply suggestions from code review Co-authored-by: Milos Djermanovic * test: add more `??` test cases * docs: add explanation of always option * Update docs/src/rules/logical-assignment-operators.md Co-authored-by: Nicholas C. Zakas --------- Co-authored-by: Milos Djermanovic Co-authored-by: Nicholas C. Zakas --- .../src/rules/logical-assignment-operators.md | 10 +- lib/rules/logical-assignment-operators.js | 34 +++- .../lib/rules/logical-assignment-operators.js | 167 ++++++++++++++++++ 3 files changed, 207 insertions(+), 4 deletions(-) diff --git a/docs/src/rules/logical-assignment-operators.md b/docs/src/rules/logical-assignment-operators.md index 058afcd3ad78..1b59cfa70bba 100644 --- a/docs/src/rules/logical-assignment-operators.md +++ b/docs/src/rules/logical-assignment-operators.md @@ -10,7 +10,7 @@ For example `a = a || b` can be shortened to `a ||= b`. ## Rule Details -This rule requires or disallows logical assignment operator shorthand. +This rule requires or disallows logical assignment operator shorthand. ### Options @@ -27,6 +27,9 @@ Object option (only available if string option is set to `"always"`): #### always +This option checks for expressions that can be shortened using logical assignment operator. For example, `a = a || b` can be shortened to `a ||= b`. +Expressions with associativity such as `a = a || b || c` are reported as being able to be shortened to `a ||= b || c` unless the evaluation order is explicitly defined using parentheses, such as `a = (a || b) || c`. + Examples of **incorrect** code for this rule with the default `"always"` option: ::: incorrect @@ -40,6 +43,9 @@ a = a ?? b a || (a = b) a && (a = b) a ?? (a = b) +a = a || b || c +a = a && b && c +a = a ?? b ?? c ``` ::: @@ -58,6 +64,8 @@ a = b || c a || (b = c) if (a) a = b + +a = (a || b) || c ``` ::: diff --git a/lib/rules/logical-assignment-operators.js b/lib/rules/logical-assignment-operators.js index 27ca585e9958..c084c04c8eda 100644 --- a/lib/rules/logical-assignment-operators.js +++ b/lib/rules/logical-assignment-operators.js @@ -150,6 +150,31 @@ function isInsideWithBlock(node) { return node.parent.type === "WithStatement" && node.parent.body === node ? true : isInsideWithBlock(node.parent); } +/** + * Gets the leftmost operand of a consecutive logical expression. + * @param {SourceCode} sourceCode The ESLint source code object + * @param {LogicalExpression} node LogicalExpression + * @returns {Expression} Leftmost operand + */ +function getLeftmostOperand(sourceCode, node) { + let left = node.left; + + while (left.type === "LogicalExpression" && left.operator === node.operator) { + + if (astUtils.isParenthesised(sourceCode, left)) { + + /* + * It should have associativity, + * but ignore it if use parentheses to make the evaluation order clear. + */ + return left; + } + left = left.left; + } + return left; + +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -318,7 +343,10 @@ module.exports = { // foo = foo || bar "AssignmentExpression[operator='='][right.type='LogicalExpression']"(assignment) { - if (!astUtils.isSameReference(assignment.left, assignment.right.left)) { + const leftOperand = getLeftmostOperand(sourceCode, assignment.right); + + if (!astUtils.isSameReference(assignment.left, leftOperand) + ) { return; } @@ -342,10 +370,10 @@ module.exports = { yield ruleFixer.insertTextBefore(assignmentOperatorToken, assignment.right.operator); // -> foo ||= bar - const logicalOperatorToken = getOperatorToken(assignment.right); + const logicalOperatorToken = getOperatorToken(leftOperand.parent); const firstRightOperandToken = sourceCode.getTokenAfter(logicalOperatorToken); - yield ruleFixer.removeRange([assignment.right.range[0], firstRightOperandToken.range[0]]); + yield ruleFixer.removeRange([leftOperand.parent.range[0], firstRightOperandToken.range[0]]); } }; diff --git a/tests/lib/rules/logical-assignment-operators.js b/tests/lib/rules/logical-assignment-operators.js index 36756815a90f..471416322d2b 100644 --- a/tests/lib/rules/logical-assignment-operators.js +++ b/tests/lib/rules/logical-assignment-operators.js @@ -354,6 +354,28 @@ ruleTester.run("logical-assignment-operators", rule, { }, { code: "a.b = a.b || c", options: ["never"] + }, + + // 3 or more operands + { + code: "a = a && b || c", + options: ["always"] + }, + { + code: "a = a && b && c || d", + options: ["always"] + }, + { + code: "a = (a || b) || c", // Allow if parentheses are used. + options: ["always"] + }, + { + code: "a = (a && b) && c", // Allow if parentheses are used. + options: ["always"] + }, + { + code: "a = (a ?? b) ?? c", // Allow if parentheses are used. + options: ["always"] } ], invalid: [ @@ -1511,6 +1533,151 @@ ruleTester.run("logical-assignment-operators", rule, { output: "(a.b.c ||= d) as number" }] }] + }, + + // 3 or more operands + { + code: "a = a || b || c", + output: "a ||= b || c", + options: ["always"], + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [] + }] + }, + { + code: "a = a && b && c", + output: "a &&= b && c", + options: ["always"], + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "&&=" }, + suggestions: [] + }] + }, + { + code: "a = a ?? b ?? c", + output: "a ??= b ?? c", + options: ["always"], + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [] + }] + }, + { + code: "a = a || b && c", + output: "a ||= b && c", + options: ["always"], + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [] + }] + }, + { + code: "a = a || b || c || d", + output: "a ||= b || c || d", + options: ["always"], + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [] + }] + }, + { + code: "a = a && b && c && d", + output: "a &&= b && c && d", + options: ["always"], + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "&&=" }, + suggestions: [] + }] + }, + { + code: "a = a ?? b ?? c ?? d", + output: "a ??= b ?? c ?? d", + options: ["always"], + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "??=" }, + suggestions: [] + }] + }, + { + code: "a = a || b || c && d", + output: "a ||= b || c && d", + options: ["always"], + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [] + }] + }, + { + code: "a = a || b && c || d", + output: "a ||= b && c || d", + options: ["always"], + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [] + }] + }, + { + code: "a = (a) || b || c", + output: "a ||= b || c", + options: ["always"], + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [] + }] + }, + { + code: "a = a || (b || c) || d", + output: "a ||= (b || c) || d", + options: ["always"], + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [] + }] + }, + { + code: "a = (a || b || c)", + output: "a ||= (b || c)", + options: ["always"], + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [] + }] + }, + { + code: "a = ((a) || (b || c) || d)", + output: "a ||= ((b || c) || d)", + options: ["always"], + errors: [{ + messageId: "assignment", + type: "AssignmentExpression", + data: { operator: "||=" }, + suggestions: [] + }] } ] }); From 7b77bccbb51bd36b2d20fea61bc782545c4029b3 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 29 Sep 2023 11:18:51 -0400 Subject: [PATCH 191/248] chore: Refactor CodePathState (#17510) * chore: Refactor and document CodePathState * Add context classes * More refactoring and JSDoc * Refactor of try context * More JSDoc and BreakContext * More refactoring and JSDoc * More comments * Incorporate feedback * Update lib/linter/code-path-analysis/code-path-state.js Co-authored-by: Milos Djermanovic * Update lib/linter/code-path-analysis/code-path-state.js Co-authored-by: Milos Djermanovic * Update lib/linter/code-path-analysis/code-path-state.js Co-authored-by: Milos Djermanovic * Update lib/linter/code-path-analysis/code-path-state.js Co-authored-by: Milos Djermanovic * Update lib/linter/code-path-analysis/code-path-state.js Co-authored-by: Milos Djermanovic * Update lib/linter/code-path-analysis/code-path-state.js Co-authored-by: Milos Djermanovic * Update lib/linter/code-path-analysis/code-path-state.js Co-authored-by: Milos Djermanovic * Update lib/linter/code-path-analysis/code-path-state.js Co-authored-by: Milos Djermanovic * Update lib/linter/code-path-analysis/code-path-state.js Co-authored-by: Milos Djermanovic * Update lib/linter/code-path-analysis/code-path-state.js Co-authored-by: Milos Djermanovic * Clean up loop context classes and comments * Update lib/linter/code-path-analysis/code-path-state.js Co-authored-by: Milos Djermanovic * Update lib/linter/code-path-analysis/code-path-state.js Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- .../code-path-analysis/code-path-state.js | 1218 ++++++++++++++--- 1 file changed, 1019 insertions(+), 199 deletions(-) diff --git a/lib/linter/code-path-analysis/code-path-state.js b/lib/linter/code-path-analysis/code-path-state.js index d187297d32b0..dcd24691a7f1 100644 --- a/lib/linter/code-path-analysis/code-path-state.js +++ b/lib/linter/code-path-analysis/code-path-state.js @@ -12,13 +12,622 @@ const CodePathSegment = require("./code-path-segment"), ForkContext = require("./fork-context"); +//----------------------------------------------------------------------------- +// Contexts +//----------------------------------------------------------------------------- + +/** + * Represents the context in which a `break` statement can be used. + * + * A `break` statement without a label is only valid in a few places in + * JavaScript: any type of loop or a `switch` statement. Otherwise, `break` + * without a label causes a syntax error. For these contexts, `breakable` is + * set to `true` to indicate that a `break` without a label is valid. + * + * However, a `break` statement with a label is also valid inside of a labeled + * statement. For example, this is valid: + * + * a : { + * break a; + * } + * + * The `breakable` property is set false for labeled statements to indicate + * that `break` without a label is invalid. + */ +class BreakContext { + + /** + * Creates a new instance. + * @param {BreakContext} upperContext The previous `BreakContext`. + * @param {boolean} breakable Indicates if we are inside a statement where + * `break` without a label will exit the statement. + * @param {string|null} label The label for the statement. + * @param {ForkContext} forkContext The current fork context. + */ + constructor(upperContext, breakable, label, forkContext) { + + /** + * The previous `BreakContext` + * @type {BreakContext} + */ + this.upper = upperContext; + + /** + * Indicates if we are inside a statement where `break` without a label + * will exit the statement. + * @type {boolean} + */ + this.breakable = breakable; + + /** + * The label associated with the statement. + * @type {string|null} + */ + this.label = label; + + /** + * The fork context for the `break`. + * @type {ForkContext} + */ + this.brokenForkContext = ForkContext.newEmpty(forkContext); + } +} + +/** + * Represents the context for `ChainExpression` nodes. + */ +class ChainContext { + + /** + * Creates a new instance. + * @param {ChainContext} upperContext The previous `ChainContext`. + */ + constructor(upperContext) { + + /** + * The previous `ChainContext` + * @type {ChainContext} + */ + this.upper = upperContext; + + /** + * The number of choice contexts inside of the `ChainContext`. + * @type {number} + */ + this.choiceContextCount = 0; + + } +} + +/** + * Represents a choice in the code path. + * + * Choices are created by logical operators such as `&&`, loops, conditionals, + * and `if` statements. This is the point at which the code path has a choice of + * which direction to go. + * + * The result of a choice might be in the left (test) expression of another choice, + * and in that case, may create a new fork. For example, `a || b` is a choice + * but does not create a new fork because the result of the expression is + * not used as the test expression in another expression. In this case, + * `isForkingAsResult` is false. In the expression `a || b || c`, the `a || b` + * expression appears as the test expression for `|| c`, so the + * result of `a || b` creates a fork because execution may or may not + * continue to `|| c`. `isForkingAsResult` for `a || b` in this case is true + * while `isForkingAsResult` for `|| c` is false. (`isForkingAsResult` is always + * false for `if` statements, conditional expressions, and loops.) + * + * All of the choices except one (`??`) operate on a true/false fork, meaning if + * true go one way and if false go the other (tracked by `trueForkContext` and + * `falseForkContext`). The `??` operator doesn't operate on true/false because + * the left expression is evaluated to be nullish or not, so only if nullish do + * we fork to the right expression (tracked by `qqForkcontext`). + */ +class ChoiceContext { + + /** + * Creates a new instance. + * @param {ChoiceContext} upperContext The previous `ChoiceContext`. + * @param {string} kind The kind of choice. If it's a logical or assignment expression, this + * is `"&&"` or `"||"` or `"??"`; if it's an `if` statement or + * conditional expression, this is `"test"`; otherwise, this is `"loop"`. + * @param {boolean} isForkingAsResult Indicates if the result of the choice + * creates a fork. + * @param {ForkContext} forkContext The containing `ForkContext`. + */ + constructor(upperContext, kind, isForkingAsResult, forkContext) { + + /** + * The previous `ChoiceContext` + * @type {ChoiceContext} + */ + this.upper = upperContext; + + /** + * The kind of choice. If it's a logical or assignment expression, this + * is `"&&"` or `"||"` or `"??"`; if it's an `if` statement or + * conditional expression, this is `"test"`; otherwise, this is `"loop"`. + * @type {string} + */ + this.kind = kind; + + /** + * Indicates if the result of the choice forks the code path. + * @type {boolean} + */ + this.isForkingAsResult = isForkingAsResult; + + /** + * The fork context for the `true` path of the choice. + * @type {ForkContext} + */ + this.trueForkContext = ForkContext.newEmpty(forkContext); + + /** + * The fork context for the `false` path of the choice. + * @type {ForkContext} + */ + this.falseForkContext = ForkContext.newEmpty(forkContext); + + /** + * The fork context for the right side of the `??` path of the choice. + * @type {ForkContext} + */ + this.qqForkContext = ForkContext.newEmpty(forkContext); + + /** + * Indicates if any of `trueForkContext`, `falseForkContext`, or + * `qqForkContext` have been updated with segments from a child context. + * @type {boolean} + */ + this.processed = false; + } + +} + +/** + * Base class for all loop contexts. + */ +class LoopContextBase { + + /** + * Creates a new instance. + * @param {LoopContext|null} upperContext The previous `LoopContext`. + * @param {string} type The AST node's `type` for the loop. + * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. + * @param {BreakContext} breakContext The context for breaking the loop. + */ + constructor(upperContext, type, label, breakContext) { + + /** + * The previous `LoopContext`. + * @type {LoopContext} + */ + this.upper = upperContext; + + /** + * The AST node's `type` for the loop. + * @type {string} + */ + this.type = type; + + /** + * The label for the loop from an enclosing `LabeledStatement`. + * @type {string|null} + */ + this.label = label; + + /** + * The fork context for when `break` is encountered. + * @type {ForkContext} + */ + this.brokenForkContext = breakContext.brokenForkContext; + } +} + +/** + * Represents the context for a `while` loop. + */ +class WhileLoopContext extends LoopContextBase { + + /** + * Creates a new instance. + * @param {LoopContext|null} upperContext The previous `LoopContext`. + * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. + * @param {BreakContext} breakContext The context for breaking the loop. + */ + constructor(upperContext, label, breakContext) { + super(upperContext, "WhileStatement", label, breakContext); + + /** + * The hardcoded literal boolean test condition for + * the loop. Used to catch infinite or skipped loops. + * @type {boolean|undefined} + */ + this.test = void 0; + + /** + * The segments representing the test condition where `continue` will + * jump to. The test condition will typically have just one segment but + * it's possible for there to be more than one. + * @type {Array|null} + */ + this.continueDestSegments = null; + } +} + +/** + * Represents the context for a `do-while` loop. + */ +class DoWhileLoopContext extends LoopContextBase { + + /** + * Creates a new instance. + * @param {LoopContext|null} upperContext The previous `LoopContext`. + * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. + * @param {BreakContext} breakContext The context for breaking the loop. + * @param {ForkContext} forkContext The enclosing fork context. + */ + constructor(upperContext, label, breakContext, forkContext) { + super(upperContext, "DoWhileStatement", label, breakContext); + + /** + * The hardcoded literal boolean test condition for + * the loop. Used to catch infinite or skipped loops. + * @type {boolean|undefined} + */ + this.test = void 0; + + /** + * The segments at the start of the loop body. This is the only loop + * where the test comes at the end, so the first iteration always + * happens and we need a reference to the first statements. + * @type {Array|null} + */ + this.entrySegments = null; + + /** + * The fork context to follow when a `continue` is found. + * @type {ForkContext} + */ + this.continueForkContext = ForkContext.newEmpty(forkContext); + } +} + +/** + * Represents the context for a `for` loop. + */ +class ForLoopContext extends LoopContextBase { + + /** + * Creates a new instance. + * @param {LoopContext|null} upperContext The previous `LoopContext`. + * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. + * @param {BreakContext} breakContext The context for breaking the loop. + */ + constructor(upperContext, label, breakContext) { + super(upperContext, "ForStatement", label, breakContext); + + /** + * The hardcoded literal boolean test condition for + * the loop. Used to catch infinite or skipped loops. + * @type {boolean|undefined} + */ + this.test = void 0; + + /** + * The end of the init expression. This may change during the lifetime + * of the instance as we traverse the loop because some loops don't have + * an init expression. + * @type {Array|null} + */ + this.endOfInitSegments = null; + + /** + * The start of the test expression. This may change during the lifetime + * of the instance as we traverse the loop because some loops don't have + * a test expression. + * @type {Array|null} + */ + this.testSegments = null; + + /** + * The end of the test expression. This may change during the lifetime + * of the instance as we traverse the loop because some loops don't have + * a test expression. + * @type {Array|null} + */ + this.endOfTestSegments = null; + + /** + * The start of the update expression. This may change during the lifetime + * of the instance as we traverse the loop because some loops don't have + * an update expression. + * @type {Array|null} + */ + this.updateSegments = null; + + /** + * The end of the update expresion. This may change during the lifetime + * of the instance as we traverse the loop because some loops don't have + * an update expression. + * @type {Array|null} + */ + this.endOfUpdateSegments = null; + + /** + * The segments representing the test condition where `continue` will + * jump to. The test condition will typically have just one segment but + * it's possible for there to be more than one. This may change during the + * lifetime of the instance as we traverse the loop because some loops + * don't have an update expression. When there is an update expression, this + * will end up pointing to that expression; otherwise it will end up pointing + * to the test expression. + * @type {Array|null} + */ + this.continueDestSegments = null; + } +} + +/** + * Represents the context for a `for-in` loop. + * + * Terminology: + * - "left" means the part of the loop to the left of the `in` keyword. For + * example, in `for (var x in y)`, the left is `var x`. + * - "right" means the part of the loop to the right of the `in` keyword. For + * example, in `for (var x in y)`, the right is `y`. + */ +class ForInLoopContext extends LoopContextBase { + + /** + * Creates a new instance. + * @param {LoopContext|null} upperContext The previous `LoopContext`. + * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. + * @param {BreakContext} breakContext The context for breaking the loop. + */ + constructor(upperContext, label, breakContext) { + super(upperContext, "ForInStatement", label, breakContext); + + /** + * The segments that came immediately before the start of the loop. + * This allows you to traverse backwards out of the loop into the + * surrounding code. This is necessary to evaluate the right expression + * correctly, as it must be evaluated in the same way as the left + * expression, but the pointer to these segments would otherwise be + * lost if not stored on the instance. Once the right expression has + * been evaluated, this property is no longer used. + * @type {Array|null} + */ + this.prevSegments = null; + + /** + * Segments representing the start of everything to the left of the + * `in` keyword. This can be used to move forward towards + * `endOfLeftSegments`. `leftSegments` and `endOfLeftSegments` are + * effectively the head and tail of a doubly-linked list. + * @type {Array|null} + */ + this.leftSegments = null; + + /** + * Segments representing the end of everything to the left of the + * `in` keyword. This can be used to move backward towards `leftSegments`. + * `leftSegments` and `endOfLeftSegments` are effectively the head + * and tail of a doubly-linked list. + * @type {Array|null} + */ + this.endOfLeftSegments = null; + + /** + * The segments representing the left expression where `continue` will + * jump to. In `for-in` loops, `continue` must always re-execute the + * left expression each time through the loop. This contains the same + * segments as `leftSegments`, but is duplicated here so each loop + * context has the same property pointing to where `continue` should + * end up. + * @type {Array|null} + */ + this.continueDestSegments = null; + } +} + +/** + * Represents the context for a `for-of` loop. + */ +class ForOfLoopContext extends LoopContextBase { + + /** + * Creates a new instance. + * @param {LoopContext|null} upperContext The previous `LoopContext`. + * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. + * @param {BreakContext} breakContext The context for breaking the loop. + */ + constructor(upperContext, label, breakContext) { + super(upperContext, "ForOfStatement", label, breakContext); + + /** + * The segments that came immediately before the start of the loop. + * This allows you to traverse backwards out of the loop into the + * surrounding code. This is necessary to evaluate the right expression + * correctly, as it must be evaluated in the same way as the left + * expression, but the pointer to these segments would otherwise be + * lost if not stored on the instance. Once the right expression has + * been evaluated, this property is no longer used. + * @type {Array|null} + */ + this.prevSegments = null; + + /** + * Segments representing the start of everything to the left of the + * `of` keyword. This can be used to move forward towards + * `endOfLeftSegments`. `leftSegments` and `endOfLeftSegments` are + * effectively the head and tail of a doubly-linked list. + * @type {Array|null} + */ + this.leftSegments = null; + + /** + * Segments representing the end of everything to the left of the + * `of` keyword. This can be used to move backward towards `leftSegments`. + * `leftSegments` and `endOfLeftSegments` are effectively the head + * and tail of a doubly-linked list. + * @type {Array|null} + */ + this.endOfLeftSegments = null; + + /** + * The segments representing the left expression where `continue` will + * jump to. In `for-in` loops, `continue` must always re-execute the + * left expression each time through the loop. This contains the same + * segments as `leftSegments`, but is duplicated here so each loop + * context has the same property pointing to where `continue` should + * end up. + * @type {Array|null} + */ + this.continueDestSegments = null; + } +} + +/** + * Represents the context for any loop. + * @typedef {WhileLoopContext|DoWhileLoopContext|ForLoopContext|ForInLoopContext|ForOfLoopContext} LoopContext + */ + +/** + * Represents the context for a `switch` statement. + */ +class SwitchContext { + + /** + * Creates a new instance. + * @param {SwitchContext} upperContext The previous context. + * @param {boolean} hasCase Indicates if there is at least one `case` statement. + * `default` doesn't count. + */ + constructor(upperContext, hasCase) { + + /** + * The previous context. + * @type {SwitchContext} + */ + this.upper = upperContext; + + /** + * Indicates if there is at least one `case` statement. `default` doesn't count. + * @type {boolean} + */ + this.hasCase = hasCase; + + /** + * The `default` keyword. + * @type {Array|null} + */ + this.defaultSegments = null; + + /** + * The default case body starting segments. + * @type {Array|null} + */ + this.defaultBodySegments = null; + + /** + * Indicates if a `default` case and is empty exists. + * @type {boolean} + */ + this.foundEmptyDefault = false; + + /** + * Indicates that a `default` exists and is the last case. + * @type {boolean} + */ + this.lastIsDefault = false; + + /** + * The number of fork contexts created. This is equivalent to the + * number of `case` statements plus a `default` statement (if present). + * @type {number} + */ + this.forkCount = 0; + } +} + +/** + * Represents the context for a `try` statement. + */ +class TryContext { + + /** + * Creates a new instance. + * @param {TryContext} upperContext The previous context. + * @param {boolean} hasFinalizer Indicates if the `try` statement has a + * `finally` block. + * @param {ForkContext} forkContext The enclosing fork context. + */ + constructor(upperContext, hasFinalizer, forkContext) { + + /** + * The previous context. + * @type {TryContext} + */ + this.upper = upperContext; + + /** + * Indicates if the `try` statement has a `finally` block. + * @type {boolean} + */ + this.hasFinalizer = hasFinalizer; + + /** + * Tracks the traversal position inside of the `try` statement. This is + * used to help determine the context necessary to create paths because + * a `try` statement may or may not have `catch` or `finally` blocks, + * and code paths behave differently in those blocks. + * @type {"try"|"catch"|"finally"} + */ + this.position = "try"; + + /** + * If the `try` statement has a `finally` block, this affects how a + * `return` statement behaves in the `try` block. Without `finally`, + * `return` behaves as usual and doesn't require a fork; with `finally`, + * `return` forks into the `finally` block, so we need a fork context + * to track it. + * @type {ForkContext|null} + */ + this.returnedForkContext = hasFinalizer + ? ForkContext.newEmpty(forkContext) + : null; + + /** + * When a `throw` occurs inside of a `try` block, the code path forks + * into the `catch` or `finally` blocks, and this fork context tracks + * that path. + * @type {ForkContext} + */ + this.thrownForkContext = ForkContext.newEmpty(forkContext); + + /** + * Indicates if the last segment in the `try` block is reachable. + * @type {boolean} + */ + this.lastOfTryIsReachable = false; + + /** + * Indicates if the last segment in the `catch` block is reachable. + * @type {boolean} + */ + this.lastOfCatchIsReachable = false; + } +} + //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ /** * Adds given segments into the `dest` array. - * If the `others` array does not includes the given segments, adds to the `all` + * If the `others` array does not include the given segments, adds to the `all` * array as well. * * This adds only reachable and used segments. @@ -40,9 +649,9 @@ function addToReturnedOrThrown(dest, others, all, segments) { } /** - * Gets a loop-context for a `continue` statement. - * @param {CodePathState} state A state to get. - * @param {string} label The label of a `continue` statement. + * Gets a loop context for a `continue` statement based on a given label. + * @param {CodePathState} state The state to search within. + * @param {string|null} label The label of a `continue` statement. * @returns {LoopContext} A loop-context for a `continue` statement. */ function getContinueContext(state, label) { @@ -65,9 +674,9 @@ function getContinueContext(state, label) { /** * Gets a context for a `break` statement. - * @param {CodePathState} state A state to get. - * @param {string} label The label of a `break` statement. - * @returns {LoopContext|SwitchContext} A context for a `break` statement. + * @param {CodePathState} state The state to search within. + * @param {string|null} label The label of a `break` statement. + * @returns {BreakContext} A context for a `break` statement. */ function getBreakContext(state, label) { let context = state.breakContext; @@ -84,8 +693,10 @@ function getBreakContext(state, label) { } /** - * Gets a context for a `return` statement. - * @param {CodePathState} state A state to get. + * Gets a context for a `return` statement. There is just one special case: + * if there is a `try` statement with a `finally` block, because that alters + * how `return` behaves; otherwise, this just passes through the given state. + * @param {CodePathState} state The state to search within * @returns {TryContext|CodePathState} A context for a `return` statement. */ function getReturnContext(state) { @@ -102,8 +713,11 @@ function getReturnContext(state) { } /** - * Gets a context for a `throw` statement. - * @param {CodePathState} state A state to get. + * Gets a context for a `throw` statement. There is just one special case: + * if there is a `try` statement with a `finally` block and we are inside of + * a `catch` because that changes how `throw` behaves; otherwise, this just + * passes through the given state. + * @param {CodePathState} state The state to search within. * @returns {TryContext|CodePathState} A context for a `throw` statement. */ function getThrowContext(state) { @@ -122,13 +736,13 @@ function getThrowContext(state) { } /** - * Removes a given element from a given array. - * @param {any[]} xs An array to remove the specific element. - * @param {any} x An element to be removed. + * Removes a given value from a given array. + * @param {any[]} elements An array to remove the specific element. + * @param {any} value The value to be removed. * @returns {void} */ -function remove(xs, x) { - xs.splice(xs.indexOf(x), 1); +function removeFromArray(elements, value) { + elements.splice(elements.indexOf(value), 1); } /** @@ -141,48 +755,77 @@ function remove(xs, x) { * @param {CodePathSegment[]} nextSegments Backward segments to disconnect. * @returns {void} */ -function removeConnection(prevSegments, nextSegments) { +function disconnectSegments(prevSegments, nextSegments) { for (let i = 0; i < prevSegments.length; ++i) { const prevSegment = prevSegments[i]; const nextSegment = nextSegments[i]; - remove(prevSegment.nextSegments, nextSegment); - remove(prevSegment.allNextSegments, nextSegment); - remove(nextSegment.prevSegments, prevSegment); - remove(nextSegment.allPrevSegments, prevSegment); + removeFromArray(prevSegment.nextSegments, nextSegment); + removeFromArray(prevSegment.allNextSegments, nextSegment); + removeFromArray(nextSegment.prevSegments, prevSegment); + removeFromArray(nextSegment.allPrevSegments, prevSegment); } } /** - * Creates looping path. - * @param {CodePathState} state The instance. + * Creates looping path between two arrays of segments, ensuring that there are + * paths going between matching segments in the arrays. + * @param {CodePathState} state The state to operate on. * @param {CodePathSegment[]} unflattenedFromSegments Segments which are source. * @param {CodePathSegment[]} unflattenedToSegments Segments which are destination. * @returns {void} */ function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) { + const fromSegments = CodePathSegment.flattenUnusedSegments(unflattenedFromSegments); const toSegments = CodePathSegment.flattenUnusedSegments(unflattenedToSegments); - const end = Math.min(fromSegments.length, toSegments.length); + /* + * This loop effectively updates a doubly-linked list between two collections + * of segments making sure that segments in the same array indices are + * combined to create a path. + */ for (let i = 0; i < end; ++i) { + + // get the segments in matching array indices const fromSegment = fromSegments[i]; const toSegment = toSegments[i]; + /* + * If the destination segment is reachable, then create a path from the + * source segment to the destination segment. + */ if (toSegment.reachable) { fromSegment.nextSegments.push(toSegment); } + + /* + * If the source segment is reachable, then create a path from the + * destination segment back to the source segment. + */ if (fromSegment.reachable) { toSegment.prevSegments.push(fromSegment); } + + /* + * Also update the arrays that don't care if the segments are reachable + * or not. This should always happen regardless of anything else. + */ fromSegment.allNextSegments.push(toSegment); toSegment.allPrevSegments.push(fromSegment); + /* + * If the destination segment has at least two previous segments in its + * path then that means there was one previous segment before this iteration + * of the loop was executed. So, we need to mark the source segment as + * looped. + */ if (toSegment.allPrevSegments.length >= 2) { CodePathSegment.markPrevSegmentAsLooped(toSegment, fromSegment); } + // let the code path analyzer know that there's been a loop created state.notifyLooped(fromSegment, toSegment); } } @@ -198,15 +841,27 @@ function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) { * @returns {void} */ function finalizeTestSegmentsOfFor(context, choiceContext, head) { + + /* + * If this choice context doesn't already contain paths from a + * child context, then add the current head to each potential path. + */ if (!choiceContext.processed) { choiceContext.trueForkContext.add(head); choiceContext.falseForkContext.add(head); choiceContext.qqForkContext.add(head); } + /* + * If the test condition isn't a hardcoded truthy value, then `break` + * must follow the same path as if the test condition is false. To represent + * that, we append the path for when the loop test is false (represented by + * `falseForkContext`) to the `brokenForkContext`. + */ if (context.test !== true) { context.brokenForkContext.addAll(choiceContext.falseForkContext); } + context.endOfTestSegments = choiceContext.trueForkContext.makeNext(0, -1); } @@ -220,35 +875,124 @@ function finalizeTestSegmentsOfFor(context, choiceContext, head) { class CodePathState { /** + * Creates a new instance. * @param {IdGenerator} idGenerator An id generator to generate id for code * path segments. * @param {Function} onLooped A callback function to notify looping. */ constructor(idGenerator, onLooped) { + + /** + * The ID generator to use when creating new segments. + * @type {IdGenerator} + */ this.idGenerator = idGenerator; + + /** + * A callback function to call when there is a loop. + * @type {Function} + */ this.notifyLooped = onLooped; + + /** + * The root fork context for this state. + * @type {ForkContext} + */ this.forkContext = ForkContext.newRoot(idGenerator); + + /** + * Context for logical expressions, conditional expressions, `if` statements, + * and loops. + * @type {ChoiceContext} + */ this.choiceContext = null; + + /** + * Context for `switch` statements. + * @type {SwitchContext} + */ this.switchContext = null; + + /** + * Context for `try` statements. + * @type {TryContext} + */ this.tryContext = null; + + /** + * Context for loop statements. + * @type {LoopContext} + */ this.loopContext = null; + + /** + * Context for `break` statements. + * @type {BreakContext} + */ this.breakContext = null; + + /** + * Context for `ChainExpression` nodes. + * @type {ChainContext} + */ this.chainContext = null; + /** + * An array that tracks the current segments in the state. The array + * starts empty and segments are added with each `onCodePathSegmentStart` + * event and removed with each `onCodePathSegmentEnd` event. Effectively, + * this is tracking the code path segment traversal as the state is + * modified. + * @type {Array} + */ this.currentSegments = []; + + /** + * Tracks the starting segment for this path. This value never changes. + * @type {CodePathSegment} + */ this.initialSegment = this.forkContext.head[0]; - // returnedSegments and thrownSegments push elements into finalSegments also. - const final = this.finalSegments = []; - const returned = this.returnedForkContext = []; - const thrown = this.thrownForkContext = []; + /** + * The final segments of the code path which are either `return` or `throw`. + * This is a union of the segments in `returnedForkContext` and `thrownForkContext`. + * @type {Array} + */ + this.finalSegments = []; + + /** + * The final segments of the code path which are `return`. These + * segments are also contained in `finalSegments`. + * @type {Array} + */ + this.returnedForkContext = []; + + /** + * The final segments of the code path which are `throw`. These + * segments are also contained in `finalSegments`. + * @type {Array} + */ + this.thrownForkContext = []; + + /* + * We add an `add` method so that these look more like fork contexts and + * can be used interchangeably when a fork context is needed to add more + * segments to a path. + * + * Ultimately, we want anything added to `returned` or `thrown` to also + * be added to `final`. We only add reachable and used segments to these + * arrays. + */ + const final = this.finalSegments; + const returned = this.returnedForkContext; + const thrown = this.thrownForkContext; returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final); thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final); } /** - * The head segments. + * A passthrough property exposing the current pointer as part of the API. * @type {CodePathSegment[]} */ get headSegments() { @@ -341,20 +1085,12 @@ class CodePathState { * If the new context is LogicalExpression's or AssignmentExpression's, this is `"&&"` or `"||"` or `"??"`. * If it's IfStatement's or ConditionalExpression's, this is `"test"`. * Otherwise, this is `"loop"`. - * @param {boolean} isForkingAsResult A flag that shows that goes different - * paths between `true` and `false`. + * @param {boolean} isForkingAsResult Indicates if the result of the choice + * creates a fork. * @returns {void} */ pushChoiceContext(kind, isForkingAsResult) { - this.choiceContext = { - upper: this.choiceContext, - kind, - isForkingAsResult, - trueForkContext: ForkContext.newEmpty(this.forkContext), - falseForkContext: ForkContext.newEmpty(this.forkContext), - qqForkContext: ForkContext.newEmpty(this.forkContext), - processed: false - }; + this.choiceContext = new ChoiceContext(this.choiceContext, kind, isForkingAsResult, this.forkContext); } /** @@ -376,9 +1112,9 @@ class CodePathState { case "??": /* - * If any result were not transferred from child contexts, - * this sets the head segments to both cases. - * The head segments are the path of the right-hand operand. + * The `headSegments` are the path of the right-hand operand. + * If we haven't previously added segments from child contexts, + * then we add these segments to all possible forks. */ if (!context.processed) { context.trueForkContext.add(headSegments); @@ -387,8 +1123,10 @@ class CodePathState { } /* - * Transfers results to upper context if this context is in - * test chunk. + * If this context is the left (test) expression for another choice + * context, such as `a || b` in the expression `a || b || c`, + * then we take the segments for this context and move them up + * to the parent context. */ if (context.isForkingAsResult) { const parentContext = this.choiceContext; @@ -398,6 +1136,7 @@ class CodePathState { parentContext.qqForkContext.addAll(context.qqForkContext); parentContext.processed = true; + // Exit early so we don't collapse all paths into one. return context; } @@ -428,8 +1167,8 @@ class CodePathState { case "loop": /* - * Loops are addressed in popLoopContext(). - * This is called from popLoopContext(). + * Loops are addressed in `popLoopContext()` so just return + * the context without modification. */ return context; @@ -438,11 +1177,13 @@ class CodePathState { throw new Error("unreachable"); } - // Merges all paths. - const prevForkContext = context.trueForkContext; + /* + * Merge the true path with the false path to create a single path. + */ + const combinedForkContext = context.trueForkContext; - prevForkContext.addAll(context.falseForkContext); - forkContext.replaceHead(prevForkContext.makeNext(0, -1)); + combinedForkContext.addAll(context.falseForkContext); + forkContext.replaceHead(combinedForkContext.makeNext(0, -1)); return context; } @@ -562,22 +1303,20 @@ class CodePathState { //-------------------------------------------------------------------------- /** - * Push a new `ChainExpression` context to the stack. - * This method is called on entering to each `ChainExpression` node. - * This context is used to count forking in the optional chain then merge them on the exiting from the `ChainExpression` node. + * Pushes a new `ChainExpression` context to the stack. This method is + * called when entering a `ChainExpression` node. A chain context is used to + * count forking in the optional chain then merge them on the exiting from the + * `ChainExpression` node. * @returns {void} */ pushChainContext() { - this.chainContext = { - upper: this.chainContext, - countChoiceContexts: 0 - }; + this.chainContext = new ChainContext(this.chainContext); } /** - * Pop a `ChainExpression` context from the stack. - * This method is called on exiting from each `ChainExpression` node. - * This merges all forks of the last optional chaining. + * Pop a `ChainExpression` context from the stack. This method is called on + * exiting from each `ChainExpression` node. This merges all forks of the + * last optional chaining. * @returns {void} */ popChainContext() { @@ -586,7 +1325,7 @@ class CodePathState { this.chainContext = context.upper; // pop all choice contexts of this. - for (let i = context.countChoiceContexts; i > 0; --i) { + for (let i = context.choiceContextCount; i > 0; --i) { this.popChoiceContext(); } } @@ -599,7 +1338,7 @@ class CodePathState { */ makeOptionalNode() { if (this.chainContext) { - this.chainContext.countChoiceContexts += 1; + this.chainContext.choiceContextCount += 1; this.pushChoiceContext("??", false); } } @@ -627,16 +1366,7 @@ class CodePathState { * @returns {void} */ pushSwitchContext(hasCase, label) { - this.switchContext = { - upper: this.switchContext, - hasCase, - defaultSegments: null, - defaultBodySegments: null, - foundDefault: false, - lastIsDefault: false, - countForks: 0 - }; - + this.switchContext = new SwitchContext(this.switchContext, hasCase); this.pushBreakContext(true, label); } @@ -657,7 +1387,7 @@ class CodePathState { const forkContext = this.forkContext; const brokenForkContext = this.popBreakContext().brokenForkContext; - if (context.countForks === 0) { + if (context.forkCount === 0) { /* * When there is only one `default` chunk and there is one or more @@ -684,47 +1414,54 @@ class CodePathState { brokenForkContext.add(lastSegments); /* - * A path which is failed in all case test should be connected to path - * of `default` chunk. + * Any value that doesn't match a `case` test should flow to the default + * case. That happens normally when the default case is last in the `switch`, + * but if it's not, we need to rewire some of the paths to be correct. */ if (!context.lastIsDefault) { if (context.defaultBodySegments) { /* - * Remove a link from `default` label to its chunk. - * It's false route. + * There is a non-empty default case, so remove the path from the `default` + * label to its body for an accurate representation. + */ + disconnectSegments(context.defaultSegments, context.defaultBodySegments); + + /* + * Connect the path from the last non-default case to the body of the + * default case. */ - removeConnection(context.defaultSegments, context.defaultBodySegments); makeLooped(this, lastCaseSegments, context.defaultBodySegments); + } else { /* - * It handles the last case body as broken if `default` chunk - * does not exist. + * There is no default case, so we treat this as if the last case + * had a `break` in it. */ brokenForkContext.add(lastCaseSegments); } } - // Pops the segment context stack until the entry segment. - for (let i = 0; i < context.countForks; ++i) { + // Traverse up to the original fork context for the `switch` statement + for (let i = 0; i < context.forkCount; ++i) { this.forkContext = this.forkContext.upper; } /* - * Creates a path from all brokenForkContext paths. - * This is a path after switch statement. + * Creates a path from all `brokenForkContext` paths. + * This is a path after `switch` statement. */ this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); } /** * Makes a code path segment for a `SwitchCase` node. - * @param {boolean} isEmpty `true` if the body is empty. - * @param {boolean} isDefault `true` if the body is the default case. + * @param {boolean} isCaseBodyEmpty `true` if the body is empty. + * @param {boolean} isDefaultCase `true` if the body is the default case. * @returns {void} */ - makeSwitchCaseBody(isEmpty, isDefault) { + makeSwitchCaseBody(isCaseBodyEmpty, isDefaultCase) { const context = this.switchContext; if (!context.hasCase) { @@ -734,7 +1471,7 @@ class CodePathState { /* * Merge forks. * The parent fork context has two segments. - * Those are from the current case and the body of the previous case. + * Those are from the current `case` and the body of the previous case. */ const parentForkContext = this.forkContext; const forkContext = this.pushForkContext(); @@ -742,26 +1479,53 @@ class CodePathState { forkContext.add(parentForkContext.makeNext(0, -1)); /* - * Save `default` chunk info. - * If the `default` label is not at the last, we must make a path from - * the last `case` to the `default` chunk. + * Add information about the default case. + * + * The purpose of this is to identify the starting segments for the + * default case to make sure there is a path there. */ - if (isDefault) { + if (isDefaultCase) { + + /* + * This is the default case in the `switch`. + * + * We first save the current pointer as `defaultSegments` to point + * to the `default` keyword. + */ context.defaultSegments = parentForkContext.head; - if (isEmpty) { - context.foundDefault = true; + + /* + * If the body of the case is empty then we just set + * `foundEmptyDefault` to true; otherwise, we save a reference + * to the current pointer as `defaultBodySegments`. + */ + if (isCaseBodyEmpty) { + context.foundEmptyDefault = true; } else { context.defaultBodySegments = forkContext.head; } + } else { - if (!isEmpty && context.foundDefault) { - context.foundDefault = false; + + /* + * This is not the default case in the `switch`. + * + * If it's not empty and there is already an empty default case found, + * that means the default case actually comes before this case, + * and that it will fall through to this case. So, we can now + * ignore the previous default case (reset `foundEmptyDefault` to false) + * and set `defaultBodySegments` to the current segments because this is + * effectively the new default case. + */ + if (!isCaseBodyEmpty && context.foundEmptyDefault) { + context.foundEmptyDefault = false; context.defaultBodySegments = forkContext.head; } } - context.lastIsDefault = isDefault; - context.countForks += 1; + // keep track if the default case ends up last + context.lastIsDefault = isDefaultCase; + context.forkCount += 1; } //-------------------------------------------------------------------------- @@ -775,19 +1539,7 @@ class CodePathState { * @returns {void} */ pushTryContext(hasFinalizer) { - this.tryContext = { - upper: this.tryContext, - position: "try", - hasFinalizer, - - returnedForkContext: hasFinalizer - ? ForkContext.newEmpty(this.forkContext) - : null, - - thrownForkContext: ForkContext.newEmpty(this.forkContext), - lastOfTryIsReachable: false, - lastOfCatchIsReachable: false - }; + this.tryContext = new TryContext(this.tryContext, hasFinalizer, this.forkContext); } /** @@ -799,25 +1551,35 @@ class CodePathState { this.tryContext = context.upper; + /* + * If we're inside the `catch` block, that means there is no `finally`, + * so we can process the `try` and `catch` blocks the simple way and + * merge their two paths. + */ if (context.position === "catch") { - - // Merges two paths from the `try` block and `catch` block merely. this.popForkContext(); return; } /* - * The following process is executed only when there is the `finally` + * The following process is executed only when there is a `finally` * block. */ - const returned = context.returnedForkContext; - const thrown = context.thrownForkContext; + const originalReturnedForkContext = context.returnedForkContext; + const originalThrownForkContext = context.thrownForkContext; - if (returned.empty && thrown.empty) { + // no `return` or `throw` in `try` or `catch` so there's nothing left to do + if (originalReturnedForkContext.empty && originalThrownForkContext.empty) { return; } + /* + * The following process is executed only when there is a `finally` + * block and there was a `return` or `throw` in the `try` or `catch` + * blocks. + */ + // Separate head to normal paths and leaving paths. const headSegments = this.forkContext.head; @@ -826,10 +1588,10 @@ class CodePathState { const leavingSegments = headSegments.slice(headSegments.length / 2 | 0); // Forwards the leaving path to upper contexts. - if (!returned.empty) { + if (!originalReturnedForkContext.empty) { getReturnContext(this).returnedForkContext.add(leavingSegments); } - if (!thrown.empty) { + if (!originalThrownForkContext.empty) { getThrowContext(this).thrownForkContext.add(leavingSegments); } @@ -852,16 +1614,20 @@ class CodePathState { makeCatchBlock() { const context = this.tryContext; const forkContext = this.forkContext; - const thrown = context.thrownForkContext; + const originalThrownForkContext = context.thrownForkContext; - // Update state. + /* + * We are now in a catch block so we need to update the context + * with that information. This includes creating a new fork + * context in case we encounter any `throw` statements here. + */ context.position = "catch"; context.thrownForkContext = ForkContext.newEmpty(forkContext); context.lastOfTryIsReachable = forkContext.reachable; - // Merge thrown paths. - thrown.add(forkContext.head); - const thrownSegments = thrown.makeNext(0, -1); + // Merge the thrown paths from the `try` and `catch` blocks + originalThrownForkContext.add(forkContext.head); + const thrownSegments = originalThrownForkContext.makeNext(0, -1); // Fork to a bypass and the merged thrown path. this.pushForkContext(); @@ -880,8 +1646,8 @@ class CodePathState { makeFinallyBlock() { const context = this.tryContext; let forkContext = this.forkContext; - const returned = context.returnedForkContext; - const thrown = context.thrownForkContext; + const originalReturnedForkContext = context.returnedForkContext; + const originalThrownForContext = context.thrownForkContext; const headOfLeavingSegments = forkContext.head; // Update state. @@ -895,9 +1661,15 @@ class CodePathState { } else { context.lastOfTryIsReachable = forkContext.reachable; } + + context.position = "finally"; - if (returned.empty && thrown.empty) { + /* + * If there was no `return` or `throw` in either the `try` or `catch` + * blocks, then there's no further code paths to create for `finally`. + */ + if (originalReturnedForkContext.empty && originalThrownForContext.empty) { // This path does not leave. return; @@ -905,18 +1677,18 @@ class CodePathState { /* * Create a parallel segment from merging returned and thrown. - * This segment will leave at the end of this finally block. + * This segment will leave at the end of this `finally` block. */ const segments = forkContext.makeNext(-1, -1); for (let i = 0; i < forkContext.count; ++i) { const prevSegsOfLeavingSegment = [headOfLeavingSegments[i]]; - for (let j = 0; j < returned.segmentsList.length; ++j) { - prevSegsOfLeavingSegment.push(returned.segmentsList[j][i]); + for (let j = 0; j < originalReturnedForkContext.segmentsList.length; ++j) { + prevSegsOfLeavingSegment.push(originalReturnedForkContext.segmentsList[j][i]); } - for (let j = 0; j < thrown.segmentsList.length; ++j) { - prevSegsOfLeavingSegment.push(thrown.segmentsList[j][i]); + for (let j = 0; j < originalThrownForContext.segmentsList.length; ++j) { + prevSegsOfLeavingSegment.push(originalThrownForContext.segmentsList[j][i]); } segments.push( @@ -971,63 +1743,32 @@ class CodePathState { */ pushLoopContext(type, label) { const forkContext = this.forkContext; + + // All loops need a path to account for `break` statements const breakContext = this.pushBreakContext(true, label); switch (type) { case "WhileStatement": this.pushChoiceContext("loop", false); - this.loopContext = { - upper: this.loopContext, - type, - label, - test: void 0, - continueDestSegments: null, - brokenForkContext: breakContext.brokenForkContext - }; + this.loopContext = new WhileLoopContext(this.loopContext, label, breakContext); break; case "DoWhileStatement": this.pushChoiceContext("loop", false); - this.loopContext = { - upper: this.loopContext, - type, - label, - test: void 0, - entrySegments: null, - continueForkContext: ForkContext.newEmpty(forkContext), - brokenForkContext: breakContext.brokenForkContext - }; + this.loopContext = new DoWhileLoopContext(this.loopContext, label, breakContext, forkContext); break; case "ForStatement": this.pushChoiceContext("loop", false); - this.loopContext = { - upper: this.loopContext, - type, - label, - test: void 0, - endOfInitSegments: null, - testSegments: null, - endOfTestSegments: null, - updateSegments: null, - endOfUpdateSegments: null, - continueDestSegments: null, - brokenForkContext: breakContext.brokenForkContext - }; + this.loopContext = new ForLoopContext(this.loopContext, label, breakContext); break; case "ForInStatement": + this.loopContext = new ForInLoopContext(this.loopContext, label, breakContext); + break; + case "ForOfStatement": - this.loopContext = { - upper: this.loopContext, - type, - label, - prevSegments: null, - leftSegments: null, - endOfLeftSegments: null, - continueDestSegments: null, - brokenForkContext: breakContext.brokenForkContext - }; + this.loopContext = new ForOfLoopContext(this.loopContext, label, breakContext); break; /* c8 ignore next */ @@ -1054,6 +1795,11 @@ class CodePathState { case "WhileStatement": case "ForStatement": this.popChoiceContext(); + + /* + * Creates the path from the end of the loop body up to the + * location where `continue` would jump to. + */ makeLooped( this, forkContext.head, @@ -1068,11 +1814,21 @@ class CodePathState { choiceContext.trueForkContext.add(forkContext.head); choiceContext.falseForkContext.add(forkContext.head); } + + /* + * If this isn't a hardcoded `true` condition, then `break` + * should continue down the path as if the condition evaluated + * to false. + */ if (context.test !== true) { brokenForkContext.addAll(choiceContext.falseForkContext); } - // `true` paths go to looping. + /* + * When the condition is true, the loop continues back to the top, + * so create a path from each possible true condition back to the + * top of the loop. + */ const segmentsList = choiceContext.trueForkContext.segmentsList; for (let i = 0; i < segmentsList.length; ++i) { @@ -1088,6 +1844,11 @@ class CodePathState { case "ForInStatement": case "ForOfStatement": brokenForkContext.add(forkContext.head); + + /* + * Creates the path from the end of the loop body up to the + * left expression (left of `in` or `of`) of the loop. + */ makeLooped( this, forkContext.head, @@ -1100,7 +1861,14 @@ class CodePathState { throw new Error("unreachable"); } - // Go next. + /* + * If there wasn't a `break` statement in the loop, then we're at + * the end of the loop's path, so we make an unreachable segment + * to mark that. + * + * If there was a `break` statement, then we continue on into the + * `brokenForkContext`. + */ if (brokenForkContext.empty) { forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); } else { @@ -1138,7 +1906,11 @@ class CodePathState { choiceContext.falseForkContext.add(forkContext.head); } - // Update state. + /* + * If this isn't a hardcoded `true` condition, then `break` + * should continue down the path as if the condition evaluated + * to false. + */ if (context.test !== true) { context.brokenForkContext.addAll(choiceContext.falseForkContext); } @@ -1170,7 +1942,11 @@ class CodePathState { context.test = test; - // Creates paths of `continue` statements. + /* + * If there is a `continue` statement in the loop then `continueForkContext` + * won't be empty. We wire up the path from `continue` to the loop + * test condition and then continue the traversal in the root fork context. + */ if (!context.continueForkContext.empty) { context.continueForkContext.add(forkContext.head); const testSegments = context.continueForkContext.makeNext(0, -1); @@ -1190,7 +1966,14 @@ class CodePathState { const endOfInitSegments = forkContext.head; const testSegments = forkContext.makeNext(-1, -1); - // Update state. + /* + * Update the state. + * + * The `continueDestSegments` are set to `testSegments` because we + * don't yet know if there is an update expression in this loop. So, + * from what we already know at this point, a `continue` statement + * will jump back to the test expression. + */ context.test = test; context.endOfInitSegments = endOfInitSegments; context.continueDestSegments = context.testSegments = testSegments; @@ -1217,7 +2000,14 @@ class CodePathState { context.endOfInitSegments = forkContext.head; } - // Update state. + /* + * Update the state. + * + * The `continueDestSegments` are now set to `updateSegments` because we + * know there is an update expression in this loop. So, a `continue` statement + * in the loop will jump to the update expression first, and then to any + * test expression the loop might have. + */ const updateSegments = forkContext.makeDisconnected(-1, -1); context.continueDestSegments = context.updateSegments = updateSegments; @@ -1233,11 +2023,30 @@ class CodePathState { const choiceContext = this.choiceContext; const forkContext = this.forkContext; - // Update state. + /* + * Determine what to do based on which part of the `for` loop are present. + * 1. If there is an update expression, then `updateSegments` is not null and + * we need to assign `endOfUpdateSegments`, and if there is a test + * expression, we then need to create the looped path to get back to + * the test condition. + * 2. If there is no update expression but there is a test expression, + * then we only need to update the test segment information. + * 3. If there is no update expression and no test expression, then we + * just save `endOfInitSegments`. + */ if (context.updateSegments) { context.endOfUpdateSegments = forkContext.head; - // `update` -> `test` + /* + * In a `for` loop that has both an update expression and a test + * condition, execution flows from the test expression into the + * loop body, to the update expression, and then back to the test + * expression to determine if the loop should continue. + * + * To account for that, we need to make a path from the end of the + * update expression to the start of the test expression. This is + * effectively what creates the loop in the code path. + */ if (context.testSegments) { makeLooped( this, @@ -1257,12 +2066,18 @@ class CodePathState { let bodySegments = context.endOfTestSegments; + /* + * If there is a test condition, then there `endOfTestSegments` is also + * the start of the loop body. If there isn't a test condition then + * `bodySegments` will be null and we need to look elsewhere to find + * the start of the body. + * + * The body starts at the end of the init expression and ends at the end + * of the update expression, so we use those locations to determine the + * body segments. + */ if (!bodySegments) { - /* - * If there is not the `test` part, the `body` path comes from the - * `init` part and the `update` part. - */ const prevForkContext = ForkContext.newEmpty(forkContext); prevForkContext.add(context.endOfInitSegments); @@ -1272,7 +2087,16 @@ class CodePathState { bodySegments = prevForkContext.makeNext(0, -1); } + + /* + * If there was no test condition and no update expression, then + * `continueDestSegments` will be null. In that case, a + * `continue` should skip directly to the body of the loop. + * Otherwise, we want to keep the current `continueDestSegments`. + */ context.continueDestSegments = context.continueDestSegments || bodySegments; + + // move pointer to the body forkContext.replaceHead(bodySegments); } @@ -1336,19 +2160,15 @@ class CodePathState { //-------------------------------------------------------------------------- /** - * Creates new context for BreakStatement. - * @param {boolean} breakable The flag to indicate it can break by - * an unlabeled BreakStatement. - * @param {string|null} label The label of this context. - * @returns {Object} The new context. + * Creates new context in which a `break` statement can be used. This occurs inside of a loop, + * labeled statement, or switch statement. + * @param {boolean} breakable Indicates if we are inside a statement where + * `break` without a label will exit the statement. + * @param {string|null} label The label associated with the statement. + * @returns {BreakContext} The new context. */ pushBreakContext(breakable, label) { - this.breakContext = { - upper: this.breakContext, - breakable, - label, - brokenForkContext: ForkContext.newEmpty(this.forkContext) - }; + this.breakContext = new BreakContext(this.breakContext, breakable, label, this.forkContext); return this.breakContext; } @@ -1380,7 +2200,7 @@ class CodePathState { * * It registers the head segment to a context of `break`. * It makes new unreachable segment, then it set the head with the segment. - * @param {string} label A label of the break statement. + * @param {string|null} label A label of the break statement. * @returns {void} */ makeBreak(label) { @@ -1406,7 +2226,7 @@ class CodePathState { * * It makes a looping path. * It makes new unreachable segment, then it set the head with the segment. - * @param {string} label A label of the continue statement. + * @param {string|null} label A label of the continue statement. * @returns {void} */ makeContinue(label) { @@ -1422,7 +2242,7 @@ class CodePathState { if (context.continueDestSegments) { makeLooped(this, forkContext.head, context.continueDestSegments); - // If the context is a for-in/of loop, this effects a break also. + // If the context is a for-in/of loop, this affects a break also. if (context.type === "ForInStatement" || context.type === "ForOfStatement" ) { From 1866da5e1d931787256ecb825a803cac5835b71c Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Sun, 1 Oct 2023 08:06:13 +0000 Subject: [PATCH 192/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b8296193241..9ea4ca8cd579 100644 --- a/README.md +++ b/README.md @@ -288,7 +288,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

-

Sentry Liftoff Siemens American Express

Bronze Sponsors

+

Liftoff Siemens American Express

Bronze Sponsors

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8 Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

From 0a9c43339a4adef24ef83034d0b078dd279cc977 Mon Sep 17 00:00:00 2001 From: Domantas Petrauskas Date: Sun, 1 Oct 2023 19:00:09 +0300 Subject: [PATCH 193/248] feat: Add `--no-warn-ignored` CLI option for flat config (#17569) * Add warnIgnored CLI option for flat config * Update ignore result warning message about its suppression * Add test for lintText warnIgnored overriding constructor option * Respect constructor warnIgnored when linting via stdin * Move warnIgnored help text under Miscellaneous * Use void 0 instead of undefined * Add test cases for eslint.lintFiles with warnIgnored:false * Add assertion for error message when warnIgnored is not the right type * Change file ignore warning wording * Simplify error condition * Fix accidentally commented out test line * Add documentation for --no-warn-ignored CLI flag * Clarify warnIgnored descriptions --- docs/src/use/command-line-interface.md | 13 ++++ docs/src/use/configure/ignore.md | 2 +- lib/cli.js | 8 ++- lib/eslint/eslint-helpers.js | 11 +++- lib/eslint/flat-eslint.js | 21 +++++-- lib/options.js | 13 ++++ tests/lib/cli.js | 26 ++++++++ tests/lib/eslint/flat-eslint.js | 87 ++++++++++++++++++++++---- tests/lib/options.js | 14 +++++ 9 files changed, 170 insertions(+), 25 deletions(-) diff --git a/docs/src/use/command-line-interface.md b/docs/src/use/command-line-interface.md index d88e35cfa04c..da4faf70ebae 100644 --- a/docs/src/use/command-line-interface.md +++ b/docs/src/use/command-line-interface.md @@ -110,6 +110,7 @@ Miscellaneous: --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* --debug Output debugging information -h, --help Show help -v, --version Output the version number @@ -703,6 +704,18 @@ This option causes ESLint to exit with exit code 2 if one or more fatal parsing npx eslint --exit-on-fatal-error file.js ``` +#### `--no-warn-ignored` + +**Flat Config Mode Only.** This option suppresses both `File ignored by default` and `File ignored because of a matching ignore pattern` warnings when an ignored filename is passed explicitly. It is useful when paired with `--max-warnings 0` as it will prevent exit code 1 due to the aforementioned warning. + +* **Argument Type**: No argument. + +##### `--no-warn-ignored` example + +```shell +npx eslint --no-warn-ignored --max-warnings 0 ignored-file.js +``` + #### `--debug` This option outputs debugging information to the console. Add this flag to an ESLint command line invocation in order to get extra debugging information while the command runs. diff --git a/docs/src/use/configure/ignore.md b/docs/src/use/configure/ignore.md index ffc23428ee02..16f1bfbcdc92 100644 --- a/docs/src/use/configure/ignore.md +++ b/docs/src/use/configure/ignore.md @@ -149,7 +149,7 @@ You'll see this warning: ```text foo.js - 0:0 warning File ignored because of a matching ignore pattern. Use "--no-ignore" to override. + 0:0 warning File ignored because of a matching ignore pattern. Use "--no-ignore" to disable file ignore settings or use "--no-warn-ignored" to suppress this warning. โœ– 1 problem (0 errors, 1 warning) ``` diff --git a/lib/cli.js b/lib/cli.js index a14930e9b0f2..807d28a0d1bc 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -91,7 +91,8 @@ async function translateOptions({ reportUnusedDisableDirectives, resolvePluginsRelativeTo, rule, - rulesdir + rulesdir, + warnIgnored }, configType) { let overrideConfig, overrideConfigFile; @@ -182,6 +183,7 @@ async function translateOptions({ if (configType === "flat") { options.ignorePatterns = ignorePattern; + options.warnIgnored = warnIgnored; } else { options.resolvePluginsRelativeTo = resolvePluginsRelativeTo; options.rulePaths = rulesdir; @@ -385,7 +387,9 @@ const cli = { if (useStdin) { results = await engine.lintText(text, { filePath: options.stdinFilename, - warnIgnored: true + + // flatConfig respects CLI flag and constructor warnIgnored, eslintrc forces true for backwards compatibility + warnIgnored: usingFlatConfig ? void 0 : true }); } else { results = await engine.lintFiles(files); diff --git a/lib/eslint/eslint-helpers.js b/lib/eslint/eslint-helpers.js index e25b10e8bc4e..72828363c3da 100644 --- a/lib/eslint/eslint-helpers.js +++ b/lib/eslint/eslint-helpers.js @@ -594,9 +594,9 @@ function createIgnoreResult(filePath, baseDir) { const isInNodeModules = baseDir && path.dirname(path.relative(baseDir, filePath)).split(path.sep).includes("node_modules"); if (isInNodeModules) { - message = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override."; + message = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; } else { - message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."; + message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; } return { @@ -676,6 +676,7 @@ function processOptions({ overrideConfigFile = null, plugins = {}, reportUnusedDisableDirectives = null, // โ† should be null by default because if it's a string then it overrides the 'reportUnusedDisableDirectives' setting in config files. And we cannot use `overrideConfig.reportUnusedDisableDirectives` instead because we cannot configure the `error` severity with that. + warnIgnored = true, ...unknownOptions }) { const errors = []; @@ -781,6 +782,9 @@ function processOptions({ ) { errors.push("'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null."); } + if (typeof warnIgnored !== "boolean") { + errors.push("'warnIgnored' must be a boolean."); + } if (errors.length > 0) { throw new ESLintInvalidOptionsError(errors); } @@ -802,7 +806,8 @@ function processOptions({ globInputPaths, ignore, ignorePatterns, - reportUnusedDisableDirectives + reportUnusedDisableDirectives, + warnIgnored }; } diff --git a/lib/eslint/flat-eslint.js b/lib/eslint/flat-eslint.js index 4ef386113616..306c80de1d65 100644 --- a/lib/eslint/flat-eslint.js +++ b/lib/eslint/flat-eslint.js @@ -84,6 +84,7 @@ const LintResultCache = require("../cli-engine/lint-result-cache"); * when a string. * @property {Record} [plugins] An array of plugin implementations. * @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives. + * @property {boolean} warnIgnored Show warnings when the file list includes ignored files */ //------------------------------------------------------------------------------ @@ -749,7 +750,8 @@ class FlatESLint { fixTypes, reportUnusedDisableDirectives, globInputPaths, - errorOnUnmatchedPattern + errorOnUnmatchedPattern, + warnIgnored } = eslintOptions; const startTime = Date.now(); const fixTypesSet = fixTypes ? new Set(fixTypes) : null; @@ -795,7 +797,11 @@ class FlatESLint { * pattern, then notify the user. */ if (ignored) { - return createIgnoreResult(filePath, cwd); + if (warnIgnored) { + return createIgnoreResult(filePath, cwd); + } + + return void 0; } const config = configs.getConfig(filePath); @@ -908,7 +914,7 @@ class FlatESLint { const { filePath, - warnIgnored = false, + warnIgnored, ...unknownOptions } = options || {}; @@ -922,7 +928,7 @@ class FlatESLint { throw new Error("'options.filePath' must be a non-empty string or undefined"); } - if (typeof warnIgnored !== "boolean") { + if (typeof warnIgnored !== "boolean" && typeof warnIgnored !== "undefined") { throw new Error("'options.warnIgnored' must be a boolean or undefined"); } @@ -937,7 +943,8 @@ class FlatESLint { allowInlineConfig, cwd, fix, - reportUnusedDisableDirectives + reportUnusedDisableDirectives, + warnIgnored: constructorWarnIgnored } = eslintOptions; const results = []; const startTime = Date.now(); @@ -945,7 +952,9 @@ class FlatESLint { // Clear the last used config arrays. if (resolvedFilename && await this.isPathIgnored(resolvedFilename)) { - if (warnIgnored) { + const shouldWarnIgnored = typeof warnIgnored === "boolean" ? warnIgnored : constructorWarnIgnored; + + if (shouldWarnIgnored) { results.push(createIgnoreResult(resolvedFilename, cwd)); } } else { diff --git a/lib/options.js b/lib/options.js index 2bc4018afb5d..ae9a5d5552a2 100644 --- a/lib/options.js +++ b/lib/options.js @@ -55,6 +55,7 @@ const optionator = require("optionator"); * @property {string} [stdinFilename] Specify filename to process STDIN as * @property {boolean} quiet Report errors only * @property {boolean} [version] Output the version number + * @property {boolean} warnIgnored Show warnings when the file list includes ignored files * @property {string[]} _ Positional filenames or patterns */ @@ -139,6 +140,17 @@ module.exports = function(usingFlatConfig) { }; } + let warnIgnoredFlag; + + if (usingFlatConfig) { + warnIgnoredFlag = { + option: "warn-ignored", + type: "Boolean", + default: "true", + description: "Suppress warnings when the file list includes ignored files" + }; + } + return optionator({ prepend: "eslint [options] file.js [file.js] [dir]", defaults: { @@ -349,6 +361,7 @@ module.exports = function(usingFlatConfig) { default: "false", description: "Exit with exit code 2 in case of fatal error" }, + warnIgnoredFlag, { option: "debug", type: "Boolean", diff --git a/tests/lib/cli.js b/tests/lib/cli.js index 15556e5cfd1e..ad0b8ad23272 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -801,6 +801,32 @@ describe("cli", () => { 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 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", () => { diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index f67e62e5c63c..d20dce0c3195 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -211,7 +211,8 @@ describe("FlatESLint", () => { overrideConfig: "", overrideConfigFile: "", plugins: "", - reportUnusedDisableDirectives: "" + reportUnusedDisableDirectives: "", + warnIgnored: "" }), new RegExp(escapeStringRegExp([ "Invalid Options:", @@ -229,7 +230,8 @@ describe("FlatESLint", () => { "- 'overrideConfig' must be an object or null.", "- 'overrideConfigFile' must be a non-empty string, null, or true.", "- 'plugins' must be an object or null.", - "- 'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null." + "- 'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null.", + "- 'warnIgnored' must be a boolean." ].join("\n")), "u") ); }); @@ -369,7 +371,31 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].filePath, getFixturePath("passing.js")); assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."); + assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); + assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return a warning when given a filename by --stdin-filename in excluded files list if constructor warnIgnored is false, but lintText warnIgnored is true", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath(".."), + overrideConfigFile: "fixtures/eslint.config_with_ignores.js", + warnIgnored: false + }); + + const options = { filePath: "fixtures/passing.js", warnIgnored: true }; + const results = await eslint.lintText("var bar = foo;", options); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("passing.js")); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); assert.strictEqual(results[0].messages[0].output, void 0); assert.strictEqual(results[0].errorCount, 0); assert.strictEqual(results[0].warningCount, 1); @@ -397,18 +423,31 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 0); }); - it("should suppress excluded file warnings by default", async () => { + it("should not return a warning when given a filename by --stdin-filename in excluded files list if constructor warnIgnored is false", async () => { eslint = new FlatESLint({ cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config_with_ignores.js" + overrideConfigFile: "fixtures/eslint.config_with_ignores.js", + warnIgnored: false }); const options = { filePath: "fixtures/passing.js" }; const results = await eslint.lintText("var bar = foo;", options); - // should not report anything because there are no errors + // should not report anything because the warning is suppressed assert.strictEqual(results.length, 0); }); + it("should show excluded file warnings by default", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath(".."), + overrideConfigFile: "fixtures/eslint.config_with_ignores.js" + }); + const options = { filePath: "fixtures/passing.js" }; + const results = await eslint.lintText("var bar = foo;", options); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); + }); + it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", async () => { eslint = new FlatESLint({ cwd: getFixturePath(".."), @@ -685,7 +724,7 @@ describe("FlatESLint", () => { ignore: false }); const results = await eslint.lintText("var bar = foo;", { filePath: "node_modules/passing.js", warnIgnored: true }); - const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override."; + const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; assert.strictEqual(results.length, 1); assert.strictEqual(results[0].filePath, getFixturePath("node_modules/passing.js")); @@ -1311,7 +1350,7 @@ describe("FlatESLint", () => { cwd: getFixturePath("cli-engine") }); const results = await eslint.lintFiles(["node_modules/foo.js"]); - const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override."; + const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; assert.strictEqual(results.length, 1); assert.strictEqual(results[0].errorCount, 0); @@ -1329,7 +1368,7 @@ describe("FlatESLint", () => { cwd: getFixturePath("cli-engine") }); const results = await eslint.lintFiles(["nested_node_modules/subdir/node_modules/text.js"]); - const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override."; + const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; assert.strictEqual(results.length, 1); assert.strictEqual(results[0].errorCount, 0); @@ -1348,7 +1387,7 @@ describe("FlatESLint", () => { ignorePatterns: ["*.js"] }); const results = await eslint.lintFiles(["node_modules_cleaner.js"]); - const expectedMsg = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."; + const expectedMsg = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; assert.strictEqual(results.length, 1); assert.strictEqual(results[0].errorCount, 0); @@ -1361,6 +1400,16 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].suppressedMessages.length, 0); }); + it("should suppress the warning when a file in the node_modules folder passed explicitly and warnIgnored is false", async () => { + eslint = new FlatESLint({ + cwd: getFixturePath("cli-engine"), + warnIgnored: false + }); + const results = await eslint.lintFiles(["node_modules/foo.js"]); + + assert.strictEqual(results.length, 0); + }); + it("should report on globs with explicit inclusion of dotfiles", async () => { eslint = new FlatESLint({ cwd: getFixturePath("cli-engine"), @@ -1483,7 +1532,7 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].filePath, filePath); assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."); + assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); assert.strictEqual(results[0].errorCount, 0); assert.strictEqual(results[0].warningCount, 1); assert.strictEqual(results[0].fatalErrorCount, 0); @@ -1492,6 +1541,18 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].suppressedMessages.length, 0); }); + it("should suppress the warning when an explicitly given file is ignored and warnIgnored is false", async () => { + eslint = new FlatESLint({ + overrideConfigFile: "eslint.config_with_ignores.js", + cwd: getFixturePath(), + warnIgnored: false + }); + const filePath = getFixturePath("passing.js"); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 0); + }); + it("should return a warning about matching ignore patterns when an explicitly given dotfile is ignored", async () => { eslint = new FlatESLint({ overrideConfigFile: "eslint.config_with_ignores.js", @@ -1503,7 +1564,7 @@ describe("FlatESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].filePath, filePath); assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."); + assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); assert.strictEqual(results[0].errorCount, 0); assert.strictEqual(results[0].warningCount, 1); assert.strictEqual(results[0].fatalErrorCount, 0); @@ -5400,7 +5461,7 @@ describe("FlatESLint", () => { { ruleId: null, fatal: false, - message: "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to override.", + message: "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.", severity: 1, nodeType: null } diff --git a/tests/lib/options.js b/tests/lib/options.js index d8f795b78a22..b663e8623e36 100644 --- a/tests/lib/options.js +++ b/tests/lib/options.js @@ -415,4 +415,18 @@ describe("options", () => { }); }); + describe("--no-warn-ignored", () => { + it("should return false when --no-warn-ignored is passed", () => { + const currentOptions = flatOptions.parse("--no-warn-ignored"); + + assert.isFalse(currentOptions.warnIgnored); + }); + + it("should return true when --warn-ignored is passed", () => { + const currentOptions = flatOptions.parse("--warn-ignored"); + + assert.isTrue(currentOptions.warnIgnored); + }); + }); + }); From dbf831e31f8eea0bc94df96cd33255579324b66e Mon Sep 17 00:00:00 2001 From: Percy Ma Date: Mon, 2 Oct 2023 00:44:09 +0800 Subject: [PATCH 194/248] docs: use generated og image (#17601) * docs: use generated og image * fix: revert code for testing * Update docs/src/_includes/layouts/base.html Co-authored-by: Milos Djermanovic * chore: urlencode title and summary in og --------- Co-authored-by: Milos Djermanovic --- docs/src/_includes/layouts/base.html | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/src/_includes/layouts/base.html b/docs/src/_includes/layouts/base.html index 49bdf8522c89..86ea680c3cb0 100644 --- a/docs/src/_includes/layouts/base.html +++ b/docs/src/_includes/layouts/base.html @@ -13,11 +13,19 @@ {% set page_title = site[hook].title %} {% endif %} + {% set rule_meta = rules_meta[title] %} {% set page_title = site.shared.title_format | replace("PAGE_TITLE", page_title) %} - {% set cover_image = ["https://", site.hostname, "/icon-512.png" ] | join %} - {% set cover_image_alt = site.shared.eslint_logo_alt %} {% set page_desc = site.shared.description %} {% set relative_page_url = page.url | url | prettyURL %} + {% set cover_image = [ + "https://", site.hostname, "/og", + "?title=", title | urlencode, "&summary=", page_desc | urlencode, + "&is_rule=", rule_meta !== undefined, + "&recommended=", rule_meta.docs.recommended, + "&fixable=", rule_meta.fixable, + "&suggestions=", rule_meta.hasSuggestions + ] | join %} + {% set cover_image_alt = site.shared.eslint_logo_alt %} {% set page_url = ["https://", site.hostname, relative_page_url ] | join %} From 47d0b446964f44d70b9457ecc368e721e1dc7c11 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Mon, 2 Oct 2023 08:07:44 +0000 Subject: [PATCH 195/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ea4ca8cd579..7aa4019ed87a 100644 --- a/README.md +++ b/README.md @@ -289,7 +289,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Liftoff Siemens American Express

Bronze Sponsors

-

ThemeIsle Nx (by Nrwl) Anagram Solver Icons8 Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

+

ThemeIsle Anagram Solver Icons8 Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

## Technology Sponsors From 1aa26df9fbcfdf5b895743c6d2d3a216479544b1 Mon Sep 17 00:00:00 2001 From: George Ashiotis Date: Mon, 2 Oct 2023 17:16:21 +0300 Subject: [PATCH 196/248] docs: Add more examples for multiline-ternary (#17610) --- docs/src/rules/multiline-ternary.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/src/rules/multiline-ternary.md b/docs/src/rules/multiline-ternary.md index 7e4688d637ae..f828bc1cd40a 100644 --- a/docs/src/rules/multiline-ternary.md +++ b/docs/src/rules/multiline-ternary.md @@ -18,9 +18,14 @@ var foo = bar > baz ? value1 : value2; The above can be rewritten as the following to improve readability and more clearly delineate the operands: ```js + var foo = bar > baz ? value1 : value2; + +var foo = bar > baz + ? value1 + : value2; ``` ## Rule Details @@ -74,6 +79,12 @@ foo > bar ? value1 : value2) : value3; + +foo > bar + ? (baz > qux + ? value1 + : value2) + : value3; ``` ::: @@ -126,6 +137,12 @@ foo > bar && bar > baz ? value1 : value2; + +foo > bar + ? baz > qux + ? value1 + : value2 + : value3; ``` ::: From 2665552ba0057e8603f9fbece0fd236f189f5cf3 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 2 Oct 2023 16:58:36 +0200 Subject: [PATCH 197/248] test: fix flat config linter tests to use Linter in flat config mode (#17616) --- tests/lib/linter/linter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index 5b3d06826126..bf1f1487593a 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -15627,7 +15627,7 @@ var a = "test2"; const code = BROKEN_TEST_CODE; it("should report a violation with a useful parse error prefix", () => { - const messages = linter.verify(code); + const messages = linter.verify(code, {}); const suppressedMessages = linter.getSuppressedMessages(); assert.strictEqual(messages.length, 1); @@ -15648,7 +15648,7 @@ var a = "test2"; " x++;", "}" ]; - const messages = linter.verify(inValidCode.join("\n")); + const messages = linter.verify(inValidCode.join("\n"), {}); const suppressedMessages = linter.getSuppressedMessages(); assert.strictEqual(messages.length, 1); @@ -16584,7 +16584,7 @@ var a = "test2"; }); it("should not crash when invalid parentheses syntax is encountered", () => { - linter.verify("left = (aSize.width/2) - ()"); + linter.verify("left = (aSize.width/2) - ()", {}); }); it("should not crash when let is used inside of switch case", () => { From d2f68019b8882278877801c5ef2f74d55e2a10c1 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 4 Oct 2023 05:30:06 -0400 Subject: [PATCH 198/248] fix: Ensure correct code path for && followed by ?? (#17618) * fix: Ensure correct code path for && followed by ?? fixes #13614 * Remove .only * Update lib/linter/code-path-analysis/code-path-state.js Co-authored-by: Milos Djermanovic * Update tests/lib/linter/code-path-analysis/code-path-analyzer.js Co-authored-by: Milos Djermanovic * Update variable names in comments --------- Co-authored-by: Milos Djermanovic --- .../code-path-analysis/code-path-state.js | 147 ++++++++++++------ .../assignment--nested-and-3.js | 10 +- .../code-path-analysis/logical--and-qq.js | 22 +++ .../logical--if-mix-and-qq-1.js | 10 +- 4 files changed, 130 insertions(+), 59 deletions(-) create mode 100644 tests/fixtures/code-path-analysis/logical--and-qq.js diff --git a/lib/linter/code-path-analysis/code-path-state.js b/lib/linter/code-path-analysis/code-path-state.js index dcd24691a7f1..2b0dc2bfca04 100644 --- a/lib/linter/code-path-analysis/code-path-state.js +++ b/lib/linter/code-path-analysis/code-path-state.js @@ -121,7 +121,7 @@ class ChainContext { * true go one way and if false go the other (tracked by `trueForkContext` and * `falseForkContext`). The `??` operator doesn't operate on true/false because * the left expression is evaluated to be nullish or not, so only if nullish do - * we fork to the right expression (tracked by `qqForkcontext`). + * we fork to the right expression (tracked by `nullishForkContext`). */ class ChoiceContext { @@ -170,14 +170,14 @@ class ChoiceContext { this.falseForkContext = ForkContext.newEmpty(forkContext); /** - * The fork context for the right side of the `??` path of the choice. + * The fork context for when the choice result is `null` or `undefined`. * @type {ForkContext} */ - this.qqForkContext = ForkContext.newEmpty(forkContext); + this.nullishForkContext = ForkContext.newEmpty(forkContext); /** * Indicates if any of `trueForkContext`, `falseForkContext`, or - * `qqForkContext` have been updated with segments from a child context. + * `nullishForkContext` have been updated with segments from a child context. * @type {boolean} */ this.processed = false; @@ -849,7 +849,7 @@ function finalizeTestSegmentsOfFor(context, choiceContext, head) { if (!choiceContext.processed) { choiceContext.trueForkContext.add(head); choiceContext.falseForkContext.add(head); - choiceContext.qqForkContext.add(head); + choiceContext.nullishForkContext.add(head); } /* @@ -1095,31 +1095,31 @@ class CodePathState { /** * Pops the last choice context and finalizes it. + * This is called upon leaving a node that represents a choice. * @throws {Error} (Unreachable.) * @returns {ChoiceContext} The popped context. */ popChoiceContext() { - const context = this.choiceContext; - - this.choiceContext = context.upper; - + const poppedChoiceContext = this.choiceContext; const forkContext = this.forkContext; - const headSegments = forkContext.head; + const head = forkContext.head; - switch (context.kind) { + this.choiceContext = poppedChoiceContext.upper; + + switch (poppedChoiceContext.kind) { case "&&": case "||": case "??": /* - * The `headSegments` are the path of the right-hand operand. + * The `head` are the path of the right-hand operand. * If we haven't previously added segments from child contexts, * then we add these segments to all possible forks. */ - if (!context.processed) { - context.trueForkContext.add(headSegments); - context.falseForkContext.add(headSegments); - context.qqForkContext.add(headSegments); + if (!poppedChoiceContext.processed) { + poppedChoiceContext.trueForkContext.add(head); + poppedChoiceContext.falseForkContext.add(head); + poppedChoiceContext.nullishForkContext.add(head); } /* @@ -1128,29 +1128,29 @@ class CodePathState { * then we take the segments for this context and move them up * to the parent context. */ - if (context.isForkingAsResult) { + if (poppedChoiceContext.isForkingAsResult) { const parentContext = this.choiceContext; - parentContext.trueForkContext.addAll(context.trueForkContext); - parentContext.falseForkContext.addAll(context.falseForkContext); - parentContext.qqForkContext.addAll(context.qqForkContext); + parentContext.trueForkContext.addAll(poppedChoiceContext.trueForkContext); + parentContext.falseForkContext.addAll(poppedChoiceContext.falseForkContext); + parentContext.nullishForkContext.addAll(poppedChoiceContext.nullishForkContext); parentContext.processed = true; // Exit early so we don't collapse all paths into one. - return context; + return poppedChoiceContext; } break; case "test": - if (!context.processed) { + if (!poppedChoiceContext.processed) { /* * The head segments are the path of the `if` block here. * Updates the `true` path with the end of the `if` block. */ - context.trueForkContext.clear(); - context.trueForkContext.add(headSegments); + poppedChoiceContext.trueForkContext.clear(); + poppedChoiceContext.trueForkContext.add(head); } else { /* @@ -1158,8 +1158,8 @@ class CodePathState { * Updates the `false` path with the end of the `else` * block. */ - context.falseForkContext.clear(); - context.falseForkContext.add(headSegments); + poppedChoiceContext.falseForkContext.clear(); + poppedChoiceContext.falseForkContext.add(head); } break; @@ -1170,7 +1170,7 @@ class CodePathState { * Loops are addressed in `popLoopContext()` so just return * the context without modification. */ - return context; + return poppedChoiceContext; /* c8 ignore next */ default: @@ -1180,71 +1180,116 @@ class CodePathState { /* * Merge the true path with the false path to create a single path. */ - const combinedForkContext = context.trueForkContext; + const combinedForkContext = poppedChoiceContext.trueForkContext; - combinedForkContext.addAll(context.falseForkContext); + combinedForkContext.addAll(poppedChoiceContext.falseForkContext); forkContext.replaceHead(combinedForkContext.makeNext(0, -1)); - return context; + return poppedChoiceContext; } /** - * Makes a code path segment of the right-hand operand of a logical + * Creates a code path segment to represent right-hand operand of a logical * expression. + * This is called in the preprocessing phase when entering a node. * @throws {Error} (Unreachable.) * @returns {void} */ makeLogicalRight() { - const context = this.choiceContext; + const currentChoiceContext = this.choiceContext; const forkContext = this.forkContext; - if (context.processed) { + if (currentChoiceContext.processed) { /* - * This got segments already from the child choice context. - * Creates the next path from own true/false fork context. + * This context was already assigned segments from a child + * choice context. In this case, we are concerned only about + * the path that does not short-circuit and so ends up on the + * right-hand operand of the logical expression. */ let prevForkContext; - switch (context.kind) { + switch (currentChoiceContext.kind) { case "&&": // if true then go to the right-hand side. - prevForkContext = context.trueForkContext; + prevForkContext = currentChoiceContext.trueForkContext; break; case "||": // if false then go to the right-hand side. - prevForkContext = context.falseForkContext; + prevForkContext = currentChoiceContext.falseForkContext; break; - case "??": // Both true/false can short-circuit, so needs the third path to go to the right-hand side. That's qqForkContext. - prevForkContext = context.qqForkContext; + case "??": // Both true/false can short-circuit, so needs the third path to go to the right-hand side. That's nullishForkContext. + prevForkContext = currentChoiceContext.nullishForkContext; break; default: throw new Error("unreachable"); } + /* + * Create the segment for the right-hand operand of the logical expression + * and adjust the fork context pointer to point there. The right-hand segment + * is added at the end of all segments in `prevForkContext`. + */ forkContext.replaceHead(prevForkContext.makeNext(0, -1)); + + /* + * We no longer need this list of segments. + * + * Reset `processed` because we've removed the segments from the child + * choice context. This allows `popChoiceContext()` to continue adding + * segments later. + */ prevForkContext.clear(); - context.processed = false; + currentChoiceContext.processed = false; + } else { /* - * This did not get segments from the child choice context. - * So addresses the head segments. - * The head segments are the path of the left-hand operand. + * This choice context was not assigned segments from a child + * choice context, which means that it's a terminal logical + * expression. + * + * `head` is the segments for the left-hand operand of the + * logical expression. + * + * Each of the fork contexts below are empty at this point. We choose + * the path(s) that will short-circuit and add the segment for the + * left-hand operand to it. Ultimately, this will be the only segment + * in that path due to the short-circuting, so we are just seeding + * these paths to start. */ - switch (context.kind) { - case "&&": // the false path can short-circuit. - context.falseForkContext.add(forkContext.head); + switch (currentChoiceContext.kind) { + case "&&": + + /* + * In most contexts, when a && expression evaluates to false, + * it short circuits, so we need to account for that by setting + * the `falseForkContext` to the left operand. + * + * When a && expression is the left-hand operand for a ?? + * expression, such as `(a && b) ?? c`, a nullish value will + * also short-circuit in a different way than a false value, + * so we also set the `nullishForkContext` to the left operand. + * This path is only used with a ?? expression and is thrown + * away for any other type of logical expression, so it's safe + * to always add. + */ + currentChoiceContext.falseForkContext.add(forkContext.head); + currentChoiceContext.nullishForkContext.add(forkContext.head); break; case "||": // the true path can short-circuit. - context.trueForkContext.add(forkContext.head); + currentChoiceContext.trueForkContext.add(forkContext.head); break; case "??": // both can short-circuit. - context.trueForkContext.add(forkContext.head); - context.falseForkContext.add(forkContext.head); + currentChoiceContext.trueForkContext.add(forkContext.head); + currentChoiceContext.falseForkContext.add(forkContext.head); break; default: throw new Error("unreachable"); } + /* + * Create the segment for the right-hand operand of the logical expression + * and adjust the fork context pointer to point there. + */ forkContext.replaceHead(forkContext.makeNext(-1, -1)); } } @@ -1265,7 +1310,7 @@ class CodePathState { if (!context.processed) { context.trueForkContext.add(forkContext.head); context.falseForkContext.add(forkContext.head); - context.qqForkContext.add(forkContext.head); + context.nullishForkContext.add(forkContext.head); } context.processed = false; diff --git a/tests/fixtures/code-path-analysis/assignment--nested-and-3.js b/tests/fixtures/code-path-analysis/assignment--nested-and-3.js index 8bb52f022395..09ca58fade56 100644 --- a/tests/fixtures/code-path-analysis/assignment--nested-and-3.js +++ b/tests/fixtures/code-path-analysis/assignment--nested-and-3.js @@ -1,7 +1,8 @@ /*expected initial->s1_1->s1_2->s1_3->s1_4; -s1_1->s1_4; -s1_2->s1_4->final; +s1_1->s1_3; +s1_2->s1_4; +s1_1->s1_4->final; */ (a &&= b) ?? c; @@ -15,7 +16,8 @@ digraph { s1_3[label="Identifier (c)"]; s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"]; initial->s1_1->s1_2->s1_3->s1_4; - s1_1->s1_4; - s1_2->s1_4->final; + s1_1->s1_3; + s1_2->s1_4; + s1_1->s1_4->final; } */ diff --git a/tests/fixtures/code-path-analysis/logical--and-qq.js b/tests/fixtures/code-path-analysis/logical--and-qq.js new file mode 100644 index 000000000000..5ce3853a240f --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--and-qq.js @@ -0,0 +1,22 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_4; +s1_1->s1_3; +s1_2->s1_4; +s1_1->s1_4->final; +*/ +(a && b) ?? c; + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (a)"]; + s1_2[label="Identifier (b)\nLogicalExpression:exit"]; + s1_3[label="Identifier (c)"]; + s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"]; + initial->s1_1->s1_2->s1_3->s1_4; + s1_1->s1_3; + s1_2->s1_4; + s1_1->s1_4->final; +}*/ diff --git a/tests/fixtures/code-path-analysis/logical--if-mix-and-qq-1.js b/tests/fixtures/code-path-analysis/logical--if-mix-and-qq-1.js index 4863ac81db3b..427cc22ec5a5 100644 --- a/tests/fixtures/code-path-analysis/logical--if-mix-and-qq-1.js +++ b/tests/fixtures/code-path-analysis/logical--if-mix-and-qq-1.js @@ -1,8 +1,9 @@ /*expected initial->s1_1->s1_2->s1_3->s1_4->s1_6; -s1_1->s1_5->s1_6; +s1_1->s1_3; s1_2->s1_4; -s1_3->s1_5; +s1_3->s1_5->s1_6; +s1_1->s1_5; s1_2->s1_5; s1_6->final; */ @@ -24,9 +25,10 @@ digraph { s1_6[label="IfStatement:exit\nProgram:exit"]; s1_5[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nIdentifier:exit (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; initial->s1_1->s1_2->s1_3->s1_4->s1_6; - s1_1->s1_5->s1_6; + s1_1->s1_3; s1_2->s1_4; - s1_3->s1_5; + s1_3->s1_5->s1_6; + s1_1->s1_5; s1_2->s1_5; s1_6->final; } From dd79abc0c1857b1d765acc312c0d6518e40d31c9 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Thu, 5 Oct 2023 00:02:08 +0900 Subject: [PATCH 199/248] fix: `eslint-disable` to be able to parse quoted rule names (#17612) * fix: `eslint-disable` to be able to parse literal rule names * fix: id casing * Apply suggestions from code review Co-authored-by: Milos Djermanovic Co-authored-by: Nicholas C. Zakas * test: update test cases * test: add eslint-env test case * test: add test case and fix test case --------- Co-authored-by: Milos Djermanovic Co-authored-by: Nicholas C. Zakas --- lib/linter/apply-disable-directives.js | 2 +- lib/linter/config-comment-parser.js | 2 +- tests/lib/linter/config-comment-parser.js | 22 ++ tests/lib/linter/linter.js | 266 ++++++++++++++++++++++ 4 files changed, 290 insertions(+), 2 deletions(-) diff --git a/lib/linter/apply-disable-directives.js b/lib/linter/apply-disable-directives.js index 13ced990ff48..55f7683f3f53 100644 --- a/lib/linter/apply-disable-directives.js +++ b/lib/linter/apply-disable-directives.js @@ -87,7 +87,7 @@ function createIndividualDirectivesRemoval(directives, commentToken) { return directives.map(directive => { const { ruleId } = directive; - const regex = new RegExp(String.raw`(?:^|\s*,\s*)${escapeRegExp(ruleId)}(?:\s*,\s*|$)`, "u"); + const regex = new RegExp(String.raw`(?:^|\s*,\s*)(?['"]?)${escapeRegExp(ruleId)}\k(?:\s*,\s*|$)`, "u"); const match = regex.exec(listText); const matchedText = match[0]; const matchStartOffset = listStartOffset + match.index; diff --git a/lib/linter/config-comment-parser.js b/lib/linter/config-comment-parser.js index 9aab3c444585..cde261204f5e 100644 --- a/lib/linter/config-comment-parser.js +++ b/lib/linter/config-comment-parser.js @@ -139,7 +139,7 @@ module.exports = class ConfigCommentParser { const items = {}; string.split(",").forEach(name => { - const trimmedName = name.trim(); + const trimmedName = name.trim().replace(/^(?['"]?)(?.*)\k$/us, "$"); if (trimmedName) { items[trimmedName] = true; diff --git a/tests/lib/linter/config-comment-parser.js b/tests/lib/linter/config-comment-parser.js index e9f595d77504..39b06b13cf6e 100644 --- a/tests/lib/linter/config-comment-parser.js +++ b/tests/lib/linter/config-comment-parser.js @@ -224,6 +224,28 @@ describe("ConfigCommentParser", () => { b: true }); }); + + it("should parse list config with quoted items", () => { + const code = "'a', \"b\", 'c\", \"d'"; + const result = commentParser.parseListConfig(code); + + assert.deepStrictEqual(result, { + a: true, + b: true, + "\"d'": true, // This result is correct because used mismatched quotes. + "'c\"": true // This result is correct because used mismatched quotes. + }); + }); + it("should parse list config with spaced items", () => { + const code = " a b , 'c d' , \"e f\" "; + const result = commentParser.parseListConfig(code); + + assert.deepStrictEqual(result, { + "a b": true, + "c d": true, + "e f": true + }); + }); }); }); diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index bf1f1487593a..1f366e237bd7 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -1467,6 +1467,32 @@ describe("Linter", () => { linter.verify(code, config); assert(spy && spy.calledOnce); }); + + it("variables should be available in global scope with quoted items", () => { + const code = `/*${ESLINT_ENV} 'node'*/ function f() {} /*${ESLINT_ENV} "browser", "mocha"*/`; + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + const scope = context.getScope(), + exports = getVariable(scope, "exports"), + window = getVariable(scope, "window"), + it = getVariable(scope, "it"); + + assert.strictEqual(exports.writeable, true); + assert.strictEqual(window.writeable, false); + assert.strictEqual(it.writeable, false); + }); + + return { Program: spy }; + } + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); }); describe("when evaluating code containing /*eslint-env */ block with sloppy whitespace", () => { @@ -2604,6 +2630,33 @@ describe("Linter", () => { assert.strictEqual(suppressedMessages.length, 2); assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); }); + + it("should report a violation with quoted rule names in eslint-disable-line", () => { + const code = [ + "alert('test'); // eslint-disable-line 'no-alert'", + "console.log('test');", // here + "alert('test'); // eslint-disable-line \"no-alert\"" + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1 + } + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + assert.strictEqual(messages[0].line, 2); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[0].line, 1); + assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].line, 3); + }); }); describe("eslint-disable-next-line", () => { @@ -2957,6 +3010,31 @@ describe("Linter", () => { assert.strictEqual(suppressedMessages.length, 0); }); + + it("should ignore violation of specified rule on next line with quoted rule names", () => { + const code = [ + "// eslint-disable-next-line 'no-alert'", + "alert('test');", + "// eslint-disable-next-line \"no-alert\"", + "alert('test');", + "console.log('test');" + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1 + } + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); + }); }); }); @@ -3233,6 +3311,61 @@ describe("Linter", () => { assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); assert.strictEqual(suppressedMessages[2].line, 6); }); + + it("should report a violation with quoted rule names in eslint-disable", () => { + const code = [ + "/*eslint-disable 'no-alert' */", + "alert('test');", + "console.log('test');", // here + "/*eslint-enable */", + "/*eslint-disable \"no-console\" */", + "alert('test');", // here + "console.log('test');" + ].join("\n"); + const config = { rules: { "no-alert": 1, "no-console": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-console"); + assert.strictEqual(messages[1].ruleId, "no-alert"); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); + }); + + it("should report a violation with quoted rule names in eslint-enable", () => { + const code = [ + "/*eslint-disable no-alert, no-console */", + "alert('test');", + "console.log('test');", + "/*eslint-enable 'no-alert'*/", + "alert('test');", // here + "console.log('test');", + "/*eslint-enable \"no-console\"*/", + "console.log('test');" // here + ].join("\n"); + const config = { rules: { "no-alert": 1, "no-console": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 5); + assert.strictEqual(messages[1].ruleId, "no-console"); + assert.strictEqual(messages[1].line, 8); + + assert.strictEqual(suppressedMessages.length, 3); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); + assert.strictEqual(suppressedMessages[1].line, 3); + assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); + assert.strictEqual(suppressedMessages[2].line, 6); + }); }); describe("when evaluating code with comments to enable and disable multiple comma separated rules", () => { @@ -4813,6 +4946,20 @@ var a = "test2"; output ); }); + + // Test for quoted rule names + for (const testcaseForLiteral of [ + { code: code.replace(/((?:un)?used[\w-]*)/gu, '"$1"'), output: output.replace(/((?:un)?used[\w-]*)/gu, '"$1"') }, + { code: code.replace(/((?:un)?used[\w-]*)/gu, "'$1'"), output: output.replace(/((?:un)?used[\w-]*)/gu, "'$1'") } + ]) { + // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach() + it(testcaseForLiteral.code, () => { + assert.strictEqual( + linter.verifyAndFix(testcaseForLiteral.code, config).output, + testcaseForLiteral.output + ); + }); + } } }); }); @@ -13060,6 +13207,60 @@ var a = "test2"; assert.strictEqual(suppressedMessages[1].line, 3); }); + it("should report a violation with quoted rule names in eslint-disable", () => { + const code = [ + "/*eslint-disable 'no-alert' */", + "alert('test');", + "console.log('test');", // here + "/*eslint-enable */", + "/*eslint-disable \"no-console\" */", + "alert('test');", // here + "console.log('test');" + ].join("\n"); + const config = { rules: { "no-alert": 1, "no-console": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-console"); + assert.strictEqual(messages[1].ruleId, "no-alert"); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); + }); + + it("should report a violation with quoted rule names in eslint-enable", () => { + const code = [ + "/*eslint-disable no-alert, no-console */", + "alert('test');", + "console.log('test');", + "/*eslint-enable 'no-alert'*/", + "alert('test');", // here + "console.log('test');", + "/*eslint-enable \"no-console\"*/", + "console.log('test');" // here + ].join("\n"); + const config = { rules: { "no-alert": 1, "no-console": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 5); + assert.strictEqual(messages[1].ruleId, "no-console"); + assert.strictEqual(messages[1].line, 8); + + assert.strictEqual(suppressedMessages.length, 3); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); + assert.strictEqual(suppressedMessages[1].line, 3); + assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); + assert.strictEqual(suppressedMessages[2].line, 6); + }); }); describe("/*eslint-disable-line*/", () => { @@ -13293,6 +13494,32 @@ var a = "test2"; assert.strictEqual(suppressedMessages.length, 5); }); + it("should report a violation with quoted rule names in eslint-disable-line", () => { + const code = [ + "alert('test'); // eslint-disable-line 'no-alert'", + "console.log('test');", // here + "alert('test'); // eslint-disable-line \"no-alert\"" + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1 + } + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + assert.strictEqual(messages[0].line, 2); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[0].line, 1); + assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].line, 3); + }); }); describe("/*eslint-disable-next-line*/", () => { @@ -13689,6 +13916,31 @@ var a = "test2"; assert.strictEqual(suppressedMessages.length, 0); }); + + it("should ignore violation of specified rule on next line with quoted rule names", () => { + const code = [ + "// eslint-disable-next-line 'no-alert'", + "alert('test');", + "// eslint-disable-next-line \"no-alert\"", + "alert('test');", + "console.log('test');" + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1 + } + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); + }); }); describe("descriptions in directive comments", () => { @@ -15366,6 +15618,20 @@ var a = "test2"; output ); }); + + // Test for quoted rule names + for (const testcaseForLiteral of [ + { code: code.replace(/(test\/[\w-]+)/gu, '"$1"'), output: output.replace(/(test\/[\w-]+)/gu, '"$1"') }, + { code: code.replace(/(test\/[\w-]+)/gu, "'$1'"), output: output.replace(/(test\/[\w-]+)/gu, "'$1'") } + ]) { + // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach() + it(testcaseForLiteral.code, () => { + assert.strictEqual( + linter.verifyAndFix(testcaseForLiteral.code, config).output, + testcaseForLiteral.output + ); + }); + } } }); }); From ee5be81fa3c4fe801c2f653854f098ed6a84dcef Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Thu, 5 Oct 2023 14:18:44 +0200 Subject: [PATCH 200/248] docs: default to `sourceType: "module"` in rule examples (#17615) * Default to `"sourceType": "module"` in rule examples * Fix `sourceType` in obvious cases * Fix `sourceType` in less obvious cases * Fix name conflicts * Apply suggestions --- docs/.eleventy.js | 5 +- docs/src/rules/camelcase.md | 2 +- docs/src/rules/comma-spacing.md | 8 +-- docs/src/rules/comma-style.md | 8 +-- docs/src/rules/consistent-return.md | 2 +- docs/src/rules/default-param-last.md | 2 +- docs/src/rules/function-paren-newline.md | 52 +++++++++---------- docs/src/rules/global-require.md | 4 +- docs/src/rules/id-length.md | 4 +- docs/src/rules/lines-around-directive.md | 16 +++--- docs/src/rules/max-statements-per-line.md | 8 +-- docs/src/rules/newline-before-return.md | 20 +++---- docs/src/rules/no-catch-shadow.md | 6 +-- docs/src/rules/no-cond-assign.md | 18 +++---- docs/src/rules/no-delete-var.md | 2 +- docs/src/rules/no-dupe-args.md | 4 +- docs/src/rules/no-else-return.md | 18 +++---- docs/src/rules/no-empty-function.md | 20 +++---- docs/src/rules/no-empty-pattern.md | 24 ++++----- docs/src/rules/no-extra-boolean-cast.md | 2 +- docs/src/rules/no-extra-parens.md | 4 +- docs/src/rules/no-func-assign.md | 12 ++--- docs/src/rules/no-inner-declarations.md | 8 +-- docs/src/rules/no-invalid-this.md | 8 +-- docs/src/rules/no-irregular-whitespace.md | 24 ++++----- docs/src/rules/no-nonoctal-decimal-escape.md | 4 +- docs/src/rules/no-octal-escape.md | 4 +- docs/src/rules/no-octal.md | 4 +- docs/src/rules/no-param-reassign.md | 50 +++++++++--------- docs/src/rules/no-plusplus.md | 4 +- docs/src/rules/no-restricted-syntax.md | 4 +- docs/src/rules/no-return-assign.md | 16 +++--- docs/src/rules/no-return-await.md | 8 +-- docs/src/rules/no-sequences.md | 6 +-- docs/src/rules/no-shadow-restricted-names.md | 4 +- docs/src/rules/no-shadow.md | 6 +-- docs/src/rules/no-throw-literal.md | 4 +- docs/src/rules/no-undefined.md | 2 +- docs/src/rules/no-unsafe-optional-chaining.md | 2 +- docs/src/rules/no-useless-escape.md | 2 +- docs/src/rules/no-useless-return.md | 18 +++---- docs/src/rules/no-with.md | 4 +- docs/src/rules/one-var.md | 44 ++++++++-------- .../rules/padding-line-between-statements.md | 16 +++--- docs/src/rules/prefer-reflect.md | 4 +- docs/src/rules/prefer-rest-params.md | 4 +- docs/src/rules/require-await.md | 2 +- docs/src/rules/require-jsdoc.md | 16 +++--- docs/src/rules/require-yield.md | 4 +- docs/src/rules/space-before-function-paren.md | 32 ++++++------ .../space-before-function-parentheses.md | 16 +++--- docs/src/rules/strict.md | 32 ++++++------ docs/src/rules/vars-on-top.md | 4 +- 53 files changed, 296 insertions(+), 301 deletions(-) diff --git a/docs/.eleventy.js b/docs/.eleventy.js index 94a202112543..68c6604cf8d0 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -205,11 +205,12 @@ module.exports = function(eleventyConfig) { } // See https://github.com/eslint/eslint.org/blob/ac38ab41f99b89a8798d374f74e2cce01171be8b/src/playground/App.js#L44 - const parserOptions = tokens[index].info?.split("correct ")[1]?.trim(); + const parserOptionsJSON = tokens[index].info?.split("correct ")[1]?.trim(); + const parserOptions = { sourceType: "module", ...(parserOptionsJSON && JSON.parse(parserOptionsJSON)) }; const { content } = tokens[index + 1]; const state = encodeToBase64( JSON.stringify({ - ...(parserOptions && { options: { parserOptions: JSON.parse(parserOptions) } }), + options: { parserOptions }, text: content }) ); diff --git a/docs/src/rules/camelcase.md b/docs/src/rules/camelcase.md index 46757fd53b06..aa75b231becd 100644 --- a/docs/src/rules/camelcase.md +++ b/docs/src/rules/camelcase.md @@ -328,7 +328,7 @@ function UNSAFE_componentWillMount() { // ... } -function UNSAFE_componentWillMount() { +function UNSAFE_componentWillReceiveProps() { // ... } ``` diff --git a/docs/src/rules/comma-spacing.md b/docs/src/rules/comma-spacing.md index b69378563211..9bc7f82dd246 100644 --- a/docs/src/rules/comma-spacing.md +++ b/docs/src/rules/comma-spacing.md @@ -60,7 +60,7 @@ var arr = [1 , 2]; var obj = {"foo": "bar" ,"baz": "qur"}; foo(a ,b); new Foo(a ,b); -function foo(a ,b){} +function baz(a ,b){} a ,b ``` @@ -80,7 +80,7 @@ var arr = [1,, 3] var obj = {"foo": "bar", "baz": "qur"}; foo(a, b); new Foo(a, b); -function foo(a, b){} +function qur(a, b){} a, b ``` @@ -131,7 +131,7 @@ var foo = 1, bar = 2; var arr = [1 , 2]; var obj = {"foo": "bar", "baz": "qur"}; new Foo(a,b); -function foo(a,b){} +function baz(a,b){} a, b ``` @@ -151,7 +151,7 @@ var arr = [1 ,,3] var obj = {"foo": "bar" ,"baz": "qur"}; foo(a ,b); new Foo(a ,b); -function foo(a ,b){} +function qur(a ,b){} a ,b ``` diff --git a/docs/src/rules/comma-style.md b/docs/src/rules/comma-style.md index 99d14b27f4fa..6936cdbcb244 100644 --- a/docs/src/rules/comma-style.md +++ b/docs/src/rules/comma-style.md @@ -69,7 +69,7 @@ var foo = 1 var foo = ["apples" , "oranges"]; -function bar() { +function baz() { return { "a": 1 ,"b:": 2 @@ -94,7 +94,7 @@ var foo = 1, var foo = ["apples", "oranges"]; -function bar() { +function baz() { return { "a": 1, "b:": 2 @@ -119,7 +119,7 @@ var foo = 1, var foo = ["apples", "oranges"]; -function bar() { +function baz() { return { "a": 1, "b:": 2 @@ -144,7 +144,7 @@ var foo = 1 var foo = ["apples" ,"oranges"]; -function bar() { +function baz() { return { "a": 1 ,"b:": 2 diff --git a/docs/src/rules/consistent-return.md b/docs/src/rules/consistent-return.md index 2d5f4e3f2ca7..797c9b08ca01 100644 --- a/docs/src/rules/consistent-return.md +++ b/docs/src/rules/consistent-return.md @@ -48,7 +48,7 @@ function doSomething(condition) { } } -function doSomething(condition) { +function doSomethingElse(condition) { if (condition) { return true; } diff --git a/docs/src/rules/default-param-last.md b/docs/src/rules/default-param-last.md index 7f64f7c4464d..63672a9103ec 100644 --- a/docs/src/rules/default-param-last.md +++ b/docs/src/rules/default-param-last.md @@ -28,7 +28,7 @@ Examples of **incorrect** code for this rule: function f(a = 0, b) {} -function f(a, b = 0, c) {} +function g(a, b = 0, c) {} ``` ::: diff --git a/docs/src/rules/function-paren-newline.md b/docs/src/rules/function-paren-newline.md index 4ba5a922c8f4..e6a067cf5f23 100644 --- a/docs/src/rules/function-paren-newline.md +++ b/docs/src/rules/function-paren-newline.md @@ -49,9 +49,9 @@ Examples of **incorrect** code for this rule with the `"always"` option: function foo(bar, baz) {} -var foo = function(bar, baz) {}; +var qux = function(bar, baz) {}; -var foo = (bar, baz) => {}; +var qux = (bar, baz) => {}; foo(bar, baz); ``` @@ -70,11 +70,11 @@ function foo( baz ) {} -var foo = function( +var qux = function( bar, baz ) {}; -var foo = ( +var qux = ( bar, baz ) => {}; @@ -99,11 +99,11 @@ function foo( baz ) {} -var foo = function( +var qux = function( bar, baz ) {}; -var foo = ( +var qux = ( bar, baz ) => {}; @@ -125,12 +125,12 @@ Examples of **correct** code for this rule with the `"never"` option: function foo(bar, baz) {} -function foo(bar, +function qux(bar, baz) {} -var foo = function(bar, baz) {}; +var foobar = function(bar, baz) {}; -var foo = (bar, baz) => {}; +var foobar = (bar, baz) => {}; foo(bar, baz); @@ -151,11 +151,11 @@ function foo(bar, baz ) {} -var foo = function( +var qux = function( bar, baz ) {}; -var foo = ( +var qux = ( bar, baz) => {}; @@ -180,12 +180,12 @@ Examples of **correct** code for this rule with the default `"multiline"` option function foo(bar, baz) {} -var foo = function( +var foobar = function( bar, baz ) {}; -var foo = (bar, baz) => {}; +var foobar = (bar, baz) => {}; foo(bar, baz, qux); @@ -213,11 +213,11 @@ function foo(bar, baz ) {} -var foo = function(bar, +var qux = function(bar, baz ) {}; -var foo = ( +var qux = ( bar, baz) => {}; @@ -243,9 +243,9 @@ Examples of **correct** code for this rule with the `"consistent"` option: function foo(bar, baz) {} -var foo = function(bar, baz) {}; +var qux = function(bar, baz) {}; -var foo = ( +var qux = ( bar, baz ) => {}; @@ -274,11 +274,11 @@ function foo(bar, baz ) {} -var foo = function(bar, +var foobar = function(bar, baz ) {}; -var foo = ( +var foobar = ( bar, baz) => {}; @@ -306,9 +306,9 @@ function foo( baz ) {} -var foo = function(bar, baz) {}; +var qux = function(bar, baz) {}; -var foo = ( +var qux = ( bar ) => {}; @@ -333,13 +333,13 @@ function foo( baz ) {} -function foo(bar, baz, qux) {} +function foobar(bar, baz, qux) {} -var foo = function( +var barbaz = function( bar, baz ) {}; -var foo = (bar, +var barbaz = (bar, baz) => {}; foo(bar, @@ -357,13 +357,13 @@ Examples of **correct** code for this rule with the `{ "minItems": 3 }` option: function foo(bar, baz) {} -var foo = function( +var foobar = function( bar, baz, qux ) {}; -var foo = ( +var foobar = ( bar, baz, qux ) => {}; diff --git a/docs/src/rules/global-require.md b/docs/src/rules/global-require.md index e960468b3807..8460acb89ad1 100644 --- a/docs/src/rules/global-require.md +++ b/docs/src/rules/global-require.md @@ -32,7 +32,7 @@ This rule requires all calls to `require()` to be at the top level of the module Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint global-require: "error"*/ @@ -76,7 +76,7 @@ try { Examples of **correct** code for this rule: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint global-require: "error"*/ diff --git a/docs/src/rules/id-length.md b/docs/src/rules/id-length.md index adf5e8978175..aed5ada0a7c6 100644 --- a/docs/src/rules/id-length.md +++ b/docs/src/rules/id-length.md @@ -160,10 +160,10 @@ try { } var myObj = { apple: 1 }; (value) => { value * value }; -function foobar(value = 0) { } +function foobaz(value = 0) { } class MyClass { } class Foobar { method() {} } -function foobar(...args) { } +function barbaz(...args) { } var { prop } = {}; var [longName] = foo; var { a: [prop] } = {}; diff --git a/docs/src/rules/lines-around-directive.md b/docs/src/rules/lines-around-directive.md index fb71775c7f16..8a83df1afd7c 100644 --- a/docs/src/rules/lines-around-directive.md +++ b/docs/src/rules/lines-around-directive.md @@ -60,7 +60,7 @@ This is the default option. Examples of **incorrect** code for this rule with the `"always"` option: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /* eslint lines-around-directive: ["error", "always"] */ @@ -92,7 +92,7 @@ function foo() { Examples of **correct** code for this rule with the `"always"` option: -::: correct +::: correct { "sourceType": "script" } ```js /* eslint lines-around-directive: ["error", "always"] */ @@ -132,7 +132,7 @@ function foo() { Examples of **incorrect** code for this rule with the `"never"` option: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /* eslint lines-around-directive: ["error", "never"] */ @@ -171,7 +171,7 @@ function foo() { Examples of **correct** code for this rule with the `"never"` option: -::: correct +::: correct { "sourceType": "script" } ```js /* eslint lines-around-directive: ["error", "never"] */ @@ -205,7 +205,7 @@ function foo() { Examples of **incorrect** code for this rule with the `{ "before": "never", "after": "always" }` option: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /* eslint lines-around-directive: ["error", { "before": "never", "after": "always" }] */ @@ -240,7 +240,7 @@ function foo() { Examples of **correct** code for this rule with the `{ "before": "never", "after": "always" }` option: -::: correct +::: correct { "sourceType": "script" } ```js /* eslint lines-around-directive: ["error", { "before": "never", "after": "always" }] */ @@ -276,7 +276,7 @@ function foo() { Examples of **incorrect** code for this rule with the `{ "before": "always", "after": "never" }` option: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /* eslint lines-around-directive: ["error", { "before": "always", "after": "never" }] */ @@ -312,7 +312,7 @@ function foo() { Examples of **correct** code for this rule with the `{ "before": "always", "after": "never" }` option: -::: correct +::: correct { "sourceType": "script" } ```js /* eslint lines-around-directive: ["error", { "before": "always", "after": "never" }] */ diff --git a/docs/src/rules/max-statements-per-line.md b/docs/src/rules/max-statements-per-line.md index 238ade787e86..76a49e3a09b1 100644 --- a/docs/src/rules/max-statements-per-line.md +++ b/docs/src/rules/max-statements-per-line.md @@ -40,7 +40,7 @@ if (condition) { bar = 1; } for (var i = 0; i < length; ++i) { bar = 1; } switch (discriminant) { default: break; } function foo() { bar = 1; } -var foo = function foo() { bar = 1; }; +var qux = function qux() { bar = 1; }; (function foo() { bar = 1; })(); ``` @@ -58,7 +58,7 @@ if (condition) bar = 1; for (var i = 0; i < length; ++i); switch (discriminant) { default: } function foo() { } -var foo = function foo() { }; +var qux = function qux() { }; (function foo() { })(); ``` @@ -76,7 +76,7 @@ if (condition) { bar = 1; } else { baz = 2; } for (var i = 0; i < length; ++i) { bar = 1; baz = 2; } switch (discriminant) { case 'test': break; default: break; } function foo() { bar = 1; baz = 2; } -var foo = function foo() { bar = 1; }; +var qux = function qux() { bar = 1; }; (function foo() { bar = 1; baz = 2; })(); ``` @@ -94,7 +94,7 @@ if (condition) bar = 1; if (condition) baz = 2; for (var i = 0; i < length; ++i) { bar = 1; } switch (discriminant) { default: break; } function foo() { bar = 1; } -var foo = function foo() { bar = 1; }; +var qux = function qux() { bar = 1; }; (function foo() { var bar = 1; })(); ``` diff --git a/docs/src/rules/newline-before-return.md b/docs/src/rules/newline-before-return.md index 41fb7726c7b2..1980f5bd3463 100644 --- a/docs/src/rules/newline-before-return.md +++ b/docs/src/rules/newline-before-return.md @@ -49,14 +49,14 @@ Examples of **incorrect** code for this rule: ```js /*eslint newline-before-return: "error"*/ -function foo(bar) { +function foo1(bar) { if (!bar) { return; } return bar; } -function foo(bar) { +function foo2(bar) { if (!bar) { return; } @@ -75,30 +75,30 @@ Examples of **correct** code for this rule: ```js /*eslint newline-before-return: "error"*/ -function foo() { +function foo1() { return; } -function foo() { +function foo2() { return; } -function foo(bar) { +function foo3(bar) { if (!bar) return; } -function foo(bar) { +function foo4(bar) { if (!bar) { return }; } -function foo(bar) { +function foo5(bar) { if (!bar) { return; } } -function foo(bar) { +function foo6(bar) { if (!bar) { return; } @@ -106,14 +106,14 @@ function foo(bar) { return bar; } -function foo(bar) { +function foo7(bar) { if (!bar) { return; } } -function foo() { +function foo8() { // comment return; diff --git a/docs/src/rules/no-catch-shadow.md b/docs/src/rules/no-catch-shadow.md index 23328e42fef2..c875f9fe4a92 100644 --- a/docs/src/rules/no-catch-shadow.md +++ b/docs/src/rules/no-catch-shadow.md @@ -39,13 +39,13 @@ try { } -function err() { +function error() { // ... }; try { throw "problem"; -} catch (err) { +} catch (error) { } ``` @@ -67,7 +67,7 @@ try { } -function err() { +function error() { // ... }; diff --git a/docs/src/rules/no-cond-assign.md b/docs/src/rules/no-cond-assign.md index b72c2d09c672..7cb02d7c32fb 100644 --- a/docs/src/rules/no-cond-assign.md +++ b/docs/src/rules/no-cond-assign.md @@ -45,8 +45,7 @@ if (x = 0) { } // Practical example that is similar to an error -function setHeight(someNode) { - "use strict"; +var setHeight = function (someNode) { do { someNode.height = "100px"; } while (someNode = someNode.parentNode); @@ -69,16 +68,14 @@ if (x === 0) { } // Practical example that wraps the assignment in parentheses -function setHeight(someNode) { - "use strict"; +var setHeight = function (someNode) { do { someNode.height = "100px"; } while ((someNode = someNode.parentNode)); } // Practical example that wraps the assignment and tests for 'null' -function setHeight(someNode) { - "use strict"; +var setHeight = function (someNode) { do { someNode.height = "100px"; } while ((someNode = someNode.parentNode) !== null); @@ -103,24 +100,21 @@ if (x = 0) { } // Practical example that is similar to an error -function setHeight(someNode) { - "use strict"; +var setHeight = function (someNode) { do { someNode.height = "100px"; } while (someNode = someNode.parentNode); } // Practical example that wraps the assignment in parentheses -function setHeight(someNode) { - "use strict"; +var setHeight = function (someNode) { do { someNode.height = "100px"; } while ((someNode = someNode.parentNode)); } // Practical example that wraps the assignment and tests for 'null' -function setHeight(someNode) { - "use strict"; +var setHeight = function (someNode) { do { someNode.height = "100px"; } while ((someNode = someNode.parentNode) !== null); diff --git a/docs/src/rules/no-delete-var.md b/docs/src/rules/no-delete-var.md index 65a5aa1614dd..f66aec5879fb 100644 --- a/docs/src/rules/no-delete-var.md +++ b/docs/src/rules/no-delete-var.md @@ -15,7 +15,7 @@ If ESLint parses code in strict mode, the parser (instead of this rule) reports Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-delete-var: "error"*/ diff --git a/docs/src/rules/no-dupe-args.md b/docs/src/rules/no-dupe-args.md index 3cb9133c83fa..748d52c2e21f 100644 --- a/docs/src/rules/no-dupe-args.md +++ b/docs/src/rules/no-dupe-args.md @@ -16,7 +16,7 @@ If ESLint parses code in strict mode, the parser (instead of this rule) reports Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-dupe-args: "error"*/ @@ -34,7 +34,7 @@ var bar = function (a, b, a) { Examples of **correct** code for this rule: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-dupe-args: "error"*/ diff --git a/docs/src/rules/no-else-return.md b/docs/src/rules/no-else-return.md index 917c44553908..79b4fe5fb01d 100644 --- a/docs/src/rules/no-else-return.md +++ b/docs/src/rules/no-else-return.md @@ -37,7 +37,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-else-return: "error"*/ -function foo() { +function foo1() { if (x) { return y; } else { @@ -45,7 +45,7 @@ function foo() { } } -function foo() { +function foo2() { if (x) { return y; } else if (z) { @@ -55,7 +55,7 @@ function foo() { } } -function foo() { +function foo3() { if (x) { return y; } else { @@ -65,7 +65,7 @@ function foo() { return t; } -function foo() { +function foo4() { if (error) { return 'It failed'; } else { @@ -76,7 +76,7 @@ function foo() { } // Two warnings for nested occurrences -function foo() { +function foo5() { if (x) { if (y) { return y; @@ -98,7 +98,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-else-return: "error"*/ -function foo() { +function foo1() { if (x) { return y; } @@ -106,7 +106,7 @@ function foo() { return z; } -function foo() { +function foo2() { if (x) { return y; } else if (z) { @@ -116,7 +116,7 @@ function foo() { } } -function foo() { +function foo3() { if (x) { if (z) { return y; @@ -126,7 +126,7 @@ function foo() { } } -function foo() { +function foo4() { if (error) { return 'It failed'; } else if (loading) { diff --git a/docs/src/rules/no-empty-function.md b/docs/src/rules/no-empty-function.md index a9be4bb05d51..68aa2ad887b6 100644 --- a/docs/src/rules/no-empty-function.md +++ b/docs/src/rules/no-empty-function.md @@ -38,13 +38,13 @@ Examples of **incorrect** code for this rule: function foo() {} -var foo = function() {}; +var bar = function() {}; -var foo = () => {}; +var bar = () => {}; -function* foo() {} +function* baz() {} -var foo = function*() {}; +var bar = function*() {}; var obj = { foo: function() {}, @@ -95,19 +95,19 @@ function foo() { // do nothing. } -var foo = function() { +var baz = function() { // any clear comments. }; -var foo = () => { +var baz = () => { bar(); }; -function* foo() { +function* foobar() { // do nothing. } -var foo = function*() { +var baz = function*() { // do nothing. }; @@ -205,7 +205,7 @@ Examples of **correct** code for the `{ "allow": ["functions"] }` option: function foo() {} -var foo = function() {}; +var bar = function() {}; var obj = { foo: function() {} @@ -241,7 +241,7 @@ Examples of **correct** code for the `{ "allow": ["generatorFunctions"] }` optio function* foo() {} -var foo = function*() {}; +var bar = function*() {}; var obj = { foo: function*() {} diff --git a/docs/src/rules/no-empty-pattern.md b/docs/src/rules/no-empty-pattern.md index 7edd933731a9..92000add1857 100644 --- a/docs/src/rules/no-empty-pattern.md +++ b/docs/src/rules/no-empty-pattern.md @@ -44,9 +44,9 @@ var [] = foo; var {a: {}} = foo; var {a: []} = foo; function foo({}) {} -function foo([]) {} -function foo({a: {}}) {} -function foo({a: []}) {} +function bar([]) {} +function baz({a: {}}) {} +function qux({a: []}) {} ``` ::: @@ -61,7 +61,7 @@ Examples of **correct** code for this rule: var {a = {}} = foo; var {a = []} = foo; function foo({a = {}}) {} -function foo({a = []}) {} +function bar({a = []}) {} ``` ::: @@ -84,12 +84,12 @@ Examples of **incorrect** code for this rule with the `{"allowObjectPatternsAsPa /*eslint no-empty-pattern: ["error", { "allowObjectPatternsAsParameters": true }]*/ function foo({a: {}}) {} -var foo = function({a: {}}) {}; -var foo = ({a: {}}) => {}; -var foo = ({} = bar) => {}; -var foo = ({} = { bar: 1 }) => {}; +var bar = function({a: {}}) {}; +var bar = ({a: {}}) => {}; +var bar = ({} = bar) => {}; +var bar = ({} = { bar: 1 }) => {}; -function foo([]) {} +function baz([]) {} ``` ::: @@ -102,10 +102,10 @@ Examples of **correct** code for this rule with the `{"allowObjectPatternsAsPara /*eslint no-empty-pattern: ["error", { "allowObjectPatternsAsParameters": true }]*/ function foo({}) {} -var foo = function({}) {}; -var foo = ({}) => {}; +var bar = function({}) {}; +var bar = ({}) => {}; -function foo({} = {}) {} +function baz({} = {}) {} ``` ::: diff --git a/docs/src/rules/no-extra-boolean-cast.md b/docs/src/rules/no-extra-boolean-cast.md index e45509890f50..2408d6171746 100644 --- a/docs/src/rules/no-extra-boolean-cast.md +++ b/docs/src/rules/no-extra-boolean-cast.md @@ -75,7 +75,7 @@ Examples of **correct** code for this rule: var foo = !!bar; var foo = Boolean(bar); -function foo() { +function qux() { return !!bar; } diff --git a/docs/src/rules/no-extra-parens.md b/docs/src/rules/no-extra-parens.md index 77bef24a7286..1a4d8e2d48a0 100644 --- a/docs/src/rules/no-extra-parens.md +++ b/docs/src/rules/no-extra-parens.md @@ -159,11 +159,11 @@ Examples of **correct** code for this rule with the `"all"` and `{ "returnAssign ```js /* eslint no-extra-parens: ["error", "all", { "returnAssign": false }] */ -function a(b) { +function a1(b) { return (b = 1); } -function a(b) { +function a2(b) { return b ? (c = d) : (c = e); } diff --git a/docs/src/rules/no-func-assign.md b/docs/src/rules/no-func-assign.md index a0f146203742..6346e2b8064f 100644 --- a/docs/src/rules/no-func-assign.md +++ b/docs/src/rules/no-func-assign.md @@ -27,8 +27,8 @@ Examples of **incorrect** code for this rule: function foo() {} foo = bar; -function foo() { - foo = bar; +function baz() { + baz = bar; } var a = function hello() { @@ -61,12 +61,12 @@ Examples of **correct** code for this rule: var foo = function () {} foo = bar; -function foo(foo) { // `foo` is shadowed. - foo = bar; +function baz(baz) { // `baz` is shadowed. + baz = bar; } -function foo() { - var foo = bar; // `foo` is shadowed. +function qux() { + var qux = bar; // `qux` is shadowed. } ``` diff --git a/docs/src/rules/no-inner-declarations.md b/docs/src/rules/no-inner-declarations.md index 8828fabe7d2f..a81a45a61fa1 100644 --- a/docs/src/rules/no-inner-declarations.md +++ b/docs/src/rules/no-inner-declarations.md @@ -74,7 +74,7 @@ This rule has a string option: Examples of **incorrect** code for this rule with the default `"functions"` option: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-inner-declarations: "error"*/ @@ -104,7 +104,7 @@ class C { Examples of **correct** code for this rule with the default `"functions"` option: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-inner-declarations: "error"*/ @@ -139,7 +139,7 @@ if (foo) var a; Examples of **incorrect** code for this rule with the `"both"` option: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-inner-declarations: ["error", "both"]*/ @@ -171,7 +171,7 @@ class C { Examples of **correct** code for this rule with the `"both"` option: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-inner-declarations: ["error", "both"]*/ diff --git a/docs/src/rules/no-invalid-this.md b/docs/src/rules/no-invalid-this.md index 9e4a2aedffae..0c529a970650 100644 --- a/docs/src/rules/no-invalid-this.md +++ b/docs/src/rules/no-invalid-this.md @@ -49,7 +49,7 @@ With `"parserOptions": { "sourceType": "module" }` in the ESLint configuration, Examples of **incorrect** code for this rule in strict mode: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-invalid-this: "error"*/ @@ -97,7 +97,7 @@ foo.forEach(function() { Examples of **correct** code for this rule in strict mode: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-invalid-this: "error"*/ @@ -243,7 +243,7 @@ Set `"capIsConstructor"` to `false` if you want those functions to be treated as Examples of **incorrect** code for this rule with `"capIsConstructor"` option set to `false`: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-invalid-this: ["error", { "capIsConstructor": false }]*/ @@ -271,7 +271,7 @@ Baz = function() { Examples of **correct** code for this rule with `"capIsConstructor"` option set to `false`: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-invalid-this: ["error", { "capIsConstructor": false }]*/ diff --git a/docs/src/rules/no-irregular-whitespace.md b/docs/src/rules/no-irregular-whitespace.md index 137c588d9b15..c0683645628e 100644 --- a/docs/src/rules/no-irregular-whitespace.md +++ b/docs/src/rules/no-irregular-whitespace.md @@ -74,31 +74,31 @@ Examples of **incorrect** code for this rule with the default `{ "skipStrings": ```js /*eslint no-irregular-whitespace: "error"*/ -function thing()ย /**/{ +var thing = function()ย /**/{ return 'test'; } -function thing(ย /**/){ +var thing = function(ย /**/){ return 'test'; } -function thingย /**/(){ +var thing = functionย /**/(){ return 'test'; } -function thingแ Ž/**/(){ +var thing = functionแ Ž/**/(){ return 'test'; } -function thing() { +var thing = function() { return 'test';โ€‚/**/ } -function thing() { +var thing = function() { return 'test';ย /**/ } -function thing() { +var thing = function() { // Descriptionย : some descriptive text } @@ -106,12 +106,12 @@ function thing() { Descriptionย : some descriptive text */ -function thing() { +var thing = function() { return /ย regexp/; } /*eslint-env es6*/ -function thing() { +var thing = function() { return `templateโ€‚string`; } ``` @@ -125,15 +125,15 @@ Examples of **correct** code for this rule with the default `{ "skipStrings": tr ```js /*eslint no-irregular-whitespace: "error"*/ -function thing() { +var thing = function() { return 'ย thing'; } -function thing() { +var thing = function() { return 'โ€‹thing'; } -function thing() { +var thing = function() { return 'thย ing'; } ``` diff --git a/docs/src/rules/no-nonoctal-decimal-escape.md b/docs/src/rules/no-nonoctal-decimal-escape.md index 6ca59fdf79a4..43d0282635af 100644 --- a/docs/src/rules/no-nonoctal-decimal-escape.md +++ b/docs/src/rules/no-nonoctal-decimal-escape.md @@ -30,7 +30,7 @@ This rule disallows `\8` and `\9` escape sequences in string literals. Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-nonoctal-decimal-escape: "error"*/ @@ -52,7 +52,7 @@ var quux = "\0\8"; Examples of **correct** code for this rule: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-nonoctal-decimal-escape: "error"*/ diff --git a/docs/src/rules/no-octal-escape.md b/docs/src/rules/no-octal-escape.md index ae8fecdce32c..755bc56bc84f 100644 --- a/docs/src/rules/no-octal-escape.md +++ b/docs/src/rules/no-octal-escape.md @@ -18,7 +18,7 @@ If ESLint parses code in strict mode, the parser (instead of this rule) reports Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-octal-escape: "error"*/ @@ -30,7 +30,7 @@ var foo = "Copyright \251"; Examples of **correct** code for this rule: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-octal-escape: "error"*/ diff --git a/docs/src/rules/no-octal.md b/docs/src/rules/no-octal.md index 6a8fa7709a4d..c40d9a977b23 100644 --- a/docs/src/rules/no-octal.md +++ b/docs/src/rules/no-octal.md @@ -21,7 +21,7 @@ If ESLint parses code in strict mode, the parser (instead of this rule) reports Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-octal: "error"*/ @@ -34,7 +34,7 @@ var result = 5 + 07; Examples of **correct** code for this rule: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-octal: "error"*/ diff --git a/docs/src/rules/no-param-reassign.md b/docs/src/rules/no-param-reassign.md index 9c23c45f981a..a3cc558cf451 100644 --- a/docs/src/rules/no-param-reassign.md +++ b/docs/src/rules/no-param-reassign.md @@ -21,19 +21,19 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-param-reassign: "error"*/ -function foo(bar) { +var foo = function(bar) { bar = 13; } -function foo(bar) { +var foo = function(bar) { bar++; } -function foo(bar) { +var foo = function(bar) { for (bar in baz) {} } -function foo(bar) { +var foo = function(bar) { for (bar of baz) {} } ``` @@ -47,7 +47,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-param-reassign: "error"*/ -function foo(bar) { +var foo = function(bar) { var baz = bar; } ``` @@ -67,23 +67,23 @@ Examples of **correct** code for the default `{ "props": false }` option: ```js /*eslint no-param-reassign: ["error", { "props": false }]*/ -function foo(bar) { +var foo = function(bar) { bar.prop = "value"; } -function foo(bar) { +var foo = function(bar) { delete bar.aaa; } -function foo(bar) { +var foo = function(bar) { bar.aaa++; } -function foo(bar) { +var foo = function(bar) { for (bar.aaa in baz) {} } -function foo(bar) { +var foo = function(bar) { for (bar.aaa of baz) {} } ``` @@ -97,23 +97,23 @@ Examples of **incorrect** code for the `{ "props": true }` option: ```js /*eslint no-param-reassign: ["error", { "props": true }]*/ -function foo(bar) { +var foo = function(bar) { bar.prop = "value"; } -function foo(bar) { +var foo = function(bar) { delete bar.aaa; } -function foo(bar) { +var foo = function(bar) { bar.aaa++; } -function foo(bar) { +var foo = function(bar) { for (bar.aaa in baz) {} } -function foo(bar) { +var foo = function(bar) { for (bar.aaa of baz) {} } ``` @@ -127,23 +127,23 @@ Examples of **correct** code for the `{ "props": true }` option with `"ignorePro ```js /*eslint no-param-reassign: ["error", { "props": true, "ignorePropertyModificationsFor": ["bar"] }]*/ -function foo(bar) { +var foo = function(bar) { bar.prop = "value"; } -function foo(bar) { +var foo = function(bar) { delete bar.aaa; } -function foo(bar) { +var foo = function(bar) { bar.aaa++; } -function foo(bar) { +var foo = function(bar) { for (bar.aaa in baz) {} } -function foo(bar) { +var foo = function(bar) { for (bar.aaa of baz) {} } ``` @@ -157,23 +157,23 @@ Examples of **correct** code for the `{ "props": true }` option with `"ignorePro ```js /*eslint no-param-reassign: ["error", { "props": true, "ignorePropertyModificationsForRegex": ["^bar"] }]*/ -function foo(barVar) { +var foo = function(barVar) { barVar.prop = "value"; } -function foo(barrito) { +var foo = function(barrito) { delete barrito.aaa; } -function foo(bar_) { +var foo = function(bar_) { bar_.aaa++; } -function foo(barBaz) { +var foo = function(barBaz) { for (barBaz.aaa in baz) {} } -function foo(barBaz) { +var foo = function(barBaz) { for (barBaz.aaa of baz) {} } ``` diff --git a/docs/src/rules/no-plusplus.md b/docs/src/rules/no-plusplus.md index ba787f1968fd..4f564b51cb90 100644 --- a/docs/src/rules/no-plusplus.md +++ b/docs/src/rules/no-plusplus.md @@ -43,7 +43,7 @@ var bar = 42; bar--; for (i = 0; i < l; i++) { - return; + doSomething(i); } ``` @@ -63,7 +63,7 @@ var bar = 42; bar -= 1; for (i = 0; i < l; i += 1) { - return; + doSomething(i); } ``` diff --git a/docs/src/rules/no-restricted-syntax.md b/docs/src/rules/no-restricted-syntax.md index 25d419c149d7..a3f51cfef809 100644 --- a/docs/src/rules/no-restricted-syntax.md +++ b/docs/src/rules/no-restricted-syntax.md @@ -57,7 +57,7 @@ The string and object formats can be freely mixed in the configuration as needed Examples of **incorrect** code for this rule with the `"FunctionExpression", "WithStatement", BinaryExpression[operator='in']` options: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /* eslint no-restricted-syntax: ["error", "FunctionExpression", "WithStatement", "BinaryExpression[operator='in']"] */ @@ -75,7 +75,7 @@ foo in bar; Examples of **correct** code for this rule with the `"FunctionExpression", "WithStatement", BinaryExpression[operator='in']` options: -::: correct +::: correct { "sourceType": "script" } ```js /* eslint no-restricted-syntax: ["error", "FunctionExpression", "WithStatement", "BinaryExpression[operator='in']"] */ diff --git a/docs/src/rules/no-return-assign.md b/docs/src/rules/no-return-assign.md index 89277580f98e..b134fe8a1821 100644 --- a/docs/src/rules/no-return-assign.md +++ b/docs/src/rules/no-return-assign.md @@ -43,7 +43,7 @@ function doSomething() { return foo = bar + 2; } -function doSomething() { +function doSomethingElse() { return foo += 2; } @@ -51,7 +51,7 @@ const foo = (a, b) => a = b const bar = (a, b, c) => (a = b, c == b) -function doSomething() { +function doSomethingMore() { return foo = bar && foo > 0; } ``` @@ -69,11 +69,11 @@ function doSomething() { return foo == bar + 2; } -function doSomething() { +function doSomethingElse() { return foo === bar + 2; } -function doSomething() { +function doSomethingMore() { return (foo = bar + 2); } @@ -81,7 +81,7 @@ const foo = (a, b) => (a = b) const bar = (a, b, c) => ((a = b), c == b) -function doSomething() { +function doAnotherThing() { return (foo = bar) && foo > 0; } ``` @@ -104,11 +104,11 @@ function doSomething() { return foo = bar + 2; } -function doSomething() { +function doSomethingElse() { return foo += 2; } -function doSomething() { +function doSomethingMore() { return (foo = bar + 2); } ``` @@ -126,7 +126,7 @@ function doSomething() { return foo == bar + 2; } -function doSomething() { +function doSomethingElse() { return foo === bar + 2; } ``` diff --git a/docs/src/rules/no-return-await.md b/docs/src/rules/no-return-await.md index 80328c09d8d6..2ee5a8b3659c 100644 --- a/docs/src/rules/no-return-await.md +++ b/docs/src/rules/no-return-await.md @@ -38,23 +38,23 @@ Examples of **correct** code for this rule: ```js /*eslint no-return-await: "error"*/ -async function foo() { +async function foo1() { return bar(); } -async function foo() { +async function foo2() { await bar(); return; } // This is essentially the same as `return await bar();`, but the rule checks only `await` in `return` statements -async function foo() { +async function foo3() { const x = await bar(); return x; } // In this example the `await` is necessary to be able to catch errors thrown from `bar()` -async function foo() { +async function foo4() { try { return await bar(); } catch (error) {} diff --git a/docs/src/rules/no-sequences.md b/docs/src/rules/no-sequences.md index cb2b99c50e1f..811dae491968 100644 --- a/docs/src/rules/no-sequences.md +++ b/docs/src/rules/no-sequences.md @@ -25,7 +25,7 @@ This rule forbids the use of the comma operator, with the following exceptions: Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-sequences: "error"*/ @@ -51,7 +51,7 @@ with (doSomething(), val) {} Examples of **correct** code for this rule: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-sequences: "error"*/ @@ -119,7 +119,7 @@ This rule takes one option, an object, with the following properties: Examples of **incorrect** code for this rule with the `{ "allowInParentheses": false }` option: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-sequences: ["error", { "allowInParentheses": false }]*/ diff --git a/docs/src/rules/no-shadow-restricted-names.md b/docs/src/rules/no-shadow-restricted-names.md index 308772edaf36..7a5e1b0403d9 100644 --- a/docs/src/rules/no-shadow-restricted-names.md +++ b/docs/src/rules/no-shadow-restricted-names.md @@ -22,7 +22,7 @@ Then any code used within the same scope would not get the global `undefined`, b Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-shadow-restricted-names: "error"*/ @@ -40,7 +40,7 @@ try {} catch(eval){} Examples of **correct** code for this rule: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-shadow-restricted-names: "error"*/ diff --git a/docs/src/rules/no-shadow.md b/docs/src/rules/no-shadow.md index e4a081f0a309..9b9a27ee7d38 100644 --- a/docs/src/rules/no-shadow.md +++ b/docs/src/rules/no-shadow.md @@ -36,14 +36,14 @@ function b() { var a = 10; } -var b = function () { +var c = function () { var a = 10; } -function b(a) { +function d(a) { a = 10; } -b(a); +d(a); if (true) { let a = 5; diff --git a/docs/src/rules/no-throw-literal.md b/docs/src/rules/no-throw-literal.md index 621bb11a09c3..1ab939ff3a00 100644 --- a/docs/src/rules/no-throw-literal.md +++ b/docs/src/rules/no-throw-literal.md @@ -84,10 +84,10 @@ throw foo("error"); throw new String("error"); -var foo = { +var baz = { bar: "error" }; -throw foo.bar; +throw baz.bar; ``` ::: diff --git a/docs/src/rules/no-undefined.md b/docs/src/rules/no-undefined.md index 9b0be204b2f9..10096533a646 100644 --- a/docs/src/rules/no-undefined.md +++ b/docs/src/rules/no-undefined.md @@ -54,7 +54,7 @@ if (foo === undefined) { // ... } -function foo(undefined) { +function baz(undefined) { // ... } diff --git a/docs/src/rules/no-unsafe-optional-chaining.md b/docs/src/rules/no-unsafe-optional-chaining.md index e4cc6f831664..57ad6de87805 100644 --- a/docs/src/rules/no-unsafe-optional-chaining.md +++ b/docs/src/rules/no-unsafe-optional-chaining.md @@ -32,7 +32,7 @@ This rule aims to detect some cases where the use of optional chaining doesn't p Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-unsafe-optional-chaining: "error"*/ diff --git a/docs/src/rules/no-useless-escape.md b/docs/src/rules/no-useless-escape.md index cd28fda75a8f..ff173c229c2f 100644 --- a/docs/src/rules/no-useless-escape.md +++ b/docs/src/rules/no-useless-escape.md @@ -43,7 +43,7 @@ Examples of **incorrect** code for this rule: Examples of **correct** code for this rule: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-useless-escape: "error"*/ diff --git a/docs/src/rules/no-useless-return.md b/docs/src/rules/no-useless-return.md index 961f0aef5ca0..de7045347c2e 100644 --- a/docs/src/rules/no-useless-return.md +++ b/docs/src/rules/no-useless-return.md @@ -18,14 +18,14 @@ Examples of **incorrect** code for this rule: ```js /* eslint no-useless-return: "error" */ -function foo() { return; } +var foo = function() { return; } -function foo() { +var foo = function() { doSomething(); return; } -function foo() { +var foo = function() { if (condition) { bar(); return; @@ -34,7 +34,7 @@ function foo() { } } -function foo() { +var foo = function() { switch (bar) { case 1: doSomething(); @@ -55,13 +55,13 @@ Examples of **correct** code for this rule: ```js /* eslint no-useless-return: "error" */ -function foo() { return 5; } +var foo = function() { return 5; } -function foo() { +var foo = function() { return doSomething(); } -function foo() { +var foo = function() { if (condition) { bar(); return; @@ -71,7 +71,7 @@ function foo() { qux(); } -function foo() { +var foo = function() { switch (bar) { case 1: doSomething(); @@ -81,7 +81,7 @@ function foo() { } } -function foo() { +var foo = function() { for (const foo of bar) { return; } diff --git a/docs/src/rules/no-with.md b/docs/src/rules/no-with.md index 4fc3f75841d2..c4e880f812d8 100644 --- a/docs/src/rules/no-with.md +++ b/docs/src/rules/no-with.md @@ -17,7 +17,7 @@ If ESLint parses code in strict mode, the parser (instead of this rule) reports Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-with: "error"*/ @@ -31,7 +31,7 @@ with (point) { Examples of **correct** code for this rule: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-with: "error"*/ diff --git a/docs/src/rules/one-var.md b/docs/src/rules/one-var.md index d9a50ed86715..9c41bcd81b21 100644 --- a/docs/src/rules/one-var.md +++ b/docs/src/rules/one-var.md @@ -74,21 +74,21 @@ Examples of **incorrect** code for this rule with the default `"always"` option: ```js /*eslint one-var: ["error", "always"]*/ -function foo() { +function foo1() { var bar; var baz; let qux; let norf; } -function foo(){ +function foo2(){ const bar = false; const baz = true; let qux; let norf; } -function foo() { +function foo3() { var bar; if (baz) { @@ -125,21 +125,21 @@ Examples of **correct** code for this rule with the default `"always"` option: ```js /*eslint one-var: ["error", "always"]*/ -function foo() { +function foo1() { var bar, baz; let qux, norf; } -function foo(){ +function foo2(){ const bar = true, baz = false; let qux, norf; } -function foo() { +function foo3() { var bar, qux; @@ -148,7 +148,7 @@ function foo() { } } -function foo(){ +function foo4(){ let bar; if (baz) { @@ -230,12 +230,12 @@ Examples of **correct** code for this rule with the `"never"` option: ```js /*eslint one-var: ["error", "never"]*/ -function foo() { +function foo1() { var bar; var baz; } -function foo() { +function foo2() { var bar; if (baz) { @@ -243,7 +243,7 @@ function foo() { } } -function foo() { +function foo3() { let bar; if (baz) { @@ -277,12 +277,12 @@ Examples of **incorrect** code for this rule with the `"consecutive"` option: ```js /*eslint one-var: ["error", "consecutive"]*/ -function foo() { +function foo1() { var bar; var baz; } -function foo(){ +function foo2(){ var bar = 1; var baz = 2; @@ -311,12 +311,12 @@ Examples of **correct** code for this rule with the `"consecutive"` option: ```js /*eslint one-var: ["error", "consecutive"]*/ -function foo() { +function foo1() { var bar, baz; } -function foo(){ +function foo2(){ var bar = 1, baz = 2; @@ -349,14 +349,14 @@ Examples of **incorrect** code for this rule with the `{ var: "always", let: "ne /*eslint one-var: ["error", { var: "always", let: "never", const: "never" }]*/ /*eslint-env es6*/ -function foo() { +function foo1() { var bar; var baz; let qux, norf; } -function foo() { +function foo2() { const bar = 1, baz = 2; let qux, @@ -374,14 +374,14 @@ Examples of **correct** code for this rule with the `{ var: "always", let: "neve /*eslint one-var: ["error", { var: "always", let: "never", const: "never" }]*/ /*eslint-env es6*/ -function foo() { +function foo1() { var bar, baz; let qux; let norf; } -function foo() { +function foo2() { const bar = 1; const baz = 2; let qux; @@ -472,7 +472,7 @@ Examples of **incorrect** code for this rule with the `{ var: "never", let: "con /*eslint one-var: ["error", { var: "never", let: "consecutive", const: "consecutive" }]*/ /*eslint-env es6*/ -function foo() { +function foo1() { let a, b; let c; @@ -481,7 +481,7 @@ function foo() { e; } -function foo() { +function foo2() { const a = 1, b = 2; const c = 3; @@ -501,7 +501,7 @@ Examples of **correct** code for this rule with the `{ var: "never", let: "conse /*eslint one-var: ["error", { var: "never", let: "consecutive", const: "consecutive" }]*/ /*eslint-env es6*/ -function foo() { +function foo1() { let a, b; @@ -511,7 +511,7 @@ function foo() { let f; } -function foo() { +function foo2() { const a = 1, b = 2; diff --git a/docs/src/rules/padding-line-between-statements.md b/docs/src/rules/padding-line-between-statements.md index 1b16881c3eee..710a6116d636 100644 --- a/docs/src/rules/padding-line-between-statements.md +++ b/docs/src/rules/padding-line-between-statements.md @@ -120,13 +120,13 @@ Examples of **correct** code for the `[{ blankLine: "always", prev: "*", next: " { blankLine: "always", prev: "*", next: "return" } ]*/ -function foo() { +function foo1() { bar(); return; } -function foo() { +function foo2() { return; } ``` @@ -148,17 +148,17 @@ Examples of **incorrect** code for the `[{ blankLine: "always", prev: ["const", { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]} ]*/ -function foo() { +function foo1() { var a = 0; bar(); } -function foo() { +function foo2() { let a = 0; bar(); } -function foo() { +function foo3() { const a = 0; bar(); } @@ -184,21 +184,21 @@ Examples of **correct** code for the `[{ blankLine: "always", prev: ["const", "l { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]} ]*/ -function foo() { +function foo1() { var a = 0; var b = 0; bar(); } -function foo() { +function foo2() { let a = 0; const b = 0; bar(); } -function foo() { +function foo3() { const a = 0; const b = 0; diff --git a/docs/src/rules/prefer-reflect.md b/docs/src/rules/prefer-reflect.md index e2a632eb6c66..d6e3d87330a1 100644 --- a/docs/src/rules/prefer-reflect.md +++ b/docs/src/rules/prefer-reflect.md @@ -380,7 +380,7 @@ delete foo.bar; // deleting object property Examples of **correct** code for this rule when used without exceptions: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint prefer-reflect: "error"*/ @@ -395,7 +395,7 @@ Note: For a rule preventing deletion of variables, see [no-delete-var instead](n Examples of **correct** code for this rule with the `{ "exceptions": ["delete"] }` option: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint prefer-reflect: ["error", { "exceptions": ["delete"] }]*/ diff --git a/docs/src/rules/prefer-rest-params.md b/docs/src/rules/prefer-rest-params.md index cf234260290d..4406b5468dcd 100644 --- a/docs/src/rules/prefer-rest-params.md +++ b/docs/src/rules/prefer-rest-params.md @@ -19,7 +19,7 @@ This rule is aimed to flag usage of `arguments` variables. Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint prefer-rest-params: "error"*/ @@ -43,7 +43,7 @@ function foo(action) { Examples of **correct** code for this rule: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint prefer-rest-params: "error"*/ diff --git a/docs/src/rules/require-await.md b/docs/src/rules/require-await.md index a17eb972dcad..e0371dfb6ade 100644 --- a/docs/src/rules/require-await.md +++ b/docs/src/rules/require-await.md @@ -63,7 +63,7 @@ bar(async () => { await doSomething(); }); -function foo() { +function baz() { doSomething(); } diff --git a/docs/src/rules/require-jsdoc.md b/docs/src/rules/require-jsdoc.md index 20516fb01883..82ea4b432222 100644 --- a/docs/src/rules/require-jsdoc.md +++ b/docs/src/rules/require-jsdoc.md @@ -77,7 +77,7 @@ function foo() { return 10; } -var foo = () => { +var bar = () => { return 10; }; @@ -87,11 +87,11 @@ class Foo { } } -var foo = function() { +var bar = function() { return 10; }; -var foo = { +var bar = { bar: function() { return 10; }, @@ -131,21 +131,21 @@ function foo() { * @params {int} test - some number * @returns {int} sum of test and 10 */ -var foo = (test) => { +var bar = (test) => { return test + 10; } /** * It returns 10 */ -var foo = () => { +var bar = () => { return 10; } /** * It returns 10 */ -var foo = function() { +var bar = function() { return 10; } @@ -169,11 +169,11 @@ class Foo { /** * It returns 10 */ -var foo = function() { +var bar = function() { return 10; }; -var foo = { +var bar = { /** * It returns 10 */ diff --git a/docs/src/rules/require-yield.md b/docs/src/rules/require-yield.md index e4f975faa3e4..eec9d0cfc941 100644 --- a/docs/src/rules/require-yield.md +++ b/docs/src/rules/require-yield.md @@ -41,12 +41,12 @@ function* foo() { return 10; } -function foo() { +function bar() { return 10; } // This rule does not warn on empty generator functions. -function* foo() { } +function* baz() { } ``` ::: diff --git a/docs/src/rules/space-before-function-paren.md b/docs/src/rules/space-before-function-paren.md index 1948d903cf0e..a132105ac415 100644 --- a/docs/src/rules/space-before-function-paren.md +++ b/docs/src/rules/space-before-function-paren.md @@ -85,13 +85,13 @@ class Foo { } } -var foo = { +var baz = { bar() { // ... } }; -var foo = async() => 1 +var baz = async() => 1 ``` ::: @@ -122,13 +122,13 @@ class Foo { } } -var foo = { +var baz = { bar () { // ... } }; -var foo = async () => 1 +var baz = async () => 1 ``` ::: @@ -161,13 +161,13 @@ class Foo { } } -var foo = { +var baz = { bar () { // ... } }; -var foo = async () => 1 +var baz = async () => 1 ``` ::: @@ -198,13 +198,13 @@ class Foo { } } -var foo = { +var baz = { bar() { // ... } }; -var foo = async() => 1 +var baz = async() => 1 ``` ::: @@ -233,13 +233,13 @@ class Foo { } } -var foo = { +var baz = { bar () { // ... } }; -var foo = async(a) => await a +var baz = async(a) => await a ``` ::: @@ -266,13 +266,13 @@ class Foo { } } -var foo = { +var baz = { bar() { // ... } }; -var foo = async (a) => await a +var baz = async (a) => await a ``` ::: @@ -301,7 +301,7 @@ class Foo { } } -var foo = { +var baz = { bar() { // ... } @@ -332,7 +332,7 @@ class Foo { } } -var foo = { +var baz = { bar () { // ... } @@ -361,7 +361,7 @@ class Foo { } } -var foo = { +var baz = { bar() { // ... } @@ -396,7 +396,7 @@ class Foo { } } -var foo = { +var baz = { bar () { // ... } diff --git a/docs/src/rules/space-before-function-parentheses.md b/docs/src/rules/space-before-function-parentheses.md index a5c0eceb01fa..0b6b9b60d610 100644 --- a/docs/src/rules/space-before-function-parentheses.md +++ b/docs/src/rules/space-before-function-parentheses.md @@ -59,7 +59,7 @@ class Foo { } } -var foo = { +var baz = { bar() { // ... } @@ -93,7 +93,7 @@ class Foo { } } -var foo = { +var baz = { bar () { // ... } @@ -127,7 +127,7 @@ class Foo { } } -var foo = { +var baz = { bar () { // ... } @@ -161,7 +161,7 @@ class Foo { } } -var foo = { +var baz = { bar() { // ... } @@ -191,7 +191,7 @@ class Foo { } } -var foo = { +var baz = { bar () { // ... } @@ -221,7 +221,7 @@ class Foo { } } -var foo = { +var baz = { bar() { // ... } @@ -251,7 +251,7 @@ class Foo { } } -var foo = { +var baz = { bar() { // ... } @@ -281,7 +281,7 @@ class Foo { } } -var foo = { +var baz = { bar () { // ... } diff --git a/docs/src/rules/strict.md b/docs/src/rules/strict.md index c559b00710d4..731ddba2c20c 100644 --- a/docs/src/rules/strict.md +++ b/docs/src/rules/strict.md @@ -82,7 +82,7 @@ Otherwise the `"safe"` option corresponds to the `"function"` option. Note that Examples of **incorrect** code for this rule with the `"global"` option: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint strict: ["error", "global"]*/ @@ -93,7 +93,7 @@ function foo() { ::: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint strict: ["error", "global"]*/ @@ -105,7 +105,7 @@ function foo() { ::: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint strict: ["error", "global"]*/ @@ -121,7 +121,7 @@ function foo() { Examples of **correct** code for this rule with the `"global"` option: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint strict: ["error", "global"]*/ @@ -140,7 +140,7 @@ This option ensures that all function bodies are strict mode code, while global Examples of **incorrect** code for this rule with the `"function"` option: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint strict: ["error", "function"]*/ @@ -153,7 +153,7 @@ function foo() { ::: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint strict: ["error", "function"]*/ @@ -170,7 +170,7 @@ function foo() { ::: -::: incorrect +::: incorrect { "ecmaVersion": 6, "sourceType": "script" } ```js /*eslint strict: ["error", "function"]*/ @@ -192,7 +192,7 @@ function foo(a = 1) { Examples of **correct** code for this rule with the `"function"` option: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint strict: ["error", "function"]*/ @@ -225,7 +225,7 @@ var foo = (function() { Examples of **incorrect** code for this rule with the `"never"` option: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint strict: ["error", "never"]*/ @@ -238,7 +238,7 @@ function foo() { ::: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint strict: ["error", "never"]*/ @@ -252,7 +252,7 @@ function foo() { Examples of **correct** code for this rule with the `"never"` option: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint strict: ["error", "never"]*/ @@ -271,7 +271,7 @@ This option ensures that all functions are executed in strict mode. A strict mod Examples of **incorrect** code for this rule with the earlier default option which has been removed: -::: incorrect +::: incorrect { "sourceType": "script" } ```js // "strict": "error" @@ -282,7 +282,7 @@ function foo() { ::: -::: incorrect +::: incorrect { "sourceType": "script" } ```js // "strict": "error" @@ -298,7 +298,7 @@ function foo() { Examples of **correct** code for this rule with the earlier default option which has been removed: -::: correct +::: correct { "sourceType": "script" } ```js // "strict": "error" @@ -311,7 +311,7 @@ function foo() { ::: -::: correct +::: correct { "sourceType": "script" } ```js // "strict": "error" @@ -323,7 +323,7 @@ function foo() { ::: -::: correct +::: correct { "sourceType": "script" } ```js // "strict": "error" diff --git a/docs/src/rules/vars-on-top.md b/docs/src/rules/vars-on-top.md index 20d1440aee4a..5fdb2f160489 100644 --- a/docs/src/rules/vars-on-top.md +++ b/docs/src/rules/vars-on-top.md @@ -34,7 +34,7 @@ function doSomething() { } // Variable declaration in for initializer: -function doSomething() { +function doSomethingElse() { for (var i=0; i<10; i++) {} } ``` @@ -95,7 +95,7 @@ function doSomething() { } } -function doSomething() { +function doSomethingElse() { var i; for (i=0; i<10; i++) {} } From 0edfe369aa5bd80a98053022bb4c6b1ea0155f44 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 6 Oct 2023 12:48:07 -0400 Subject: [PATCH 201/248] fix: Ensure crash error messages are not duplicated (#17584) * fix: Ensure crash error messages are not duplicated Fixes #17560 * Add test * Fix merge conflicts * Add tests --------- Co-authored-by: Milos Djermanovic --- bin/eslint.js | 19 +++++++++--- tests/bin/eslint.js | 30 +++++++++++++++++++ tests/fixtures/bin/eslint.config-invalid.js | 3 ++ .../fixtures/bin/eslint.config-tick-throws.js | 24 +++++++++++++++ 4 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/bin/eslint.config-invalid.js create mode 100644 tests/fixtures/bin/eslint.config-tick-throws.js diff --git a/bin/eslint.js b/bin/eslint.js index 7094ac77bc4b..5c7972cc086e 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -92,6 +92,14 @@ function getErrorMessage(error) { return util.format("%o", error); } +/** + * Tracks error messages that are shown to the user so we only ever show the + * same message once. + * @type {Set} + */ + +const displayedErrors = new Set(); + /** * Catch and report unexpected error. * @param {any} error The thrown error object. @@ -101,14 +109,17 @@ function onFatalError(error) { process.exitCode = 2; const { version } = require("../package.json"); - const message = getErrorMessage(error); - - console.error(` + const message = ` Oops! Something went wrong! :( ESLint: ${version} -${message}`); +${getErrorMessage(error)}`; + + if (!displayedErrors.has(message)) { + console.error(message); + displayedErrors.add(message); + } } //------------------------------------------------------------------------------ diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js index b09496202b03..dca8955d0386 100644 --- a/tests/bin/eslint.js +++ b/tests/bin/eslint.js @@ -387,6 +387,36 @@ describe("bin/eslint.js", () => { return Promise.all([exitCodeAssertion, outputAssertion]); }); + // https://github.com/eslint/eslint/issues/17560 + describe("does not print duplicate errors in the event of a crash", () => { + + it("when there is an invalid config read from a config file", () => { + const config = path.join(__dirname, "../fixtures/bin/eslint.config-invalid.js"); + const child = runESLint(["--config", config, "conf", "tools"]); + const exitCodeAssertion = assertExitCode(child, 2); + const outputAssertion = getOutput(child).then(output => { + + // The error text should appear exactly once in stderr + assert.strictEqual(output.stderr.match(/A config object is using the "globals" key/gu).length, 1); + }); + + return Promise.all([exitCodeAssertion, outputAssertion]); + }); + + it("when there is an error in the next tick", () => { + const config = path.join(__dirname, "../fixtures/bin/eslint.config-tick-throws.js"); + const child = runESLint(["--config", config, "Makefile.js"]); + const exitCodeAssertion = assertExitCode(child, 2); + const outputAssertion = getOutput(child).then(output => { + + // The error text should appear exactly once in stderr + assert.strictEqual(output.stderr.match(/test_error_stack/gu).length, 1); + }); + + return Promise.all([exitCodeAssertion, outputAssertion]); + }); + }); + it("prints the error message pointing to line of code", () => { const invalidConfig = path.join(__dirname, "../fixtures/bin/eslint.config.js"); const child = runESLint(["--no-ignore", "-c", invalidConfig]); diff --git a/tests/fixtures/bin/eslint.config-invalid.js b/tests/fixtures/bin/eslint.config-invalid.js new file mode 100644 index 000000000000..6f68e1784199 --- /dev/null +++ b/tests/fixtures/bin/eslint.config-invalid.js @@ -0,0 +1,3 @@ +module.exports = [{ + globals: {} +}]; diff --git a/tests/fixtures/bin/eslint.config-tick-throws.js b/tests/fixtures/bin/eslint.config-tick-throws.js new file mode 100644 index 000000000000..c72f86a840f7 --- /dev/null +++ b/tests/fixtures/bin/eslint.config-tick-throws.js @@ -0,0 +1,24 @@ +function throwError() { + const error = new Error(); + error.stack = "test_error_stack"; + throw error; +} + +module.exports = [{ + plugins: { + foo: { + rules: { + bar: { + create() { + process.nextTick(throwError); + process.nextTick(throwError); + return {}; + } + } + } + } + }, + rules: { + "foo/bar": "error" + } +}]; From f976b2f7bfe7cc78bb649f8b37e90fd519ff3bcc Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 6 Oct 2023 19:03:59 +0200 Subject: [PATCH 202/248] fix: make rule severity case-sensitive in flat config (#17619) Fixes #17570 --- lib/config/flat-config-schema.js | 4 +-- tests/lib/config/flat-config-array.js | 11 ++++++++ tests/lib/linter/linter.js | 39 +++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js index a79c02d663b5..df850995d87f 100644 --- a/lib/config/flat-config-schema.js +++ b/lib/config/flat-config-schema.js @@ -179,9 +179,7 @@ class InvalidRuleSeverityError extends Error { * @throws {InvalidRuleSeverityError} If the value isn't a valid rule severity. */ function assertIsRuleSeverity(ruleId, value) { - const severity = typeof value === "string" - ? ruleSeverities.get(value.toLowerCase()) - : ruleSeverities.get(value); + const severity = ruleSeverities.get(value); if (typeof severity === "undefined") { throw new InvalidRuleSeverityError(ruleId, value); diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index 358882374831..728b3e93785c 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -1726,6 +1726,17 @@ describe("FlatConfigArray", () => { ], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2."); }); + it("should error when a string rule severity is not in lowercase", async () => { + + await assertInvalidConfig([ + { + rules: { + foo: "Error" + } + } + ], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2."); + }); + it("should error when an invalid rule severity is set in an array", async () => { await assertInvalidConfig([ diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index 1f366e237bd7..c8fd4134caa6 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -1932,6 +1932,22 @@ describe("Linter", () => { assert.strictEqual(suppressedMessages.length, 0); }); + it("should enable rule configured using a string severity that contains uppercase letters", () => { + const code = "/*eslint no-alert: \"Error\"*/ alert('test');"; + const config = { rules: {} }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual(messages[0].message, "Unexpected alert."); + assert.include(messages[0].nodeType, "CallExpression"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + it("rules should not change initial config", () => { const config = { rules: { strict: 2 } }; const codeA = "/*eslint strict: 0*/ function bar() { return 2; }"; @@ -12353,6 +12369,29 @@ describe("Linter with FlatConfigArray", () => { assert.strictEqual(suppressedMessages.length, 0); }); + it("should report a violation when a rule is configured using a string severity that contains uppercase letters", () => { + const messages = linter.verify("/*eslint no-alert: \"Error\"*/ alert('test');", {}); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual( + messages, + [ + { + severity: 2, + ruleId: "no-alert", + message: "Inline configuration for rule \"no-alert\" is invalid:\n\tExpected severity of \"off\", 0, \"warn\", 1, \"error\", or 2. You passed \"Error\".\n", + line: 1, + column: 1, + endLine: 1, + endColumn: 29, + nodeType: null + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + it("should report a violation when the config violates a rule's schema", () => { const messages = linter.verify("/* eslint no-alert: [error, {nonExistentPropertyName: true}]*/", {}); const suppressedMessages = linter.getSuppressedMessages(); From f8c7403255c11e99c402860aef3c0179f2b16628 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 6 Oct 2023 15:31:35 -0400 Subject: [PATCH 203/248] 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 fc5fb50fcb9a..7e43db7f2e9a 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "8.50.0", + "version": "8.51.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 1ef39ea5b884453be717ebc929155d7eb584dcbf Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 6 Oct 2023 21:55:52 +0200 Subject: [PATCH 204/248] chore: upgrade @eslint/js@8.51.0 (#17624) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 989e3528a479..f891855df88e 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", + "@eslint/js": "8.51.0", "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", From e8235e5366362c86c314f2b2a7a1f30e27d575c6 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 6 Oct 2023 16:14:33 -0400 Subject: [PATCH 205/248] Build: changelog update for 8.51.0 --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index efb9cc93035a..61aa641c59f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +v8.51.0 - October 6, 2023 + +* [`1ef39ea`](https://github.com/eslint/eslint/commit/1ef39ea5b884453be717ebc929155d7eb584dcbf) chore: upgrade @eslint/js@8.51.0 (#17624) (Milos Djermanovic) +* [`f8c7403`](https://github.com/eslint/eslint/commit/f8c7403255c11e99c402860aef3c0179f2b16628) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`f976b2f`](https://github.com/eslint/eslint/commit/f976b2f7bfe7cc78bb649f8b37e90fd519ff3bcc) fix: make rule severity case-sensitive in flat config (#17619) (Milos Djermanovic) +* [`0edfe36`](https://github.com/eslint/eslint/commit/0edfe369aa5bd80a98053022bb4c6b1ea0155f44) fix: Ensure crash error messages are not duplicated (#17584) (Nicholas C. Zakas) +* [`ee5be81`](https://github.com/eslint/eslint/commit/ee5be81fa3c4fe801c2f653854f098ed6a84dcef) docs: default to `sourceType: "module"` in rule examples (#17615) (Francesco Trotta) +* [`dd79abc`](https://github.com/eslint/eslint/commit/dd79abc0c1857b1d765acc312c0d6518e40d31c9) fix: `eslint-disable` to be able to parse quoted rule names (#17612) (Yosuke Ota) +* [`d2f6801`](https://github.com/eslint/eslint/commit/d2f68019b8882278877801c5ef2f74d55e2a10c1) fix: Ensure correct code path for && followed by ?? (#17618) (Nicholas C. Zakas) +* [`2665552`](https://github.com/eslint/eslint/commit/2665552ba0057e8603f9fbece0fd236f189f5cf3) test: fix flat config linter tests to use Linter in flat config mode (#17616) (Milos Djermanovic) +* [`1aa26df`](https://github.com/eslint/eslint/commit/1aa26df9fbcfdf5b895743c6d2d3a216479544b1) docs: Add more examples for multiline-ternary (#17610) (George Ashiotis) +* [`47d0b44`](https://github.com/eslint/eslint/commit/47d0b446964f44d70b9457ecc368e721e1dc7c11) docs: Update README (GitHub Actions Bot) +* [`dbf831e`](https://github.com/eslint/eslint/commit/dbf831e31f8eea0bc94df96cd33255579324b66e) docs: use generated og image (#17601) (Percy Ma) +* [`0a9c433`](https://github.com/eslint/eslint/commit/0a9c43339a4adef24ef83034d0b078dd279cc977) feat: Add `--no-warn-ignored` CLI option for flat config (#17569) (Domantas Petrauskas) +* [`1866da5`](https://github.com/eslint/eslint/commit/1866da5e1d931787256ecb825a803cac5835b71c) docs: Update README (GitHub Actions Bot) +* [`7b77bcc`](https://github.com/eslint/eslint/commit/7b77bccbb51bd36b2d20fea61bc782545c4029b3) chore: Refactor CodePathState (#17510) (Nicholas C. Zakas) +* [`977e67e`](https://github.com/eslint/eslint/commit/977e67ec274a05cb7391665b5e3453e7f72f72b2) feat: logical-assignment-operators to report expressions with 3 operands (#17600) (Yosuke Ota) +* [`bc77c9a`](https://github.com/eslint/eslint/commit/bc77c9af12539f350ef19e30611a153a5b869c6b) chore: Document and refactor ForkContext (#17566) (Nicholas C. Zakas) +* [`24e1f14`](https://github.com/eslint/eslint/commit/24e1f140ec68659e55c1ace0d7500addb135a2b4) chore: Refactor and document CodePath (#17558) (Nicholas C. Zakas) + v8.50.0 - September 22, 2023 * [`f8a8a2d`](https://github.com/eslint/eslint/commit/f8a8a2d6b45c82f94a574623759b6e3d2af193f3) chore: upgrade @eslint/js@8.50.0 (#17599) (Milos Djermanovic) From f0793828fdba1f17fda78abb392f7944ab751185 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 6 Oct 2023 16:14:34 -0400 Subject: [PATCH 206/248] 8.51.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 120aa91c60c3..1b2019bb4cb6 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.50.0", + "version": "8.51.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 9545c0914c4b..2981958fab76 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

- 9 problems (5 errors, 4 warnings) - Generated on Fri Sep 22 2023 17:03:30 GMT-0400 (Eastern Daylight Time) + 9 problems (5 errors, 4 warnings) - Generated on Fri Oct 06 2023 16:14:35 GMT-0400 (Eastern Daylight Time)
diff --git a/package.json b/package.json index f891855df88e..ece3957419ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.50.0", + "version": "8.51.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 000290c4c923cc1473e21b4bdbdc0c42765ef7dd Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Sun, 8 Oct 2023 08:06:18 +0000 Subject: [PATCH 207/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7aa4019ed87a..4aeefc08cd2e 100644 --- a/README.md +++ b/README.md @@ -288,7 +288,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

-

Liftoff Siemens American Express

Bronze Sponsors

+

Liftoff American Express

Bronze Sponsors

ThemeIsle Anagram Solver Icons8 Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

From 1000187e00949332babcee4d37d46c96a6a554a8 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Sun, 8 Oct 2023 15:39:25 +0200 Subject: [PATCH 208/248] docs: Fix examples in `unicode-bom` (#17631) Fix examples in `unicode-bom` docs --- docs/src/rules/unicode-bom.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/src/rules/unicode-bom.md b/docs/src/rules/unicode-bom.md index 1bae51ce64e1..6fcde6c3304e 100644 --- a/docs/src/rules/unicode-bom.md +++ b/docs/src/rules/unicode-bom.md @@ -31,9 +31,10 @@ Example of **correct** code for this rule with the `"always"` option: ::: correct ```js +๏ปฟ// U+FEFF at the beginning + /*eslint unicode-bom: ["error", "always"]*/ -U+FEFF var abc; ``` @@ -70,9 +71,10 @@ Example of **incorrect** code for this rule with the `"never"` option: ::: incorrect ```js +๏ปฟ// U+FEFF at the beginning + /*eslint unicode-bom: ["error", "never"]*/ -U+FEFF var abc; ``` From 6b8acfb770589f3941df41c3910d3b8ffc3e1e45 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Mon, 9 Oct 2023 09:15:58 +0200 Subject: [PATCH 209/248] docs: Add real whitespace to `no-trailing-spaces` examples (#17630) * Add real whitespace to `no-trailing-spaces` examples * Fix space-only line --- docs/src/rules/no-trailing-spaces.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/src/rules/no-trailing-spaces.md b/docs/src/rules/no-trailing-spaces.md index fba4f7df9a21..61aec1fcf4aa 100644 --- a/docs/src/rules/no-trailing-spaces.md +++ b/docs/src/rules/no-trailing-spaces.md @@ -18,9 +18,9 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-trailing-spaces: "error"*/ -var foo = 0;//โ€ขโ€ขโ€ขโ€ขโ€ข -var baz = 5;//โ€ขโ€ข -//โ€ขโ€ขโ€ขโ€ขโ€ข +var foo = 0;/* trailing whitespace */ +var baz = 5;/* trailing whitespace */ +/* trailing whitespace */ ``` ::: @@ -58,7 +58,8 @@ Examples of **correct** code for this rule with the `{ "skipBlankLines": true }` var foo = 0; var baz = 5; -//โ€ขโ€ขโ€ขโ€ขโ€ข +// โ†“ a line with whitespace only โ†“ + ``` ::: @@ -72,12 +73,12 @@ Examples of **correct** code for this rule with the `{ "ignoreComments": true }` ```js /*eslint no-trailing-spaces: ["error", { "ignoreComments": true }]*/ -//fooโ€ข -//โ€ขโ€ขโ€ขโ€ขโ€ข +// โ†“ these comments have trailing whitespace โ†’ +// /** - *โ€ขbaz - *โ€ขโ€ข - *โ€ขbar + * baz + * + * bar */ ``` From 392305bf4797e3ebc696dfca48bd874741fca845 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Mon, 9 Oct 2023 10:55:39 +0200 Subject: [PATCH 210/248] docs: Update `no-irregular-whitespace` and fix examples (#17626) Update `no-irregular-whitespace` docs --- docs/src/_data/further_reading_links.json | 7 +++++++ docs/src/rules/no-irregular-whitespace.md | 19 +++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/src/_data/further_reading_links.json b/docs/src/_data/further_reading_links.json index 045bd4faf628..a33ce6c0b3bb 100644 --- a/docs/src/_data/further_reading_links.json +++ b/docs/src/_data/further_reading_links.json @@ -740,5 +740,12 @@ "logo": "https://v8.dev/favicon.ico", "title": "RegExp v flag with set notation and properties of strings ยท V8", "description": "The new RegExp `v` flag enables `unicodeSets` mode, unlocking support for extended character classes, including Unicode properties of strings, set notation, and improved case-insensitive matching." + }, + "https://codepoints.net/U+1680": { + "domain": "codepoints.net", + "url": "https://codepoints.net/U+1680", + "logo": "https://codepoints.net/favicon.ico", + "title": "U+1680 OGHAM SPACE MARK: แš€ โ€“ Unicode โ€“ Codepoints", + "description": "แš€, codepoint U+1680 OGHAM SPACE MARK in Unicode, is located in the block โ€œOghamโ€. It belongs to the Ogham script and is a Space Separator." } } \ No newline at end of file diff --git a/docs/src/rules/no-irregular-whitespace.md b/docs/src/rules/no-irregular-whitespace.md index c0683645628e..4118defe6311 100644 --- a/docs/src/rules/no-irregular-whitespace.md +++ b/docs/src/rules/no-irregular-whitespace.md @@ -4,6 +4,7 @@ rule_type: problem further_reading: - https://es5.github.io/#x7.2 - https://web.archive.org/web/20200414142829/http://timelessrepo.com/json-isnt-a-javascript-subset +- https://codepoints.net/U+1680 --- @@ -16,11 +17,17 @@ A simple fix for this problem could be to rewrite the offending line from scratc Known issues these spaces cause: +* Ogham Space Mark + * Is a valid token separator, but is rendered as a visible glyph in most typefaces, which may be misleading in source code. +* Mongolian Vowel Separator + * Is no longer considered a space separator since Unicode 6.3. It will result in a syntax error in current parsers when used in place of a regular token separator. +* Line Separator and Paragraph Separator + * These have always been valid whitespace characters and line terminators, but were considered illegal in string literals prior to ECMAScript 2019. * Zero Width Space - * Is NOT considered a separator for tokens and is often parsed as an `Unexpected token ILLEGAL` - * Is NOT shown in modern browsers making code repository software expected to resolve the visualization -* Line Separator - * Is NOT a valid character within JSON which would cause parse errors + * Is NOT considered a separator for tokens and is often parsed as an `Unexpected token ILLEGAL`. + * Is NOT shown in modern browsers making code repository software expected to resolve the visualization. + +In JSON, none of the characters listed as irregular whitespace by this rule may appear outside of a string. ## Rule Details @@ -86,7 +93,7 @@ var thing = functionย /**/(){ return 'test'; } -var thing = functionแ Ž/**/(){ +var thing = functionแš€/**/(){ return 'test'; } @@ -204,7 +211,7 @@ Examples of additional **correct** code for this rule with the `{ "skipJSXText": /*eslint-env es6*/ function Thing() { - return
text inโ€‚JSX
; + return
text inโ€‚JSX
; // before `JSX` } ``` From fab249ae6addac2ee18cd81cee80916010bb469e Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Tue, 10 Oct 2023 08:07:17 +0000 Subject: [PATCH 211/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4aeefc08cd2e..dc500d03d6ae 100644 --- a/README.md +++ b/README.md @@ -289,7 +289,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Liftoff American Express

Bronze Sponsors

-

ThemeIsle Anagram Solver Icons8 Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

+

bulkfollow ThemeIsle Anagram Solver Icons8 Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

## Technology Sponsors From ff8e4bf327b5c92b0623b0fc5f8f101954f785db Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Wed, 11 Oct 2023 08:07:17 +0000 Subject: [PATCH 212/248] docs: Update README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index dc500d03d6ae..97f165daaa6a 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,11 @@ Francesco Trotta
Yosuke Ota +
+ +
+Tanuj Kanti +
### Website Team From 9fafe450c31ed9b6bdd9dcd6c115255943b8c1c2 Mon Sep 17 00:00:00 2001 From: Percy Ma Date: Wed, 11 Oct 2023 07:04:32 -0500 Subject: [PATCH 213/248] docs: upgrade to 11ty 2.0 (#17632) * docs: upgrade to 11ty 2.0 * docs: fix broken links * chore: clean * Update Makefile.js Co-authored-by: Milos Djermanovic * chore: change filename --------- Co-authored-by: Milos Djermanovic --- Makefile.js | 16 +- conf/rule-type-list.json | 58 +- docs/.eleventy.js | 22 +- docs/package.json | 8 +- docs/src/_data/rules.json | 4057 ++++++++++++++-------------- docs/src/_data/rules_categories.js | 26 + docs/src/pages/rules.md | 25 +- 7 files changed, 2093 insertions(+), 2119 deletions(-) create mode 100644 docs/src/_data/rules_categories.js diff --git a/Makefile.js b/Makefile.js index 7978369c0b05..8a963388ebea 100644 --- a/Makefile.js +++ b/Makefile.js @@ -214,7 +214,7 @@ function generateRuleIndexPage() { }; if (rule.meta.deprecated) { - ruleTypesData.deprecated.rules.push({ + ruleTypesData.deprecated.push({ name: basename, replacedBy: rule.meta.replacedBy || [] }); @@ -226,22 +226,18 @@ function generateRuleIndexPage() { fixable: !!rule.meta.fixable, hasSuggestions: !!rule.meta.hasSuggestions }, - ruleType = ruleTypesData.types.find(c => c.name === rule.meta.type); + ruleType = ruleTypesData.types[rule.meta.type]; - if (!ruleType.rules) { - ruleType.rules = []; - } - - ruleType.rules.push(output); + ruleType.push(output); } }); - // `.rules` will be `undefined` if all rules in category are deprecated. - ruleTypesData.types = ruleTypesData.types.filter(ruleType => !!ruleType.rules); + ruleTypesData.types = Object.fromEntries( + Object.entries(ruleTypesData.types).filter(([, value]) => value && value.length > 0) + ); JSON.stringify(ruleTypesData, null, 4).to(docsSiteOutputFile); JSON.stringify(meta, null, 4).to(docsSiteMetaOutputFile); - } /** diff --git a/conf/rule-type-list.json b/conf/rule-type-list.json index d5823acc898e..6ca730f34f02 100644 --- a/conf/rule-type-list.json +++ b/conf/rule-type-list.json @@ -1,36 +1,28 @@ { - "types": [ - { "name": "problem", "displayName": "Possible Problems", "description": "These rules relate to possible logic errors in code:" }, - { "name": "suggestion", "displayName": "Suggestions", "description": "These rules suggest alternate ways of doing things:" }, - { "name": "layout", "displayName": "Layout & Formatting", "description": "These rules care about how the code looks rather than how it executes:" } - ], - "deprecated": { - "name": "Deprecated", - "description": "These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:", - "rules": [] + "types": { + "problem": [], + "suggestion": [], + "layout": [] }, - "removed": { - "name": "Removed", - "description": "These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:", - "rules": [ - { "removed": "generator-star", "replacedBy": ["generator-star-spacing"] }, - { "removed": "global-strict", "replacedBy": ["strict"] }, - { "removed": "no-arrow-condition", "replacedBy": ["no-confusing-arrow", "no-constant-condition"] }, - { "removed": "no-comma-dangle", "replacedBy": ["comma-dangle"] }, - { "removed": "no-empty-class", "replacedBy": ["no-empty-character-class"] }, - { "removed": "no-empty-label", "replacedBy": ["no-labels"] }, - { "removed": "no-extra-strict", "replacedBy": ["strict"] }, - { "removed": "no-reserved-keys", "replacedBy": ["quote-props"] }, - { "removed": "no-space-before-semi", "replacedBy": ["semi-spacing"] }, - { "removed": "no-wrap-func", "replacedBy": ["no-extra-parens"] }, - { "removed": "space-after-function-name", "replacedBy": ["space-before-function-paren"] }, - { "removed": "space-after-keywords", "replacedBy": ["keyword-spacing"] }, - { "removed": "space-before-function-parentheses", "replacedBy": ["space-before-function-paren"] }, - { "removed": "space-before-keywords", "replacedBy": ["keyword-spacing"] }, - { "removed": "space-in-brackets", "replacedBy": ["object-curly-spacing", "array-bracket-spacing"] }, - { "removed": "space-return-throw-case", "replacedBy": ["keyword-spacing"] }, - { "removed": "space-unary-word-ops", "replacedBy": ["space-unary-ops"] }, - { "removed": "spaced-line-comment", "replacedBy": ["spaced-comment"] } - ] - } + "deprecated": [], + "removed": [ + { "removed": "generator-star", "replacedBy": ["generator-star-spacing"] }, + { "removed": "global-strict", "replacedBy": ["strict"] }, + { "removed": "no-arrow-condition", "replacedBy": ["no-confusing-arrow", "no-constant-condition"] }, + { "removed": "no-comma-dangle", "replacedBy": ["comma-dangle"] }, + { "removed": "no-empty-class", "replacedBy": ["no-empty-character-class"] }, + { "removed": "no-empty-label", "replacedBy": ["no-labels"] }, + { "removed": "no-extra-strict", "replacedBy": ["strict"] }, + { "removed": "no-reserved-keys", "replacedBy": ["quote-props"] }, + { "removed": "no-space-before-semi", "replacedBy": ["semi-spacing"] }, + { "removed": "no-wrap-func", "replacedBy": ["no-extra-parens"] }, + { "removed": "space-after-function-name", "replacedBy": ["space-before-function-paren"] }, + { "removed": "space-after-keywords", "replacedBy": ["keyword-spacing"] }, + { "removed": "space-before-function-parentheses", "replacedBy": ["space-before-function-paren"] }, + { "removed": "space-before-keywords", "replacedBy": ["keyword-spacing"] }, + { "removed": "space-in-brackets", "replacedBy": ["object-curly-spacing", "array-bracket-spacing"] }, + { "removed": "space-return-throw-case", "replacedBy": ["keyword-spacing"] }, + { "removed": "space-unary-word-ops", "replacedBy": ["space-unary-ops"] }, + { "removed": "spaced-line-comment", "replacedBy": ["spaced-comment"] } + ] } diff --git a/docs/.eleventy.js b/docs/.eleventy.js index 68c6604cf8d0..a2a0b71c17bc 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -54,6 +54,7 @@ module.exports = function(eleventyConfig) { eleventyConfig.addGlobalData("GIT_BRANCH", process.env.BRANCH); eleventyConfig.addGlobalData("HEAD", process.env.BRANCH === "main"); eleventyConfig.addGlobalData("NOINDEX", process.env.BRANCH !== "latest"); + eleventyConfig.addGlobalData("PATH_PREFIX", pathPrefix); eleventyConfig.addDataExtension("yml", contents => yaml.load(contents)); //------------------------------------------------------------------------------ @@ -487,25 +488,6 @@ module.exports = function(eleventyConfig) { // Settings //------------------------------------------------------------------------------ - /* - * When we run `eleventy --serve`, Eleventy 1.x uses browser-sync to serve the content. - * By default, browser-sync (more precisely, underlying serve-static) will not serve - * `foo/bar.html` when we request `foo/bar`. Thus, we need to rewrite URLs to append `.html` - * so that pretty links without `.html` can work in a local development environment. - * - * There's no need to rewrite URLs that end with `/`, because that already works well - * (server will return the content of `index.html` in the directory). - * URLs with a file extension, like main.css, main.js, sitemap.xml, etc. should not be rewritten - */ - eleventyConfig.setBrowserSyncConfig({ - middleware(req, res, next) { - if (!/(?:\.[a-zA-Z][^/]*|\/)$/u.test(req.url)) { - req.url += ".html"; - } - return next(); - } - }); - /* * Generate the sitemap only in certain contexts to prevent unwanted discovery of sitemaps that * contain URLs we'd prefer not to appear in search results (URLs in sitemaps are considered important). @@ -525,14 +507,12 @@ module.exports = function(eleventyConfig) { eleventyConfig.ignores.add("src/static/sitemap.njk"); // ... then don't generate the sitemap. } - return { passthroughFileCopy: true, pathPrefix, markdownTemplateEngine: "njk", - dataTemplateEngine: "njk", htmlTemplateEngine: "njk", dir: { diff --git a/docs/package.json b/docs/package.json index 1b2019bb4cb6..d24e203338c3 100644 --- a/docs/package.json +++ b/docs/package.json @@ -23,11 +23,11 @@ "start": "npm-run-all build:sass build:postcss --parallel *:*:watch" }, "devDependencies": { - "@11ty/eleventy": "^1.0.1", - "@11ty/eleventy-img": "^1.0.0", - "@11ty/eleventy-navigation": "^0.3.2", + "@11ty/eleventy": "^2.0.1", + "@11ty/eleventy-img": "^3.1.1", + "@11ty/eleventy-navigation": "^0.3.5", "@11ty/eleventy-plugin-rss": "^1.1.1", - "@11ty/eleventy-plugin-syntaxhighlight": "^3.1.2", + "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0", "@munter/tap-render": "^0.2.0", "@types/markdown-it": "^12.2.3", "algoliasearch": "^4.12.1", diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 2ac3df8c535c..3565b9ca65e7 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -1,2129 +1,2106 @@ { - "types": [ - { - "name": "problem", - "displayName": "Possible Problems", - "description": "These rules relate to possible logic errors in code:", - "rules": [ - { - "name": "array-callback-return", - "description": "Enforce `return` statements in callbacks of array methods", - "recommended": false, - "fixable": false, - "hasSuggestions": true - }, - { - "name": "constructor-super", - "description": "Require `super()` calls in constructors", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "for-direction", - "description": "Enforce \"for\" loop update clause moving the counter in the right direction", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "getter-return", - "description": "Enforce `return` statements in getters", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-async-promise-executor", - "description": "Disallow using an async function as a Promise executor", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-await-in-loop", - "description": "Disallow `await` inside of loops", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-class-assign", - "description": "Disallow reassigning class members", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-compare-neg-zero", - "description": "Disallow comparing against -0", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-cond-assign", - "description": "Disallow assignment operators in conditional expressions", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-const-assign", - "description": "Disallow reassigning `const` variables", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-constant-binary-expression", - "description": "Disallow expressions where the operation doesn't affect the value", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-constant-condition", - "description": "Disallow constant expressions in conditions", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-constructor-return", - "description": "Disallow returning value from constructor", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-control-regex", - "description": "Disallow control characters in regular expressions", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-debugger", - "description": "Disallow the use of `debugger`", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-dupe-args", - "description": "Disallow duplicate arguments in `function` definitions", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-dupe-class-members", - "description": "Disallow duplicate class members", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-dupe-else-if", - "description": "Disallow duplicate conditions in if-else-if chains", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-dupe-keys", - "description": "Disallow duplicate keys in object literals", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-duplicate-case", - "description": "Disallow duplicate case labels", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-duplicate-imports", - "description": "Disallow duplicate module imports", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-empty-character-class", - "description": "Disallow empty character classes in regular expressions", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-empty-pattern", - "description": "Disallow empty destructuring patterns", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-ex-assign", - "description": "Disallow reassigning exceptions in `catch` clauses", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-fallthrough", - "description": "Disallow fallthrough of `case` statements", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-func-assign", - "description": "Disallow reassigning `function` declarations", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-import-assign", - "description": "Disallow assigning to imported bindings", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-inner-declarations", - "description": "Disallow variable or `function` declarations in nested blocks", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-invalid-regexp", - "description": "Disallow invalid regular expression strings in `RegExp` constructors", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-irregular-whitespace", - "description": "Disallow irregular whitespace", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-loss-of-precision", - "description": "Disallow literal numbers that lose precision", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-misleading-character-class", - "description": "Disallow characters which are made with multiple code points in character class syntax", - "recommended": true, - "fixable": false, - "hasSuggestions": true - }, - { - "name": "no-new-native-nonconstructor", - "description": "Disallow `new` operators with global non-constructor functions", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-new-symbol", - "description": "Disallow `new` operators with the `Symbol` object", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-obj-calls", - "description": "Disallow calling global object properties as functions", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-promise-executor-return", - "description": "Disallow returning values from Promise executor functions", - "recommended": false, - "fixable": false, - "hasSuggestions": true - }, - { - "name": "no-prototype-builtins", - "description": "Disallow calling some `Object.prototype` methods directly on objects", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-self-assign", - "description": "Disallow assignments where both sides are exactly the same", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-self-compare", - "description": "Disallow comparisons where both sides are exactly the same", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-setter-return", - "description": "Disallow returning values from setters", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-sparse-arrays", - "description": "Disallow sparse arrays", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-template-curly-in-string", - "description": "Disallow template literal placeholder syntax in regular strings", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-this-before-super", - "description": "Disallow `this`/`super` before calling `super()` in constructors", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-undef", - "description": "Disallow the use of undeclared variables unless mentioned in `/*global */` comments", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-unexpected-multiline", - "description": "Disallow confusing multiline expressions", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-unmodified-loop-condition", - "description": "Disallow unmodified loop conditions", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-unreachable", - "description": "Disallow unreachable code after `return`, `throw`, `continue`, and `break` statements", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-unreachable-loop", - "description": "Disallow loops with a body that allows only one iteration", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-unsafe-finally", - "description": "Disallow control flow statements in `finally` blocks", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-unsafe-negation", - "description": "Disallow negating the left operand of relational operators", - "recommended": true, - "fixable": false, - "hasSuggestions": true - }, - { - "name": "no-unsafe-optional-chaining", - "description": "Disallow use of optional chaining in contexts where the `undefined` value is not allowed", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-unused-private-class-members", - "description": "Disallow unused private class members", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-unused-vars", - "description": "Disallow unused variables", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-use-before-define", - "description": "Disallow the use of variables before they are defined", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-useless-backreference", - "description": "Disallow useless backreferences in regular expressions", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "require-atomic-updates", - "description": "Disallow assignments that can lead to race conditions due to usage of `await` or `yield`", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "use-isnan", - "description": "Require calls to `isNaN()` when checking for `NaN`", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "valid-typeof", - "description": "Enforce comparing `typeof` expressions against valid strings", - "recommended": true, - "fixable": false, - "hasSuggestions": true - } - ] - }, - { - "name": "suggestion", - "displayName": "Suggestions", - "description": "These rules suggest alternate ways of doing things:", - "rules": [ - { - "name": "accessor-pairs", - "description": "Enforce getter and setter pairs in objects and classes", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "arrow-body-style", - "description": "Require braces around arrow function bodies", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "block-scoped-var", - "description": "Enforce the use of variables within the scope they are defined", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "camelcase", - "description": "Enforce camelcase naming convention", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "capitalized-comments", - "description": "Enforce or disallow capitalization of the first letter of a comment", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "class-methods-use-this", - "description": "Enforce that class methods utilize `this`", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "complexity", - "description": "Enforce a maximum cyclomatic complexity allowed in a program", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "consistent-return", - "description": "Require `return` statements to either always or never specify values", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "consistent-this", - "description": "Enforce consistent naming when capturing the current execution context", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "curly", - "description": "Enforce consistent brace style for all control statements", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "default-case", - "description": "Require `default` cases in `switch` statements", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "default-case-last", - "description": "Enforce default clauses in switch statements to be last", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "default-param-last", - "description": "Enforce default parameters to be last", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "dot-notation", - "description": "Enforce dot notation whenever possible", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "eqeqeq", - "description": "Require the use of `===` and `!==`", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "func-name-matching", - "description": "Require function names to match the name of the variable or property to which they are assigned", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "func-names", - "description": "Require or disallow named `function` expressions", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "func-style", - "description": "Enforce the consistent use of either `function` declarations or expressions", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "grouped-accessor-pairs", - "description": "Require grouped accessor pairs in object literals and classes", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "guard-for-in", - "description": "Require `for-in` loops to include an `if` statement", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "id-denylist", - "description": "Disallow specified identifiers", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "id-length", - "description": "Enforce minimum and maximum identifier lengths", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "id-match", - "description": "Require identifiers to match a specified regular expression", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "init-declarations", - "description": "Require or disallow initialization in variable declarations", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "logical-assignment-operators", - "description": "Require or disallow logical assignment operator shorthand", - "recommended": false, - "fixable": true, - "hasSuggestions": true - }, - { - "name": "max-classes-per-file", - "description": "Enforce a maximum number of classes per file", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "max-depth", - "description": "Enforce a maximum depth that blocks can be nested", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "max-lines", - "description": "Enforce a maximum number of lines per file", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "max-lines-per-function", - "description": "Enforce a maximum number of lines of code in a function", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "max-nested-callbacks", - "description": "Enforce a maximum depth that callbacks can be nested", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "max-params", - "description": "Enforce a maximum number of parameters in function definitions", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "max-statements", - "description": "Enforce a maximum number of statements allowed in function blocks", - "recommended": false, - "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", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-alert", - "description": "Disallow the use of `alert`, `confirm`, and `prompt`", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-array-constructor", - "description": "Disallow `Array` constructors", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-bitwise", - "description": "Disallow bitwise operators", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-caller", - "description": "Disallow the use of `arguments.caller` or `arguments.callee`", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-case-declarations", - "description": "Disallow lexical declarations in case clauses", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-confusing-arrow", - "description": "Disallow arrow functions where they could be confused with comparisons", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-console", - "description": "Disallow the use of `console`", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-continue", - "description": "Disallow `continue` statements", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-delete-var", - "description": "Disallow deleting variables", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-div-regex", - "description": "Disallow equal signs explicitly at the beginning of regular expressions", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-else-return", - "description": "Disallow `else` blocks after `return` statements in `if` statements", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-empty", - "description": "Disallow empty block statements", - "recommended": true, - "fixable": false, - "hasSuggestions": true - }, - { - "name": "no-empty-function", - "description": "Disallow empty functions", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-empty-static-block", - "description": "Disallow empty static blocks", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-eq-null", - "description": "Disallow `null` comparisons without type-checking operators", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-eval", - "description": "Disallow the use of `eval()`", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-extend-native", - "description": "Disallow extending native types", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-extra-bind", - "description": "Disallow unnecessary calls to `.bind()`", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-extra-boolean-cast", - "description": "Disallow unnecessary boolean casts", - "recommended": true, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-extra-label", - "description": "Disallow unnecessary labels", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-extra-semi", - "description": "Disallow unnecessary semicolons", - "recommended": true, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-floating-decimal", - "description": "Disallow leading or trailing decimal points in numeric literals", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-global-assign", - "description": "Disallow assignments to native objects or read-only global variables", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-implicit-coercion", - "description": "Disallow shorthand type conversions", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-implicit-globals", - "description": "Disallow declarations in the global scope", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-implied-eval", - "description": "Disallow the use of `eval()`-like methods", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-inline-comments", - "description": "Disallow inline comments after code", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-invalid-this", - "description": "Disallow use of `this` in contexts where the value of `this` is `undefined`", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-iterator", - "description": "Disallow the use of the `__iterator__` property", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-label-var", - "description": "Disallow labels that share a name with a variable", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-labels", - "description": "Disallow labeled statements", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-lone-blocks", - "description": "Disallow unnecessary nested blocks", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-lonely-if", - "description": "Disallow `if` statements as the only statement in `else` blocks", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-loop-func", - "description": "Disallow function declarations that contain unsafe references inside loop statements", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-magic-numbers", - "description": "Disallow magic numbers", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-mixed-operators", - "description": "Disallow mixed binary operators", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-multi-assign", - "description": "Disallow use of chained assignment expressions", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-multi-str", - "description": "Disallow multiline strings", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-negated-condition", - "description": "Disallow negated conditions", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-nested-ternary", - "description": "Disallow nested ternary expressions", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-new", - "description": "Disallow `new` operators outside of assignments or comparisons", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-new-func", - "description": "Disallow `new` operators with the `Function` object", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-new-wrappers", - "description": "Disallow `new` operators with the `String`, `Number`, and `Boolean` objects", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-nonoctal-decimal-escape", - "description": "Disallow `\\8` and `\\9` escape sequences in string literals", - "recommended": true, - "fixable": false, - "hasSuggestions": true - }, - { - "name": "no-object-constructor", - "description": "Disallow calls to the `Object` constructor without an argument", - "recommended": false, - "fixable": false, - "hasSuggestions": true - }, - { - "name": "no-octal", - "description": "Disallow octal literals", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-octal-escape", - "description": "Disallow octal escape sequences in string literals", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-param-reassign", - "description": "Disallow reassigning `function` parameters", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-plusplus", - "description": "Disallow the unary operators `++` and `--`", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-proto", - "description": "Disallow the use of the `__proto__` property", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-redeclare", - "description": "Disallow variable redeclaration", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-regex-spaces", - "description": "Disallow multiple spaces in regular expressions", - "recommended": true, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-restricted-exports", - "description": "Disallow specified names in exports", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-restricted-globals", - "description": "Disallow specified global variables", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-restricted-imports", - "description": "Disallow specified modules when loaded by `import`", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-restricted-properties", - "description": "Disallow certain properties on certain objects", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-restricted-syntax", - "description": "Disallow specified syntax", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-return-assign", - "description": "Disallow assignment operators in `return` statements", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-script-url", - "description": "Disallow `javascript:` urls", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-sequences", - "description": "Disallow comma operators", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-shadow", - "description": "Disallow variable declarations from shadowing variables declared in the outer scope", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-shadow-restricted-names", - "description": "Disallow identifiers from shadowing restricted names", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-ternary", - "description": "Disallow ternary operators", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-throw-literal", - "description": "Disallow throwing literals as exceptions", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-undef-init", - "description": "Disallow initializing variables to `undefined`", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-undefined", - "description": "Disallow the use of `undefined` as an identifier", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-underscore-dangle", - "description": "Disallow dangling underscores in identifiers", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-unneeded-ternary", - "description": "Disallow ternary operators when simpler alternatives exist", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-unused-expressions", - "description": "Disallow unused expressions", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-unused-labels", - "description": "Disallow unused labels", - "recommended": true, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-useless-call", - "description": "Disallow unnecessary calls to `.call()` and `.apply()`", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-useless-catch", - "description": "Disallow unnecessary `catch` clauses", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-useless-computed-key", - "description": "Disallow unnecessary computed property keys in objects and classes", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-useless-concat", - "description": "Disallow unnecessary concatenation of literals or template literals", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-useless-constructor", - "description": "Disallow unnecessary constructors", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-useless-escape", - "description": "Disallow unnecessary escape characters", - "recommended": true, - "fixable": false, - "hasSuggestions": true - }, - { - "name": "no-useless-rename", - "description": "Disallow renaming import, export, and destructured assignments to the same name", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-useless-return", - "description": "Disallow redundant return statements", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-var", - "description": "Require `let` or `const` instead of `var`", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-void", - "description": "Disallow `void` operators", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-warning-comments", - "description": "Disallow specified warning terms in comments", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-with", - "description": "Disallow `with` statements", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "object-shorthand", - "description": "Require or disallow method and property shorthand syntax for object literals", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "one-var", - "description": "Enforce variables to be declared either together or separately in functions", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "one-var-declaration-per-line", - "description": "Require or disallow newlines around variable declarations", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "operator-assignment", - "description": "Require or disallow assignment operator shorthand where possible", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "prefer-arrow-callback", - "description": "Require using arrow functions for callbacks", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "prefer-const", - "description": "Require `const` declarations for variables that are never reassigned after declared", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "prefer-destructuring", - "description": "Require destructuring from arrays and/or objects", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "prefer-exponentiation-operator", - "description": "Disallow the use of `Math.pow` in favor of the `**` operator", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "prefer-named-capture-group", - "description": "Enforce using named capture group in regular expression", - "recommended": false, - "fixable": false, - "hasSuggestions": true - }, - { - "name": "prefer-numeric-literals", - "description": "Disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "prefer-object-has-own", - "description": "Disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "prefer-object-spread", - "description": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "prefer-promise-reject-errors", - "description": "Require using Error objects as Promise rejection reasons", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "prefer-regex-literals", - "description": "Disallow use of the `RegExp` constructor in favor of regular expression literals", - "recommended": false, - "fixable": false, - "hasSuggestions": true - }, - { - "name": "prefer-rest-params", - "description": "Require rest parameters instead of `arguments`", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "prefer-spread", - "description": "Require spread operators instead of `.apply()`", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "prefer-template", - "description": "Require template literals instead of string concatenation", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "quote-props", - "description": "Require quotes around object literal property names", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "radix", - "description": "Enforce the consistent use of the radix argument when using `parseInt()`", - "recommended": false, - "fixable": false, - "hasSuggestions": true - }, - { - "name": "require-await", - "description": "Disallow async functions which have no `await` expression", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "require-unicode-regexp", - "description": "Enforce the use of `u` or `v` flag on RegExp", - "recommended": false, - "fixable": false, - "hasSuggestions": true - }, - { - "name": "require-yield", - "description": "Require generator functions to contain `yield`", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "sort-imports", - "description": "Enforce sorted import declarations within modules", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "sort-keys", - "description": "Require object keys to be sorted", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "sort-vars", - "description": "Require variables within the same declaration block to be sorted", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "spaced-comment", - "description": "Enforce consistent spacing after the `//` or `/*` in a comment", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "strict", - "description": "Require or disallow strict mode directives", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "symbol-description", - "description": "Require symbol descriptions", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "vars-on-top", - "description": "Require `var` declarations be placed at the top of their containing scope", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "yoda", - "description": "Require or disallow \"Yoda\" conditions", - "recommended": false, - "fixable": true, - "hasSuggestions": false - } - ] - }, - { - "name": "layout", - "displayName": "Layout & Formatting", - "description": "These rules care about how the code looks rather than how it executes:", - "rules": [ - { - "name": "array-bracket-newline", - "description": "Enforce linebreaks after opening and before closing array brackets", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "array-bracket-spacing", - "description": "Enforce consistent spacing inside array brackets", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "array-element-newline", - "description": "Enforce line breaks after each array element", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "arrow-parens", - "description": "Require parentheses around arrow function arguments", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "arrow-spacing", - "description": "Enforce consistent spacing before and after the arrow in arrow functions", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "block-spacing", - "description": "Disallow or enforce spaces inside of blocks after opening block and before closing block", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "brace-style", - "description": "Enforce consistent brace style for blocks", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "comma-dangle", - "description": "Require or disallow trailing commas", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "comma-spacing", - "description": "Enforce consistent spacing before and after commas", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "comma-style", - "description": "Enforce consistent comma style", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "computed-property-spacing", - "description": "Enforce consistent spacing inside computed property brackets", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "dot-location", - "description": "Enforce consistent newlines before and after dots", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "eol-last", - "description": "Require or disallow newline at the end of files", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "func-call-spacing", - "description": "Require or disallow spacing between function identifiers and their invocations", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "function-call-argument-newline", - "description": "Enforce line breaks between arguments of a function call", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "function-paren-newline", - "description": "Enforce consistent line breaks inside function parentheses", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "generator-star-spacing", - "description": "Enforce consistent spacing around `*` operators in generator functions", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "implicit-arrow-linebreak", - "description": "Enforce the location of arrow function bodies", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "indent", - "description": "Enforce consistent indentation", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "jsx-quotes", - "description": "Enforce the consistent use of either double or single quotes in JSX attributes", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "key-spacing", - "description": "Enforce consistent spacing between keys and values in object literal properties", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "keyword-spacing", - "description": "Enforce consistent spacing before and after keywords", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "line-comment-position", - "description": "Enforce position of line comments", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "linebreak-style", - "description": "Enforce consistent linebreak style", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "lines-around-comment", - "description": "Require empty lines around comments", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "lines-between-class-members", - "description": "Require or disallow an empty line between class members", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "max-len", - "description": "Enforce a maximum line length", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "max-statements-per-line", - "description": "Enforce a maximum number of statements allowed per line", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "multiline-ternary", - "description": "Enforce newlines between operands of ternary expressions", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "new-parens", - "description": "Enforce or disallow parentheses when invoking a constructor with no arguments", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "newline-per-chained-call", - "description": "Require a newline after each call in a method chain", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-extra-parens", - "description": "Disallow unnecessary parentheses", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-mixed-spaces-and-tabs", - "description": "Disallow mixed spaces and tabs for indentation", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-multi-spaces", - "description": "Disallow multiple spaces", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-multiple-empty-lines", - "description": "Disallow multiple empty lines", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-tabs", - "description": "Disallow all tabs", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-trailing-spaces", - "description": "Disallow trailing whitespace at the end of lines", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-whitespace-before-property", - "description": "Disallow whitespace before properties", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "nonblock-statement-body-position", - "description": "Enforce the location of single-line statements", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "object-curly-newline", - "description": "Enforce consistent line breaks after opening and before closing braces", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "object-curly-spacing", - "description": "Enforce consistent spacing inside braces", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "object-property-newline", - "description": "Enforce placing object properties on separate lines", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "operator-linebreak", - "description": "Enforce consistent linebreak style for operators", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "padded-blocks", - "description": "Require or disallow padding within blocks", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "padding-line-between-statements", - "description": "Require or disallow padding lines between statements", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "quotes", - "description": "Enforce the consistent use of either backticks, double, or single quotes", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "rest-spread-spacing", - "description": "Enforce spacing between rest and spread operators and their expressions", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "semi", - "description": "Require or disallow semicolons instead of ASI", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "semi-spacing", - "description": "Enforce consistent spacing before and after semicolons", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "semi-style", - "description": "Enforce location of semicolons", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "space-before-blocks", - "description": "Enforce consistent spacing before blocks", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "space-before-function-paren", - "description": "Enforce consistent spacing before `function` definition opening parenthesis", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "space-in-parens", - "description": "Enforce consistent spacing inside parentheses", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "space-infix-ops", - "description": "Require spacing around infix operators", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "space-unary-ops", - "description": "Enforce consistent spacing before or after unary operators", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "switch-colon-spacing", - "description": "Enforce spacing around colons of switch statements", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "template-curly-spacing", - "description": "Require or disallow spacing around embedded expressions of template strings", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "template-tag-spacing", - "description": "Require or disallow spacing between template tags and their literals", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "unicode-bom", - "description": "Require or disallow Unicode byte order mark (BOM)", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "wrap-iife", - "description": "Require parentheses around immediate `function` invocations", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "wrap-regex", - "description": "Require parenthesis around regex literals", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "yield-star-spacing", - "description": "Require or disallow spacing around the `*` in `yield*` expressions", - "recommended": false, - "fixable": true, - "hasSuggestions": false - } - ] - } - ], - "deprecated": { - "name": "Deprecated", - "description": "These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:", - "rules": [ + "types": { + "problem": [ + { + "name": "array-callback-return", + "description": "Enforce `return` statements in callbacks of array methods", + "recommended": false, + "fixable": false, + "hasSuggestions": true + }, + { + "name": "constructor-super", + "description": "Require `super()` calls in constructors", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "for-direction", + "description": "Enforce \"for\" loop update clause moving the counter in the right direction", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "getter-return", + "description": "Enforce `return` statements in getters", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-async-promise-executor", + "description": "Disallow using an async function as a Promise executor", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-await-in-loop", + "description": "Disallow `await` inside of loops", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-class-assign", + "description": "Disallow reassigning class members", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-compare-neg-zero", + "description": "Disallow comparing against -0", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-cond-assign", + "description": "Disallow assignment operators in conditional expressions", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-const-assign", + "description": "Disallow reassigning `const` variables", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-constant-binary-expression", + "description": "Disallow expressions where the operation doesn't affect the value", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-constant-condition", + "description": "Disallow constant expressions in conditions", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-constructor-return", + "description": "Disallow returning value from constructor", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-control-regex", + "description": "Disallow control characters in regular expressions", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-debugger", + "description": "Disallow the use of `debugger`", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-dupe-args", + "description": "Disallow duplicate arguments in `function` definitions", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-dupe-class-members", + "description": "Disallow duplicate class members", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-dupe-else-if", + "description": "Disallow duplicate conditions in if-else-if chains", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-dupe-keys", + "description": "Disallow duplicate keys in object literals", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-duplicate-case", + "description": "Disallow duplicate case labels", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-duplicate-imports", + "description": "Disallow duplicate module imports", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-empty-character-class", + "description": "Disallow empty character classes in regular expressions", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-empty-pattern", + "description": "Disallow empty destructuring patterns", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-ex-assign", + "description": "Disallow reassigning exceptions in `catch` clauses", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-fallthrough", + "description": "Disallow fallthrough of `case` statements", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-func-assign", + "description": "Disallow reassigning `function` declarations", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-import-assign", + "description": "Disallow assigning to imported bindings", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-inner-declarations", + "description": "Disallow variable or `function` declarations in nested blocks", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-invalid-regexp", + "description": "Disallow invalid regular expression strings in `RegExp` constructors", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-irregular-whitespace", + "description": "Disallow irregular whitespace", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-loss-of-precision", + "description": "Disallow literal numbers that lose precision", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-misleading-character-class", + "description": "Disallow characters which are made with multiple code points in character class syntax", + "recommended": true, + "fixable": false, + "hasSuggestions": true + }, + { + "name": "no-new-native-nonconstructor", + "description": "Disallow `new` operators with global non-constructor functions", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-new-symbol", + "description": "Disallow `new` operators with the `Symbol` object", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-obj-calls", + "description": "Disallow calling global object properties as functions", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-promise-executor-return", + "description": "Disallow returning values from Promise executor functions", + "recommended": false, + "fixable": false, + "hasSuggestions": true + }, + { + "name": "no-prototype-builtins", + "description": "Disallow calling some `Object.prototype` methods directly on objects", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-self-assign", + "description": "Disallow assignments where both sides are exactly the same", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-self-compare", + "description": "Disallow comparisons where both sides are exactly the same", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-setter-return", + "description": "Disallow returning values from setters", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-sparse-arrays", + "description": "Disallow sparse arrays", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-template-curly-in-string", + "description": "Disallow template literal placeholder syntax in regular strings", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-this-before-super", + "description": "Disallow `this`/`super` before calling `super()` in constructors", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-undef", + "description": "Disallow the use of undeclared variables unless mentioned in `/*global */` comments", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-unexpected-multiline", + "description": "Disallow confusing multiline expressions", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-unmodified-loop-condition", + "description": "Disallow unmodified loop conditions", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-unreachable", + "description": "Disallow unreachable code after `return`, `throw`, `continue`, and `break` statements", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-unreachable-loop", + "description": "Disallow loops with a body that allows only one iteration", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-unsafe-finally", + "description": "Disallow control flow statements in `finally` blocks", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-unsafe-negation", + "description": "Disallow negating the left operand of relational operators", + "recommended": true, + "fixable": false, + "hasSuggestions": true + }, + { + "name": "no-unsafe-optional-chaining", + "description": "Disallow use of optional chaining in contexts where the `undefined` value is not allowed", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-unused-private-class-members", + "description": "Disallow unused private class members", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-unused-vars", + "description": "Disallow unused variables", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-use-before-define", + "description": "Disallow the use of variables before they are defined", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-useless-backreference", + "description": "Disallow useless backreferences in regular expressions", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "require-atomic-updates", + "description": "Disallow assignments that can lead to race conditions due to usage of `await` or `yield`", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "use-isnan", + "description": "Require calls to `isNaN()` when checking for `NaN`", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "valid-typeof", + "description": "Enforce comparing `typeof` expressions against valid strings", + "recommended": true, + "fixable": false, + "hasSuggestions": true + } + ], + "suggestion": [ + { + "name": "accessor-pairs", + "description": "Enforce getter and setter pairs in objects and classes", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "arrow-body-style", + "description": "Require braces around arrow function bodies", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "block-scoped-var", + "description": "Enforce the use of variables within the scope they are defined", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "camelcase", + "description": "Enforce camelcase naming convention", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "capitalized-comments", + "description": "Enforce or disallow capitalization of the first letter of a comment", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "class-methods-use-this", + "description": "Enforce that class methods utilize `this`", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "complexity", + "description": "Enforce a maximum cyclomatic complexity allowed in a program", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "consistent-return", + "description": "Require `return` statements to either always or never specify values", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "consistent-this", + "description": "Enforce consistent naming when capturing the current execution context", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "curly", + "description": "Enforce consistent brace style for all control statements", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "default-case", + "description": "Require `default` cases in `switch` statements", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "default-case-last", + "description": "Enforce default clauses in switch statements to be last", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "default-param-last", + "description": "Enforce default parameters to be last", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "dot-notation", + "description": "Enforce dot notation whenever possible", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "eqeqeq", + "description": "Require the use of `===` and `!==`", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "func-name-matching", + "description": "Require function names to match the name of the variable or property to which they are assigned", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "func-names", + "description": "Require or disallow named `function` expressions", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "func-style", + "description": "Enforce the consistent use of either `function` declarations or expressions", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "grouped-accessor-pairs", + "description": "Require grouped accessor pairs in object literals and classes", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "guard-for-in", + "description": "Require `for-in` loops to include an `if` statement", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "id-denylist", + "description": "Disallow specified identifiers", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "id-length", + "description": "Enforce minimum and maximum identifier lengths", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "id-match", + "description": "Require identifiers to match a specified regular expression", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "init-declarations", + "description": "Require or disallow initialization in variable declarations", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "logical-assignment-operators", + "description": "Require or disallow logical assignment operator shorthand", + "recommended": false, + "fixable": true, + "hasSuggestions": true + }, + { + "name": "max-classes-per-file", + "description": "Enforce a maximum number of classes per file", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "max-depth", + "description": "Enforce a maximum depth that blocks can be nested", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "max-lines", + "description": "Enforce a maximum number of lines per file", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "max-lines-per-function", + "description": "Enforce a maximum number of lines of code in a function", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "max-nested-callbacks", + "description": "Enforce a maximum depth that callbacks can be nested", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "max-params", + "description": "Enforce a maximum number of parameters in function definitions", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "max-statements", + "description": "Enforce a maximum number of statements allowed in function blocks", + "recommended": false, + "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", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-alert", + "description": "Disallow the use of `alert`, `confirm`, and `prompt`", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-array-constructor", + "description": "Disallow `Array` constructors", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-bitwise", + "description": "Disallow bitwise operators", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-caller", + "description": "Disallow the use of `arguments.caller` or `arguments.callee`", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-case-declarations", + "description": "Disallow lexical declarations in case clauses", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-confusing-arrow", + "description": "Disallow arrow functions where they could be confused with comparisons", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-console", + "description": "Disallow the use of `console`", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-continue", + "description": "Disallow `continue` statements", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-delete-var", + "description": "Disallow deleting variables", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-div-regex", + "description": "Disallow equal signs explicitly at the beginning of regular expressions", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-else-return", + "description": "Disallow `else` blocks after `return` statements in `if` statements", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-empty", + "description": "Disallow empty block statements", + "recommended": true, + "fixable": false, + "hasSuggestions": true + }, + { + "name": "no-empty-function", + "description": "Disallow empty functions", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-empty-static-block", + "description": "Disallow empty static blocks", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-eq-null", + "description": "Disallow `null` comparisons without type-checking operators", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-eval", + "description": "Disallow the use of `eval()`", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-extend-native", + "description": "Disallow extending native types", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-extra-bind", + "description": "Disallow unnecessary calls to `.bind()`", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-extra-boolean-cast", + "description": "Disallow unnecessary boolean casts", + "recommended": true, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-extra-label", + "description": "Disallow unnecessary labels", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-extra-semi", + "description": "Disallow unnecessary semicolons", + "recommended": true, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-floating-decimal", + "description": "Disallow leading or trailing decimal points in numeric literals", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-global-assign", + "description": "Disallow assignments to native objects or read-only global variables", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-implicit-coercion", + "description": "Disallow shorthand type conversions", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-implicit-globals", + "description": "Disallow declarations in the global scope", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-implied-eval", + "description": "Disallow the use of `eval()`-like methods", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-inline-comments", + "description": "Disallow inline comments after code", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-invalid-this", + "description": "Disallow use of `this` in contexts where the value of `this` is `undefined`", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-iterator", + "description": "Disallow the use of the `__iterator__` property", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-label-var", + "description": "Disallow labels that share a name with a variable", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-labels", + "description": "Disallow labeled statements", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-lone-blocks", + "description": "Disallow unnecessary nested blocks", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-lonely-if", + "description": "Disallow `if` statements as the only statement in `else` blocks", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-loop-func", + "description": "Disallow function declarations that contain unsafe references inside loop statements", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-magic-numbers", + "description": "Disallow magic numbers", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-mixed-operators", + "description": "Disallow mixed binary operators", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-multi-assign", + "description": "Disallow use of chained assignment expressions", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-multi-str", + "description": "Disallow multiline strings", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-negated-condition", + "description": "Disallow negated conditions", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-nested-ternary", + "description": "Disallow nested ternary expressions", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-new", + "description": "Disallow `new` operators outside of assignments or comparisons", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-new-func", + "description": "Disallow `new` operators with the `Function` object", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-new-wrappers", + "description": "Disallow `new` operators with the `String`, `Number`, and `Boolean` objects", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-nonoctal-decimal-escape", + "description": "Disallow `\\8` and `\\9` escape sequences in string literals", + "recommended": true, + "fixable": false, + "hasSuggestions": true + }, + { + "name": "no-object-constructor", + "description": "Disallow calls to the `Object` constructor without an argument", + "recommended": false, + "fixable": false, + "hasSuggestions": true + }, + { + "name": "no-octal", + "description": "Disallow octal literals", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-octal-escape", + "description": "Disallow octal escape sequences in string literals", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-param-reassign", + "description": "Disallow reassigning `function` parameters", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-plusplus", + "description": "Disallow the unary operators `++` and `--`", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-proto", + "description": "Disallow the use of the `__proto__` property", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-redeclare", + "description": "Disallow variable redeclaration", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-regex-spaces", + "description": "Disallow multiple spaces in regular expressions", + "recommended": true, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-restricted-exports", + "description": "Disallow specified names in exports", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-restricted-globals", + "description": "Disallow specified global variables", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-restricted-imports", + "description": "Disallow specified modules when loaded by `import`", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-restricted-properties", + "description": "Disallow certain properties on certain objects", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-restricted-syntax", + "description": "Disallow specified syntax", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-return-assign", + "description": "Disallow assignment operators in `return` statements", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-script-url", + "description": "Disallow `javascript:` urls", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-sequences", + "description": "Disallow comma operators", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-shadow", + "description": "Disallow variable declarations from shadowing variables declared in the outer scope", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-shadow-restricted-names", + "description": "Disallow identifiers from shadowing restricted names", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, { - "name": "callback-return", - "replacedBy": [] + "name": "no-ternary", + "description": "Disallow ternary operators", + "recommended": false, + "fixable": false, + "hasSuggestions": false }, { - "name": "global-require", - "replacedBy": [] + "name": "no-throw-literal", + "description": "Disallow throwing literals as exceptions", + "recommended": false, + "fixable": false, + "hasSuggestions": false }, { - "name": "handle-callback-err", - "replacedBy": [] + "name": "no-undef-init", + "description": "Disallow initializing variables to `undefined`", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "name": "id-blacklist", - "replacedBy": [ - "id-denylist" - ] + "name": "no-undefined", + "description": "Disallow the use of `undefined` as an identifier", + "recommended": false, + "fixable": false, + "hasSuggestions": false }, { - "name": "indent-legacy", - "replacedBy": [ - "indent" - ] + "name": "no-underscore-dangle", + "description": "Disallow dangling underscores in identifiers", + "recommended": false, + "fixable": false, + "hasSuggestions": false }, { - "name": "lines-around-directive", - "replacedBy": [ - "padding-line-between-statements" - ] + "name": "no-unneeded-ternary", + "description": "Disallow ternary operators when simpler alternatives exist", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "name": "newline-after-var", - "replacedBy": [ - "padding-line-between-statements" - ] + "name": "no-unused-expressions", + "description": "Disallow unused expressions", + "recommended": false, + "fixable": false, + "hasSuggestions": false }, { - "name": "newline-before-return", - "replacedBy": [ - "padding-line-between-statements" - ] + "name": "no-unused-labels", + "description": "Disallow unused labels", + "recommended": true, + "fixable": true, + "hasSuggestions": false }, { - "name": "no-buffer-constructor", - "replacedBy": [] + "name": "no-useless-call", + "description": "Disallow unnecessary calls to `.call()` and `.apply()`", + "recommended": false, + "fixable": false, + "hasSuggestions": false }, { - "name": "no-catch-shadow", - "replacedBy": [ - "no-shadow" - ] + "name": "no-useless-catch", + "description": "Disallow unnecessary `catch` clauses", + "recommended": true, + "fixable": false, + "hasSuggestions": false }, { - "name": "no-mixed-requires", - "replacedBy": [] + "name": "no-useless-computed-key", + "description": "Disallow unnecessary computed property keys in objects and classes", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "name": "no-native-reassign", - "replacedBy": [ - "no-global-assign" - ] + "name": "no-useless-concat", + "description": "Disallow unnecessary concatenation of literals or template literals", + "recommended": false, + "fixable": false, + "hasSuggestions": false }, { - "name": "no-negated-in-lhs", - "replacedBy": [ - "no-unsafe-negation" - ] + "name": "no-useless-constructor", + "description": "Disallow unnecessary constructors", + "recommended": false, + "fixable": false, + "hasSuggestions": false }, { - "name": "no-new-object", - "replacedBy": [ - "no-object-constructor" - ] + "name": "no-useless-escape", + "description": "Disallow unnecessary escape characters", + "recommended": true, + "fixable": false, + "hasSuggestions": true }, { - "name": "no-new-require", - "replacedBy": [] + "name": "no-useless-rename", + "description": "Disallow renaming import, export, and destructured assignments to the same name", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "name": "no-path-concat", - "replacedBy": [] + "name": "no-useless-return", + "description": "Disallow redundant return statements", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "name": "no-process-env", - "replacedBy": [] + "name": "no-var", + "description": "Require `let` or `const` instead of `var`", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "name": "no-process-exit", - "replacedBy": [] + "name": "no-void", + "description": "Disallow `void` operators", + "recommended": false, + "fixable": false, + "hasSuggestions": false }, { - "name": "no-restricted-modules", - "replacedBy": [] + "name": "no-warning-comments", + "description": "Disallow specified warning terms in comments", + "recommended": false, + "fixable": false, + "hasSuggestions": false }, { - "name": "no-return-await", - "replacedBy": [] + "name": "no-with", + "description": "Disallow `with` statements", + "recommended": true, + "fixable": false, + "hasSuggestions": false }, { - "name": "no-spaced-func", - "replacedBy": [ - "func-call-spacing" - ] + "name": "object-shorthand", + "description": "Require or disallow method and property shorthand syntax for object literals", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "name": "no-sync", - "replacedBy": [] + "name": "one-var", + "description": "Enforce variables to be declared either together or separately in functions", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "name": "prefer-reflect", - "replacedBy": [] + "name": "one-var-declaration-per-line", + "description": "Require or disallow newlines around variable declarations", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "name": "require-jsdoc", - "replacedBy": [] + "name": "operator-assignment", + "description": "Require or disallow assignment operator shorthand where possible", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "name": "valid-jsdoc", - "replacedBy": [] + "name": "prefer-arrow-callback", + "description": "Require using arrow functions for callbacks", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "prefer-const", + "description": "Require `const` declarations for variables that are never reassigned after declared", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "prefer-destructuring", + "description": "Require destructuring from arrays and/or objects", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "prefer-exponentiation-operator", + "description": "Disallow the use of `Math.pow` in favor of the `**` operator", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "prefer-named-capture-group", + "description": "Enforce using named capture group in regular expression", + "recommended": false, + "fixable": false, + "hasSuggestions": true + }, + { + "name": "prefer-numeric-literals", + "description": "Disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "prefer-object-has-own", + "description": "Disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "prefer-object-spread", + "description": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "prefer-promise-reject-errors", + "description": "Require using Error objects as Promise rejection reasons", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "prefer-regex-literals", + "description": "Disallow use of the `RegExp` constructor in favor of regular expression literals", + "recommended": false, + "fixable": false, + "hasSuggestions": true + }, + { + "name": "prefer-rest-params", + "description": "Require rest parameters instead of `arguments`", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "prefer-spread", + "description": "Require spread operators instead of `.apply()`", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "prefer-template", + "description": "Require template literals instead of string concatenation", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "quote-props", + "description": "Require quotes around object literal property names", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "radix", + "description": "Enforce the consistent use of the radix argument when using `parseInt()`", + "recommended": false, + "fixable": false, + "hasSuggestions": true + }, + { + "name": "require-await", + "description": "Disallow async functions which have no `await` expression", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "require-unicode-regexp", + "description": "Enforce the use of `u` or `v` flag on RegExp", + "recommended": false, + "fixable": false, + "hasSuggestions": true + }, + { + "name": "require-yield", + "description": "Require generator functions to contain `yield`", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "sort-imports", + "description": "Enforce sorted import declarations within modules", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "sort-keys", + "description": "Require object keys to be sorted", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "sort-vars", + "description": "Require variables within the same declaration block to be sorted", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "spaced-comment", + "description": "Enforce consistent spacing after the `//` or `/*` in a comment", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "strict", + "description": "Require or disallow strict mode directives", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "symbol-description", + "description": "Require symbol descriptions", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "vars-on-top", + "description": "Require `var` declarations be placed at the top of their containing scope", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "yoda", + "description": "Require or disallow \"Yoda\" conditions", + "recommended": false, + "fixable": true, + "hasSuggestions": false } - ] - }, - "removed": { - "name": "Removed", - "description": "These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:", - "rules": [ + ], + "layout": [ + { + "name": "array-bracket-newline", + "description": "Enforce linebreaks after opening and before closing array brackets", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "array-bracket-spacing", + "description": "Enforce consistent spacing inside array brackets", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "array-element-newline", + "description": "Enforce line breaks after each array element", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "arrow-parens", + "description": "Require parentheses around arrow function arguments", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "arrow-spacing", + "description": "Enforce consistent spacing before and after the arrow in arrow functions", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "block-spacing", + "description": "Disallow or enforce spaces inside of blocks after opening block and before closing block", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "brace-style", + "description": "Enforce consistent brace style for blocks", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "comma-dangle", + "description": "Require or disallow trailing commas", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "comma-spacing", + "description": "Enforce consistent spacing before and after commas", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "comma-style", + "description": "Enforce consistent comma style", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "computed-property-spacing", + "description": "Enforce consistent spacing inside computed property brackets", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "dot-location", + "description": "Enforce consistent newlines before and after dots", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "eol-last", + "description": "Require or disallow newline at the end of files", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "func-call-spacing", + "description": "Require or disallow spacing between function identifiers and their invocations", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "function-call-argument-newline", + "description": "Enforce line breaks between arguments of a function call", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "function-paren-newline", + "description": "Enforce consistent line breaks inside function parentheses", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "generator-star-spacing", + "description": "Enforce consistent spacing around `*` operators in generator functions", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "implicit-arrow-linebreak", + "description": "Enforce the location of arrow function bodies", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "indent", + "description": "Enforce consistent indentation", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "jsx-quotes", + "description": "Enforce the consistent use of either double or single quotes in JSX attributes", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "key-spacing", + "description": "Enforce consistent spacing between keys and values in object literal properties", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "keyword-spacing", + "description": "Enforce consistent spacing before and after keywords", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "line-comment-position", + "description": "Enforce position of line comments", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "linebreak-style", + "description": "Enforce consistent linebreak style", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "lines-around-comment", + "description": "Require empty lines around comments", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "lines-between-class-members", + "description": "Require or disallow an empty line between class members", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "max-len", + "description": "Enforce a maximum line length", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "max-statements-per-line", + "description": "Enforce a maximum number of statements allowed per line", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "multiline-ternary", + "description": "Enforce newlines between operands of ternary expressions", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "new-parens", + "description": "Enforce or disallow parentheses when invoking a constructor with no arguments", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "newline-per-chained-call", + "description": "Require a newline after each call in a method chain", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-extra-parens", + "description": "Disallow unnecessary parentheses", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-mixed-spaces-and-tabs", + "description": "Disallow mixed spaces and tabs for indentation", + "recommended": true, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-multi-spaces", + "description": "Disallow multiple spaces", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-multiple-empty-lines", + "description": "Disallow multiple empty lines", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-tabs", + "description": "Disallow all tabs", + "recommended": false, + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-trailing-spaces", + "description": "Disallow trailing whitespace at the end of lines", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "no-whitespace-before-property", + "description": "Disallow whitespace before properties", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, { - "removed": "generator-star", - "replacedBy": [ - "generator-star-spacing" - ] + "name": "nonblock-statement-body-position", + "description": "Enforce the location of single-line statements", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "global-strict", - "replacedBy": [ - "strict" - ] + "name": "object-curly-newline", + "description": "Enforce consistent line breaks after opening and before closing braces", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "no-arrow-condition", - "replacedBy": [ - "no-confusing-arrow", - "no-constant-condition" - ] + "name": "object-curly-spacing", + "description": "Enforce consistent spacing inside braces", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "no-comma-dangle", - "replacedBy": [ - "comma-dangle" - ] + "name": "object-property-newline", + "description": "Enforce placing object properties on separate lines", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "no-empty-class", - "replacedBy": [ - "no-empty-character-class" - ] + "name": "operator-linebreak", + "description": "Enforce consistent linebreak style for operators", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "no-empty-label", - "replacedBy": [ - "no-labels" - ] + "name": "padded-blocks", + "description": "Require or disallow padding within blocks", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "no-extra-strict", - "replacedBy": [ - "strict" - ] + "name": "padding-line-between-statements", + "description": "Require or disallow padding lines between statements", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "no-reserved-keys", - "replacedBy": [ - "quote-props" - ] + "name": "quotes", + "description": "Enforce the consistent use of either backticks, double, or single quotes", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "no-space-before-semi", - "replacedBy": [ - "semi-spacing" - ] + "name": "rest-spread-spacing", + "description": "Enforce spacing between rest and spread operators and their expressions", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "no-wrap-func", - "replacedBy": [ - "no-extra-parens" - ] + "name": "semi", + "description": "Require or disallow semicolons instead of ASI", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "space-after-function-name", - "replacedBy": [ - "space-before-function-paren" - ] + "name": "semi-spacing", + "description": "Enforce consistent spacing before and after semicolons", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "space-after-keywords", - "replacedBy": [ - "keyword-spacing" - ] + "name": "semi-style", + "description": "Enforce location of semicolons", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "space-before-function-parentheses", - "replacedBy": [ - "space-before-function-paren" - ] + "name": "space-before-blocks", + "description": "Enforce consistent spacing before blocks", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "space-before-keywords", - "replacedBy": [ - "keyword-spacing" - ] + "name": "space-before-function-paren", + "description": "Enforce consistent spacing before `function` definition opening parenthesis", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "space-in-brackets", - "replacedBy": [ - "object-curly-spacing", - "array-bracket-spacing" - ] + "name": "space-in-parens", + "description": "Enforce consistent spacing inside parentheses", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "space-return-throw-case", - "replacedBy": [ - "keyword-spacing" - ] + "name": "space-infix-ops", + "description": "Require spacing around infix operators", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "space-unary-word-ops", - "replacedBy": [ - "space-unary-ops" - ] + "name": "space-unary-ops", + "description": "Enforce consistent spacing before or after unary operators", + "recommended": false, + "fixable": true, + "hasSuggestions": false }, { - "removed": "spaced-line-comment", - "replacedBy": [ - "spaced-comment" - ] + "name": "switch-colon-spacing", + "description": "Enforce spacing around colons of switch statements", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "template-curly-spacing", + "description": "Require or disallow spacing around embedded expressions of template strings", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "template-tag-spacing", + "description": "Require or disallow spacing between template tags and their literals", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "unicode-bom", + "description": "Require or disallow Unicode byte order mark (BOM)", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "wrap-iife", + "description": "Require parentheses around immediate `function` invocations", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "wrap-regex", + "description": "Require parenthesis around regex literals", + "recommended": false, + "fixable": true, + "hasSuggestions": false + }, + { + "name": "yield-star-spacing", + "description": "Require or disallow spacing around the `*` in `yield*` expressions", + "recommended": false, + "fixable": true, + "hasSuggestions": false } ] - } + }, + "deprecated": [ + { + "name": "callback-return", + "replacedBy": [] + }, + { + "name": "global-require", + "replacedBy": [] + }, + { + "name": "handle-callback-err", + "replacedBy": [] + }, + { + "name": "id-blacklist", + "replacedBy": [ + "id-denylist" + ] + }, + { + "name": "indent-legacy", + "replacedBy": [ + "indent" + ] + }, + { + "name": "lines-around-directive", + "replacedBy": [ + "padding-line-between-statements" + ] + }, + { + "name": "newline-after-var", + "replacedBy": [ + "padding-line-between-statements" + ] + }, + { + "name": "newline-before-return", + "replacedBy": [ + "padding-line-between-statements" + ] + }, + { + "name": "no-buffer-constructor", + "replacedBy": [] + }, + { + "name": "no-catch-shadow", + "replacedBy": [ + "no-shadow" + ] + }, + { + "name": "no-mixed-requires", + "replacedBy": [] + }, + { + "name": "no-native-reassign", + "replacedBy": [ + "no-global-assign" + ] + }, + { + "name": "no-negated-in-lhs", + "replacedBy": [ + "no-unsafe-negation" + ] + }, + { + "name": "no-new-object", + "replacedBy": [ + "no-object-constructor" + ] + }, + { + "name": "no-new-require", + "replacedBy": [] + }, + { + "name": "no-path-concat", + "replacedBy": [] + }, + { + "name": "no-process-env", + "replacedBy": [] + }, + { + "name": "no-process-exit", + "replacedBy": [] + }, + { + "name": "no-restricted-modules", + "replacedBy": [] + }, + { + "name": "no-return-await", + "replacedBy": [] + }, + { + "name": "no-spaced-func", + "replacedBy": [ + "func-call-spacing" + ] + }, + { + "name": "no-sync", + "replacedBy": [] + }, + { + "name": "prefer-reflect", + "replacedBy": [] + }, + { + "name": "require-jsdoc", + "replacedBy": [] + }, + { + "name": "valid-jsdoc", + "replacedBy": [] + } + ], + "removed": [ + { + "removed": "generator-star", + "replacedBy": [ + "generator-star-spacing" + ] + }, + { + "removed": "global-strict", + "replacedBy": [ + "strict" + ] + }, + { + "removed": "no-arrow-condition", + "replacedBy": [ + "no-confusing-arrow", + "no-constant-condition" + ] + }, + { + "removed": "no-comma-dangle", + "replacedBy": [ + "comma-dangle" + ] + }, + { + "removed": "no-empty-class", + "replacedBy": [ + "no-empty-character-class" + ] + }, + { + "removed": "no-empty-label", + "replacedBy": [ + "no-labels" + ] + }, + { + "removed": "no-extra-strict", + "replacedBy": [ + "strict" + ] + }, + { + "removed": "no-reserved-keys", + "replacedBy": [ + "quote-props" + ] + }, + { + "removed": "no-space-before-semi", + "replacedBy": [ + "semi-spacing" + ] + }, + { + "removed": "no-wrap-func", + "replacedBy": [ + "no-extra-parens" + ] + }, + { + "removed": "space-after-function-name", + "replacedBy": [ + "space-before-function-paren" + ] + }, + { + "removed": "space-after-keywords", + "replacedBy": [ + "keyword-spacing" + ] + }, + { + "removed": "space-before-function-parentheses", + "replacedBy": [ + "space-before-function-paren" + ] + }, + { + "removed": "space-before-keywords", + "replacedBy": [ + "keyword-spacing" + ] + }, + { + "removed": "space-in-brackets", + "replacedBy": [ + "object-curly-spacing", + "array-bracket-spacing" + ] + }, + { + "removed": "space-return-throw-case", + "replacedBy": [ + "keyword-spacing" + ] + }, + { + "removed": "space-unary-word-ops", + "replacedBy": [ + "space-unary-ops" + ] + }, + { + "removed": "spaced-line-comment", + "replacedBy": [ + "spaced-comment" + ] + } + ] } \ No newline at end of file diff --git a/docs/src/_data/rules_categories.js b/docs/src/_data/rules_categories.js new file mode 100644 index 000000000000..46856958f228 --- /dev/null +++ b/docs/src/_data/rules_categories.js @@ -0,0 +1,26 @@ +module.exports = eleventy => { + const PATH_PREFIX = eleventy.PATH_PREFIX; + + return { + problem: { + displayName: "Possible Problems", + description: "These rules relate to possible logic errors in code:" + }, + suggestion: { + displayName: "Suggestions", + description: "These rules suggest alternate ways of doing things:" + }, + layout: { + displayName: "Layout & Formatting", + description: "These rules care about how the code looks rather than how it executes:" + }, + deprecated: { + displayName: "Deprecated", + description: `These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:` + }, + removed: { + 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/pages/rules.md b/docs/src/pages/rules.md index 996860e2c865..9d801cf5126b 100644 --- a/docs/src/pages/rules.md +++ b/docs/src/pages/rules.md @@ -20,14 +20,17 @@ Rules in ESLint are grouped by type to help you understand their purpose. Each r hasSuggestions: true }) }} -{%- for type in rules.types -%} +{%- for type, content in rules.types -%} -

{{ type.displayName }}

+

{{ rules_categories[type].displayName }}

-{{ type.description | safe }} +{{ rules_categories[type].description | safe }} - {%- for the_rule in type.rules -%} - {%- if type.displayName == 'deprecated' -%}{%- set deprecated_value = true -%}{%- endif -%} + {%- for the_rule in content -%} + + {%- if rules_categories[type].displayName == 'deprecated' -%} + {%- set deprecated_value = true -%} + {%- endif -%} {%- set name_value = the_rule.name -%} {%- set description_value = the_rule.description -%} @@ -50,11 +53,11 @@ Rules in ESLint are grouped by type to help you understand their purpose. Each r {%- if rules.deprecated -%} -

{{ rules.deprecated.name }}

+

{{ rules_categories.deprecated.displayName }}

-{{ rules.deprecated.description | safe }} +{{ rules_categories.deprecated.description | safe }} -{%- for the_rule in rules.deprecated.rules -%} +{%- for the_rule in rules.deprecated -%} {%- set name_value = the_rule.name -%} {%- set isReplacedBy = the_rule.replacedBy -%} @@ -68,11 +71,11 @@ Rules in ESLint are grouped by type to help you understand their purpose. Each r {%- if rules.removed -%} -

{{ rules.removed.name }}

+

{{ rules_categories.removed.displayName }}

-{{ rules.removed.description | safe }} +{{ rules_categories.removed.description | safe }} -{%- for the_rule in rules.removed.rules -%} +{%- for the_rule in rules.removed -%} {%- set name_value = the_rule.removed -%} {%- set isReplacedBy = the_rule.replacedBy -%} From 61b90839633ef300ac7707a651f65f532e65f42d Mon Sep 17 00:00:00 2001 From: Zhongyuan Zhou Date: Wed, 11 Oct 2023 23:40:58 +0800 Subject: [PATCH 214/248] docs: Make no-continue example code work (#17643) Update no-continue.md make code works without errors --- docs/src/rules/no-continue.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/rules/no-continue.md b/docs/src/rules/no-continue.md index af5cd8f98f16..fd0df533dec2 100644 --- a/docs/src/rules/no-continue.md +++ b/docs/src/rules/no-continue.md @@ -15,7 +15,7 @@ for(i = 0; i < 10; i++) { continue; } - a += i; + sum += i; } ``` @@ -38,7 +38,7 @@ for(i = 0; i < 10; i++) { continue; } - a += i; + sum += i; } ``` @@ -57,7 +57,7 @@ labeledLoop: for(i = 0; i < 10; i++) { continue labeledLoop; } - a += i; + sum += i; } ``` @@ -75,7 +75,7 @@ var sum = 0, for(i = 0; i < 10; i++) { if(i < 5) { - a += i; + sum += i; } } ``` From 0bcb9a8db608a3d0bd2645f99e0707b9a9bbaaf0 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Wed, 11 Oct 2023 22:39:57 +0200 Subject: [PATCH 215/248] docs: Fix syntax errors in rule examples (#17633) * Fix code examples in rule docs * Update `id-length` * Restore example in `id-match` under option `properties` * Update docs/src/rules/constructor-super.md Co-authored-by: Nicholas C. Zakas * Update docs/src/rules/no-useless-rename.md Co-authored-by: Milos Djermanovic --------- Co-authored-by: Nicholas C. Zakas Co-authored-by: Milos Djermanovic --- docs/src/rules/arrow-body-style.md | 28 ++-- docs/src/rules/camelcase.md | 18 +-- docs/src/rules/class-methods-use-this.md | 4 +- docs/src/rules/constructor-super.md | 22 ++-- docs/src/rules/id-denylist.md | 28 ++-- docs/src/rules/id-length.md | 48 +++---- docs/src/rules/id-match.md | 18 +-- docs/src/rules/indent-legacy.md | 120 +++++++++--------- docs/src/rules/indent.md | 80 ++++++------ docs/src/rules/jsx-quotes.md | 24 ++-- docs/src/rules/keyword-spacing.md | 34 ++--- docs/src/rules/lines-around-comment.md | 2 +- docs/src/rules/lines-between-class-members.md | 16 ++- docs/src/rules/max-params.md | 8 +- docs/src/rules/max-statements.md | 4 +- docs/src/rules/newline-after-var.md | 24 ++-- docs/src/rules/no-dupe-class-members.md | 20 +-- docs/src/rules/no-empty-static-block.md | 2 +- docs/src/rules/no-extra-parens.md | 22 ++-- docs/src/rules/no-invalid-this.md | 4 +- docs/src/rules/no-loss-of-precision.md | 26 ++-- .../rules/no-misleading-character-class.md | 34 ++--- docs/src/rules/no-multi-assign.md | 10 +- docs/src/rules/no-restricted-exports.md | 27 ++++ docs/src/rules/no-restricted-imports.md | 2 +- docs/src/rules/no-sequences.md | 8 +- docs/src/rules/no-this-before-super.md | 14 +- docs/src/rules/no-underscore-dangle.md | 52 ++++---- docs/src/rules/no-unexpected-multiline.md | 6 +- .../rules/no-unused-private-class-members.md | 16 +-- docs/src/rules/no-useless-constructor.md | 6 +- docs/src/rules/no-useless-rename.md | 43 ++++--- docs/src/rules/object-curly-newline.md | 14 +- .../src/rules/one-var-declaration-per-line.md | 20 +-- docs/src/rules/one-var.md | 14 +- docs/src/rules/prefer-const.md | 52 ++++---- docs/src/rules/prefer-destructuring.md | 11 +- docs/src/rules/require-unicode-regexp.md | 2 +- docs/src/rules/rest-spread-spacing.md | 16 +-- docs/src/rules/sort-imports.md | 56 +++++++- docs/src/rules/sort-keys.md | 80 ++++++------ docs/src/rules/space-before-keywords.md | 6 +- docs/src/rules/space-infix-ops.md | 4 +- 43 files changed, 580 insertions(+), 465 deletions(-) diff --git a/docs/src/rules/arrow-body-style.md b/docs/src/rules/arrow-body-style.md index 1975af13eed9..1bfdb5a73084 100644 --- a/docs/src/rules/arrow-body-style.md +++ b/docs/src/rules/arrow-body-style.md @@ -45,7 +45,7 @@ Examples of **correct** code for this rule with the `"always"` option: let foo = () => { return 0; }; -let foo = (retv, name) => { +let bar = (retv, name) => { retv[name] = true; return retv; }; @@ -66,7 +66,7 @@ Examples of **incorrect** code for this rule with the default `"as-needed"` opti let foo = () => { return 0; }; -let foo = () => { +let bar = () => { return { bar: { foo: 1, @@ -86,24 +86,24 @@ Examples of **correct** code for this rule with the default `"as-needed"` option /*eslint arrow-body-style: ["error", "as-needed"]*/ /*eslint-env es6*/ -let foo = () => 0; -let foo = (retv, name) => { +let foo1 = () => 0; +let foo2 = (retv, name) => { retv[name] = true; return retv; }; -let foo = () => ({ +let foo3 = () => ({ bar: { foo: 1, bar: 2, } }); -let foo = () => { bar(); }; -let foo = () => {}; -let foo = () => { /* do nothing */ }; -let foo = () => { +let foo4 = () => { bar(); }; +let foo5 = () => {}; +let foo6 = () => { /* do nothing */ }; +let foo7 = () => { // do nothing. }; -let foo = () => ({ bar: 0 }); +let foo8 = () => ({ bar: 0 }); ``` ::: @@ -120,7 +120,7 @@ Examples of **incorrect** code for this rule with the `{ "requireReturnForObject /*eslint arrow-body-style: ["error", "as-needed", { "requireReturnForObjectLiteral": true }]*/ /*eslint-env es6*/ let foo = () => ({}); -let foo = () => ({ bar: 0 }); +let bar = () => ({ bar: 0 }); ``` ::: @@ -134,7 +134,7 @@ Examples of **correct** code for this rule with the `{ "requireReturnForObjectLi /*eslint-env es6*/ let foo = () => {}; -let foo = () => { return { bar: 0 }; }; +let bar = () => { return { bar: 0 }; }; ``` ::: @@ -152,7 +152,7 @@ Examples of **incorrect** code for this rule with the `"never"` option: let foo = () => { return 0; }; -let foo = (retv, name) => { +let bar = (retv, name) => { retv[name] = true; return retv; }; @@ -169,7 +169,7 @@ Examples of **correct** code for this rule with the `"never"` option: /*eslint-env es6*/ let foo = () => 0; -let foo = () => ({ foo: 0 }); +let bar = () => ({ foo: 0 }); ``` ::: diff --git a/docs/src/rules/camelcase.md b/docs/src/rules/camelcase.md index aa75b231becd..536fba9439b8 100644 --- a/docs/src/rules/camelcase.md +++ b/docs/src/rules/camelcase.md @@ -49,11 +49,11 @@ function foo({ no_camelcased }) { // ... }; -function foo({ isCamelcased: no_camelcased }) { +function bar({ isCamelcased: no_camelcased }) { // ... } -function foo({ no_camelcased = 'default value' }) { +function baz({ no_camelcased = 'default value' }) { // ... }; @@ -63,7 +63,7 @@ var obj = { var { category_id = 1 } = query; -var { foo: no_camelcased } = bar; +var { foo: snake_cased } = bar; var { foo: bar_baz = 1 } = quz; ``` @@ -83,8 +83,8 @@ var myFavoriteColor = "#112C85"; var _myFavoriteColor = "#112C85"; var myFavoriteColor_ = "#112C85"; var MY_FAVORITE_COLOR = "#112C85"; -var foo = bar.baz_boom; -var foo = { qux: bar.baz_boom }; +var foo1 = bar.baz_boom; +var foo2 = { qux: bar.baz_boom }; obj.do_something(); do_something(); @@ -96,11 +96,11 @@ function foo({ isCamelCased }) { // ... }; -function foo({ isCamelCased: isAlsoCamelCased }) { +function bar({ isCamelCased: isAlsoCamelCased }) { // ... } -function foo({ isCamelCased = 'default value' }) { +function baz({ isCamelCased = 'default value' }) { // ... }; @@ -143,9 +143,9 @@ Examples of **incorrect** code for this rule with the default `{ "ignoreDestruct var { category_id } = query; -var { category_id = 1 } = query; +var { category_name = 1 } = query; -var { category_id: category_id } = query; +var { category_id: category_title } = query; var { category_id: category_alias } = query; diff --git a/docs/src/rules/class-methods-use-this.md b/docs/src/rules/class-methods-use-this.md index 2269fac8effd..5a12f9af3c93 100644 --- a/docs/src/rules/class-methods-use-this.md +++ b/docs/src/rules/class-methods-use-this.md @@ -86,13 +86,13 @@ class A { } } -class A { +class B { constructor() { // OK. constructor is exempt } } -class A { +class C { static foo() { // OK. static methods aren't expected to use this. } diff --git a/docs/src/rules/constructor-super.md b/docs/src/rules/constructor-super.md index 7c19df77dbab..c6f008f13d6d 100644 --- a/docs/src/rules/constructor-super.md +++ b/docs/src/rules/constructor-super.md @@ -14,6 +14,16 @@ This rule checks whether or not there is a valid `super()` call. This rule is aimed to flag invalid/missing `super()` calls. +This is a syntax error because there is no `extends` clause in the class: + +```js +class A { + constructor() { + super(); + } +} +``` + Examples of **incorrect** code for this rule: :::incorrect @@ -22,24 +32,18 @@ Examples of **incorrect** code for this rule: /*eslint constructor-super: "error"*/ /*eslint-env es6*/ -class A { - constructor() { - super(); // This is a SyntaxError. - } -} - class A extends B { constructor() { } // Would throw a ReferenceError. } // Classes which inherits from a non constructor are always problems. -class A extends null { +class C extends null { constructor() { super(); // Would throw a TypeError. } } -class A extends null { +class D extends null { constructor() { } // Would throw a ReferenceError. } ``` @@ -58,7 +62,7 @@ class A { constructor() { } } -class A extends B { +class B extends C { constructor() { super(); } diff --git a/docs/src/rules/id-denylist.md b/docs/src/rules/id-denylist.md index 850f5923e830..82859ec8b430 100644 --- a/docs/src/rules/id-denylist.md +++ b/docs/src/rules/id-denylist.md @@ -46,7 +46,7 @@ Examples of **incorrect** code for this rule with sample `"data", "callback"` re ```js /*eslint id-denylist: ["error", "data", "callback"] */ -var data = {...}; +var data = { ...values }; function callback() { // ... @@ -57,23 +57,23 @@ element.callback = function() { }; var itemSet = { - data: [...] + data: [...values] }; class Foo { data = []; } -class Foo { +class Bar { #data = []; } -class Foo { - callback( {); +class Baz { + callback() {} } -class Foo { - #callback( {); +class Qux { + #callback() {} } ``` @@ -86,7 +86,7 @@ Examples of **correct** code for this rule with sample `"data", "callback"` rest ```js /*eslint id-denylist: ["error", "data", "callback"] */ -var encodingOptions = {...}; +var encodingOptions = {...values}; function processFileResult() { // ... @@ -97,7 +97,7 @@ element.successHandler = function() { }; var itemSet = { - entities: [...] + entities: [...values] }; callback(); // all function calls are ignored @@ -110,16 +110,16 @@ class Foo { items = []; } -class Foo { +class Bar { #items = []; } -class Foo { - method( {); +class Baz { + method() {} } -class Foo { - #method( {); +class Qux { + #method() {} } ``` diff --git a/docs/src/rules/id-length.md b/docs/src/rules/id-length.md index aed5ada0a7c6..ea5d2fa9d1ab 100644 --- a/docs/src/rules/id-length.md +++ b/docs/src/rules/id-length.md @@ -41,16 +41,16 @@ try { } var myObj = { a: 1 }; (a) => { a * a }; -class x { } +class y { } class Foo { x() {} } -class Foo { #x() {} } -class Foo { x = 1 } -class Foo { #x = 1 } -function foo(...x) { } -function foo([x]) { } +class Bar { #x() {} } +class Baz { x = 1 } +class Qux { #x = 1 } +function bar(...x) { } +function baz([x]) { } var [x] = arr; var { prop: [x]} = {}; -function foo({x}) { } +function qux({x}) { } var { x } = {}; var { prop: a} = {}; ({ prop: obj.x } = {}); @@ -78,19 +78,19 @@ try { } var myObj = { apple: 1 }; (num) => { num * num }; -function foo(num = 0) { } +function bar(num = 0) { } class MyClass { } class Foo { method() {} } -class Foo { #method() {} } -class Foo { field = 1 } -class Foo { #field = 1 } -function foo(...args) { } -function foo([longName]) { } +class Bar { #method() {} } +class Baz { field = 1 } +class Qux { #field = 1 } +function baz(...args) { } +function qux([longName]) { } var { prop } = {}; var { prop: [longName] } = {}; var [longName] = arr; -function foo({ prop }) { } -function foo({ a: prop }) { } +function foobar({ prop }) { } +function foobaz({ a: prop }) { } var { prop } = {}; var { a: prop } = {}; ({ prop: obj.longName } = {}); @@ -129,9 +129,9 @@ try { } var myObj = { a: 1 }; (val) => { val * val }; -class x { } +class y { } class Foo { x() {} } -function foo(...x) { } +function bar(...x) { } var { x } = {}; var { prop: a} = {}; var [x] = arr; @@ -151,7 +151,7 @@ Examples of **correct** code for this rule with the `{ "min": 4 }` option: var value = 5; function func() { return 42; } -obj.element = document.body; +object.element = document.body; var foobar = function (event) { /* do stuff */ }; try { dangerousStuff(); @@ -168,7 +168,7 @@ var { prop } = {}; var [longName] = foo; var { a: [prop] } = {}; var { a: longName } = {}; -({ prop: obj.name } = {}); +({ prop: object.name } = {}); var data = { "x": 1 }; // excused because of quotes data["y"] = 3; // excused because of calculated property access ``` @@ -242,16 +242,16 @@ var myObj = { a: 1 }; ### exceptions -Examples of additional **correct** code for this rule with the `{ "exceptions": ["x"] }` option: +Examples of additional **correct** code for this rule with the `{ "exceptions": ["x", "y", "z", "ฮถ"] }` option: ::: correct ```js -/*eslint id-length: ["error", { "exceptions": ["x"] }]*/ +/*eslint id-length: ["error", { "exceptions": ["x", "y", "z", "ฮถ"] }]*/ /*eslint-env es6*/ var x = 5; -function x() { return 42; } +function y() { return 42; } obj.x = document.body; var foo = function (x) { /* do stuff */ }; try { @@ -261,8 +261,8 @@ try { } (x) => { return x * x; }; var [x] = arr; -const { x } = foo; -const { a: x } = foo; +const { z } = foo; +const { a: ฮถ } = foo; ``` ::: diff --git a/docs/src/rules/id-match.md b/docs/src/rules/id-match.md index 08d5ff3ba9c4..6d15d19e859b 100644 --- a/docs/src/rules/id-match.md +++ b/docs/src/rules/id-match.md @@ -42,17 +42,13 @@ function do_something() { // ... } -obj.do_something = function() { - // ... -}; - class My_Class {} class myClass { do_something() {} } -class myClass { +class anotherClass { #do_something() {} } ``` @@ -76,11 +72,11 @@ var obj = { class myClass {} -class myClass { +class anotherClass { doSomething() {} } -class myClass { +class oneMoreClass { #doSomething() {} } ``` @@ -110,6 +106,10 @@ Examples of **incorrect** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$", var obj = { my_pref: 1 }; + +obj.do_something = function() { + // ... +}; ``` ::: @@ -121,13 +121,13 @@ Examples of **incorrect** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$", ::: incorrect ```js -/*eslint id-match: ["error", "^[a-z]+([A-Z][a-z]+)*$", { "properties": true }]*/ +/*eslint id-match: ["error", "^[a-z]+([A-Z][a-z]+)*$", { "classFields": true }]*/ class myClass { my_pref = 1; } -class myClass { +class anotherClass { #my_pref = 1; } ``` diff --git a/docs/src/rules/indent-legacy.md b/docs/src/rules/indent-legacy.md index 97372ebc8ff0..c3543133be4c 100644 --- a/docs/src/rules/indent-legacy.md +++ b/docs/src/rules/indent-legacy.md @@ -39,7 +39,7 @@ For example, for 2-space indentation: ```json { - "indent": ["error", 2] + "indent-legacy": ["error", 2] } ``` @@ -47,7 +47,7 @@ Or for tabbed indentation: ```json { - "indent": ["error", "tab"] + "indent-legacy": ["error", "tab"] } ``` @@ -56,7 +56,7 @@ Examples of **incorrect** code for this rule with the default options: ::: incorrect ```js -/*eslint indent: "error"*/ +/*eslint indent-legacy: "error"*/ if (a) { b=c; @@ -73,7 +73,7 @@ Examples of **correct** code for this rule with the default options: ::: correct ```js -/*eslint indent: "error"*/ +/*eslint indent-legacy: "error"*/ if (a) { b=c; @@ -126,7 +126,7 @@ Examples of **incorrect** code for this rule with the `"tab"` option: ::: incorrect ```js -/*eslint indent: ["error", "tab"]*/ +/*eslint indent-legacy: ["error", "tab"]*/ if (a) { b=c; @@ -143,7 +143,7 @@ Examples of **correct** code for this rule with the `"tab"` option: ::: correct ```js -/*eslint indent: ["error", "tab"]*/ +/*eslint indent-legacy: ["error", "tab"]*/ if (a) { /*tab*/b=c; @@ -162,7 +162,7 @@ Examples of **incorrect** code for this rule with the `2, { "SwitchCase": 1 }` o ::: incorrect ```js -/*eslint indent: ["error", 2, { "SwitchCase": 1 }]*/ +/*eslint indent-legacy: ["error", 2, { "SwitchCase": 1 }]*/ switch(a){ case "a": @@ -179,7 +179,7 @@ Examples of **correct** code for this rule with the `2, { "SwitchCase": 1 }` opt ::: correct ```js -/*eslint indent: ["error", 2, { "SwitchCase": 1 }]*/ +/*eslint indent-legacy: ["error", 2, { "SwitchCase": 1 }]*/ switch(a){ case "a": @@ -198,18 +198,18 @@ Examples of **incorrect** code for this rule with the `2, { "VariableDeclarator" ::: incorrect ```js -/*eslint indent: ["error", 2, { "VariableDeclarator": 1 }]*/ +/*eslint indent-legacy: ["error", 2, { "VariableDeclarator": 1 }]*/ /*eslint-env es6*/ var a, b, c; -let a, - b, - c; -const a = 1, - b = 2, - c = 3; +let d, + e, + f; +const g = 1, + h = 2, + i = 3; ``` ::: @@ -219,18 +219,18 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ::: correct ```js -/*eslint indent: ["error", 2, { "VariableDeclarator": 1 }]*/ +/*eslint indent-legacy: ["error", 2, { "VariableDeclarator": 1 }]*/ /*eslint-env es6*/ var a, b, c; -let a, - b, - c; -const a = 1, - b = 2, - c = 3; +let d, + e, + f; +const g = 1, + h = 2, + i = 3; ``` ::: @@ -240,18 +240,18 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ::: correct ```js -/*eslint indent: ["error", 2, { "VariableDeclarator": 2 }]*/ +/*eslint indent-legacy: ["error", 2, { "VariableDeclarator": 2 }]*/ /*eslint-env es6*/ var a, b, c; -let a, - b, - c; -const a = 1, - b = 2, - c = 3; +let d, + e, + f; +const g = 1, + h = 2, + i = 3; ``` ::: @@ -261,18 +261,18 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ::: correct ```js -/*eslint indent: ["error", 2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }]*/ +/*eslint indent-legacy: ["error", 2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }]*/ /*eslint-env es6*/ var a, b, c; -let a, - b, - c; -const a = 1, - b = 2, - c = 3; +let e, + f, + g; +const h = 1, + i = 2, + j = 3; ``` ::: @@ -284,7 +284,7 @@ Examples of **incorrect** code for this rule with the options `2, { "outerIIFEBo ::: incorrect ```js -/*eslint indent: ["error", 2, { "outerIIFEBody": 0 }]*/ +/*eslint indent-legacy: ["error", 2, { "outerIIFEBody": 0 }]*/ (function() { @@ -306,7 +306,7 @@ Examples of **correct** code for this rule with the options `2, {"outerIIFEBody" ::: correct ```js -/*eslint indent: ["error", 2, { "outerIIFEBody": 0 }]*/ +/*eslint indent-legacy: ["error", 2, { "outerIIFEBody": 0 }]*/ (function() { @@ -330,7 +330,7 @@ Examples of **incorrect** code for this rule with the `2, { "MemberExpression": ::: incorrect ```js -/*eslint indent: ["error", 2, { "MemberExpression": 1 }]*/ +/*eslint indent-legacy: ["error", 2, { "MemberExpression": 1 }]*/ foo .bar @@ -344,7 +344,7 @@ Examples of **correct** code for this rule with the `2, { "MemberExpression": 1 ::: correct ```js -/*eslint indent: ["error", 2, { "MemberExpression": 1 }]*/ +/*eslint indent-legacy: ["error", 2, { "MemberExpression": 1 }]*/ foo .bar @@ -364,7 +364,7 @@ Examples of **incorrect** code for this rule with the `2, { "FunctionDeclaration ::: incorrect ```js -/*eslint indent: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ +/*eslint indent-legacy: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ function foo(bar, baz, @@ -380,7 +380,7 @@ Examples of **correct** code for this rule with the `2, { "FunctionDeclaration": ::: correct ```js -/*eslint indent: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ +/*eslint indent-legacy: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ function foo(bar, baz, @@ -396,7 +396,7 @@ Examples of **incorrect** code for this rule with the `2, { "FunctionDeclaration ::: incorrect ```js -/*eslint indent: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ +/*eslint indent-legacy: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ function foo(bar, baz, qux, boop) { @@ -411,7 +411,7 @@ Examples of **correct** code for this rule with the `2, { "FunctionDeclaration": ::: correct ```js -/*eslint indent: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ +/*eslint indent-legacy: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ function foo(bar, baz, qux, boop) { @@ -428,7 +428,7 @@ Examples of **incorrect** code for this rule with the `2, { "FunctionExpression" ::: incorrect ```js -/*eslint indent: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ +/*eslint indent-legacy: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ var foo = function(bar, baz, @@ -444,7 +444,7 @@ Examples of **correct** code for this rule with the `2, { "FunctionExpression": ::: correct ```js -/*eslint indent: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ +/*eslint indent-legacy: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ var foo = function(bar, baz, @@ -460,7 +460,7 @@ Examples of **incorrect** code for this rule with the `2, { "FunctionExpression" ::: incorrect ```js -/*eslint indent: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ +/*eslint indent-legacy: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ var foo = function(bar, baz, qux, boop) { @@ -475,7 +475,7 @@ Examples of **correct** code for this rule with the `2, { "FunctionExpression": ::: correct ```js -/*eslint indent: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ +/*eslint indent-legacy: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ var foo = function(bar, baz, qux, boop) { @@ -492,7 +492,7 @@ Examples of **incorrect** code for this rule with the `2, { "CallExpression": {" ::: incorrect ```js -/*eslint indent: ["error", 2, { "CallExpression": {"arguments": 1} }]*/ +/*eslint indent-legacy: ["error", 2, { "CallExpression": {"arguments": 1} }]*/ foo(bar, baz, @@ -507,7 +507,7 @@ Examples of **correct** code for this rule with the `2, { "CallExpression": {"ar ::: correct ```js -/*eslint indent: ["error", 2, { "CallExpression": {"arguments": 1} }]*/ +/*eslint indent-legacy: ["error", 2, { "CallExpression": {"arguments": 1} }]*/ foo(bar, baz, @@ -522,7 +522,7 @@ Examples of **incorrect** code for this rule with the `2, { "CallExpression": {" ::: incorrect ```js -/*eslint indent: ["error", 2, {"CallExpression": {"arguments": "first"}}]*/ +/*eslint indent-legacy: ["error", 2, {"CallExpression": {"arguments": "first"}}]*/ foo(bar, baz, baz, boop, beep); @@ -535,7 +535,7 @@ Examples of **correct** code for this rule with the `2, { "CallExpression": {"ar ::: correct ```js -/*eslint indent: ["error", 2, {"CallExpression": {"arguments": "first"}}]*/ +/*eslint indent-legacy: ["error", 2, {"CallExpression": {"arguments": "first"}}]*/ foo(bar, baz, baz, boop, beep); @@ -550,7 +550,7 @@ Examples of **incorrect** code for this rule with the `2, { "ArrayExpression": 1 ::: incorrect ```js -/*eslint indent: ["error", 2, { "ArrayExpression": 1 }]*/ +/*eslint indent-legacy: ["error", 2, { "ArrayExpression": 1 }]*/ var foo = [ bar, @@ -566,7 +566,7 @@ Examples of **correct** code for this rule with the `2, { "ArrayExpression": 1 } ::: correct ```js -/*eslint indent: ["error", 2, { "ArrayExpression": 1 }]*/ +/*eslint indent-legacy: ["error", 2, { "ArrayExpression": 1 }]*/ var foo = [ bar, @@ -582,7 +582,7 @@ Examples of **incorrect** code for this rule with the `2, { "ArrayExpression": " ::: incorrect ```js -/*eslint indent: ["error", 2, {"ArrayExpression": "first"}]*/ +/*eslint indent-legacy: ["error", 2, {"ArrayExpression": "first"}]*/ var foo = [bar, baz, @@ -597,7 +597,7 @@ Examples of **correct** code for this rule with the `2, { "ArrayExpression": "fi ::: correct ```js -/*eslint indent: ["error", 2, {"ArrayExpression": "first"}]*/ +/*eslint indent-legacy: ["error", 2, {"ArrayExpression": "first"}]*/ var foo = [bar, baz, @@ -614,7 +614,7 @@ Examples of **incorrect** code for this rule with the `2, { "ObjectExpression": ::: incorrect ```js -/*eslint indent: ["error", 2, { "ObjectExpression": 1 }]*/ +/*eslint indent-legacy: ["error", 2, { "ObjectExpression": 1 }]*/ var foo = { bar: 1, @@ -630,7 +630,7 @@ Examples of **correct** code for this rule with the `2, { "ObjectExpression": 1 ::: correct ```js -/*eslint indent: ["error", 2, { "ObjectExpression": 1 }]*/ +/*eslint indent-legacy: ["error", 2, { "ObjectExpression": 1 }]*/ var foo = { bar: 1, @@ -646,7 +646,7 @@ Examples of **incorrect** code for this rule with the `2, { "ObjectExpression": ::: incorrect ```js -/*eslint indent: ["error", 2, {"ObjectExpression": "first"}]*/ +/*eslint indent-legacy: ["error", 2, {"ObjectExpression": "first"}]*/ var foo = { bar: 1, baz: 2 }; @@ -659,7 +659,7 @@ Examples of **correct** code for this rule with the `2, { "ObjectExpression": "f ::: correct ```js -/*eslint indent: ["error", 2, {"ObjectExpression": "first"}]*/ +/*eslint indent-legacy: ["error", 2, {"ObjectExpression": "first"}]*/ var foo = { bar: 1, baz: 2 }; diff --git a/docs/src/rules/indent.md b/docs/src/rules/indent.md index 7f81c0fd57bc..e53a1f9a6e1b 100644 --- a/docs/src/rules/indent.md +++ b/docs/src/rules/indent.md @@ -248,12 +248,12 @@ Examples of **incorrect** code for this rule with the `2, { "VariableDeclarator" var a, b, c; -let a, - b, - c; -const a = 1, - b = 2, - c = 3; +let d, + e, + f; +const g = 1, + h = 2, + i = 3; ``` ::: @@ -269,12 +269,12 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": var a, b, c; -let a, - b, - c; -const a = 1, - b = 2, - c = 3; +let d, + e, + f; +const g = 1, + h = 2, + i = 3; ``` ::: @@ -290,12 +290,12 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": var a, b, c; -let a, - b, - c; -const a = 1, - b = 2, - c = 3; +let d, + e, + f; +const g = 1, + h = 2, + i = 3; ``` ::: @@ -311,12 +311,12 @@ Examples of **incorrect** code for this rule with the `2, { "VariableDeclarator" var a, b, c; -let a, - b, - c; -const a = 1, - b = 2, - c = 3; +let d, + e, + f; +const g = 1, + h = 2, + i = 3; ``` ::: @@ -332,12 +332,12 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": var a, b, c; -let a, - b, - c; -const a = 1, - b = 2, - c = 3; +let d, + e, + f; +const g = 1, + h = 2, + i = 3; ``` ::: @@ -353,12 +353,12 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": var a, b, c; -let a, - b, - c; -const a = 1, - b = 2, - c = 3; +let d, + e, + f; +const g = 1, + h = 2, + i = 3; ``` ::: @@ -858,6 +858,14 @@ import { foo, bar, baz, } from 'qux'; +``` + +::: + +::: correct + +```js +/*eslint indent: ["error", 4, { "ImportDeclaration": 1 }]*/ import { foo, diff --git a/docs/src/rules/jsx-quotes.md b/docs/src/rules/jsx-quotes.md index ddb20002b8a0..4e0799687550 100644 --- a/docs/src/rules/jsx-quotes.md +++ b/docs/src/rules/jsx-quotes.md @@ -9,17 +9,17 @@ related_rules: JSX attribute values can contain string literals, which are delimited with single or double quotes. -```xml - - +```jsx +; +; ``` Unlike string literals in JavaScript, string literals within JSX attributes canโ€™t contain escaped quotes. If you want to have e.g. a double quote within a JSX attribute value, you have to use single quotes as string delimiter. -```xml - - +```jsx +; +; ``` ## Rule Details @@ -42,7 +42,7 @@ Examples of **incorrect** code for this rule with the default `"prefer-double"` ```jsx /*eslint jsx-quotes: ["error", "prefer-double"]*/ - +; ``` ::: @@ -54,8 +54,8 @@ Examples of **correct** code for this rule with the default `"prefer-double"` op ```jsx /*eslint jsx-quotes: ["error", "prefer-double"]*/ - - +; +; ``` ::: @@ -69,7 +69,7 @@ Examples of **incorrect** code for this rule with the `"prefer-single"` option: ```jsx /*eslint jsx-quotes: ["error", "prefer-single"]*/ - +; ``` ::: @@ -81,8 +81,8 @@ Examples of **correct** code for this rule with the `"prefer-single"` option: ```jsx /*eslint jsx-quotes: ["error", "prefer-single"]*/ - - +; +; ``` ::: diff --git a/docs/src/rules/keyword-spacing.md b/docs/src/rules/keyword-spacing.md index 9c1ca55fc380..0ffe1e13df63 100644 --- a/docs/src/rules/keyword-spacing.md +++ b/docs/src/rules/keyword-spacing.md @@ -77,30 +77,30 @@ let a = [this]; let b = [function() {}]; // Avoid conflict with `arrow-spacing` -let a = ()=> this.foo; +let c = ()=> this.foo; // Avoid conflict with `block-spacing` {function foo() {}} // Avoid conflict with `comma-spacing` -let a = [100,this.foo, this.bar]; +let d = [100,this.foo, this.bar]; // Avoid conflict with `computed-property-spacing` obj[this.foo] = 0; // Avoid conflict with `generator-star-spacing` -function *foo() {} +function *bar() {} // Avoid conflict with `key-spacing` -let obj = { +let obj1 = { foo:function() {} }; // Avoid conflict with `object-curly-spacing` -let obj = {foo: this}; +let obj2 = {foo: this}; // Avoid conflict with `semi-spacing` -let a = this;function foo() {} +let e = this;function foo() {} // Avoid conflict with `space-in-parens` (function () {})(); @@ -110,7 +110,7 @@ if ("foo"in {foo: 0}) {} if (10+this.foo<= this.bar) {} // Avoid conflict with `jsx-curly-spacing` -let a = +let f = ``` ::: @@ -190,10 +190,10 @@ if (foo) { let a = [this]; // Avoid conflict with `arrow-spacing` -let a = ()=> this.foo; +let b = ()=> this.foo; // Avoid conflict with `comma-spacing` -let a = [100, this.foo, this.bar]; +let c = [100, this.foo, this.bar]; // Avoid conflict with `computed-property-spacing` obj[this.foo] = 0; @@ -202,42 +202,42 @@ obj[this.foo] = 0; function* foo() {} // Avoid conflict with `key-spacing` -let obj = { +let obj1 = { foo:function() {} }; // Avoid conflict with `func-call-spacing` -class A { +class A extends B { constructor() { super(); } } // Avoid conflict with `object-curly-spacing` -let obj = {foo: this}; +let obj2 = {foo: this}; // Avoid conflict with `semi-spacing` -let a = this;function foo() {} +let d = this;function bar() {} // Avoid conflict with `space-before-function-paren` -function() {} +(function() {})(); // Avoid conflict with `space-infix-ops` if ("foo"in{foo: 0}) {} if (10+this.foo<= this.bar) {} // Avoid conflict with `space-unary-ops` -function* foo(a) { +function* baz(a) { return yield+a; } // Avoid conflict with `yield-star-spacing` -function* foo(a) { +function* qux(a) { return yield* a; } // Avoid conflict with `jsx-curly-spacing` -let a = +let e = ``` ::: diff --git a/docs/src/rules/lines-around-comment.md b/docs/src/rules/lines-around-comment.md index 924ba66cf1a4..0cafa6df7036 100644 --- a/docs/src/rules/lines-around-comment.md +++ b/docs/src/rules/lines-around-comment.md @@ -663,7 +663,7 @@ Examples of **correct** code for the `ignorePattern` option: /*eslint lines-around-comment: ["error"]*/ foo(); -/* eslint mentioned in this comment */, +/* eslint mentioned in this comment */ bar(); /*eslint lines-around-comment: ["error", { "ignorePattern": "pragma" }] */ diff --git a/docs/src/rules/lines-between-class-members.md b/docs/src/rules/lines-between-class-members.md index 55627501096a..42c20b92ddda 100644 --- a/docs/src/rules/lines-between-class-members.md +++ b/docs/src/rules/lines-between-class-members.md @@ -97,9 +97,15 @@ class Foo{ bar(){} baz(){} } +``` + +::: +::: incorrect + +```js /* eslint lines-between-class-members: ["error", "never"]*/ -class Foo{ +class Bar{ x; bar(){} @@ -123,9 +129,15 @@ class Foo{ baz(){} } +``` + +::: +::: correct + +```js /* eslint lines-between-class-members: ["error", "never"]*/ -class Foo{ +class Bar{ x; bar(){} baz(){} diff --git a/docs/src/rules/max-params.md b/docs/src/rules/max-params.md index 681723fb8117..b0a55e9009fa 100644 --- a/docs/src/rules/max-params.md +++ b/docs/src/rules/max-params.md @@ -42,11 +42,11 @@ Examples of **incorrect** code for this rule with the default `{ "max": 3 }` opt /*eslint max-params: ["error", 3]*/ /*eslint-env es6*/ -function foo (bar, baz, qux, qxx) { +function foo1 (bar, baz, qux, qxx) { doSomething(); } -let foo = (bar, baz, qux, qxx) => { +let foo2 = (bar, baz, qux, qxx) => { doSomething(); }; ``` @@ -61,11 +61,11 @@ Examples of **correct** code for this rule with the default `{ "max": 3 }` optio /*eslint max-params: ["error", 3]*/ /*eslint-env es6*/ -function foo (bar, baz, qux) { +function foo1 (bar, baz, qux) { doSomething(); } -let foo = (bar, baz, qux) => { +let foo2 = (bar, baz, qux) => { doSomething(); }; ``` diff --git a/docs/src/rules/max-statements.md b/docs/src/rules/max-statements.md index d41f7f2de4d3..4462011db742 100644 --- a/docs/src/rules/max-statements.md +++ b/docs/src/rules/max-statements.md @@ -63,7 +63,7 @@ function foo() { var foo11 = 11; // Too many. } -let foo = () => { +let bar = () => { var foo1 = 1; var foo2 = 2; var foo3 = 3; @@ -109,7 +109,7 @@ function foo() { }; } -let foo = () => { +let bar = () => { var foo1 = 1; var foo2 = 2; var foo3 = 3; diff --git a/docs/src/rules/newline-after-var.md b/docs/src/rules/newline-after-var.md index db11e25471c3..9d6dc216ecec 100644 --- a/docs/src/rules/newline-after-var.md +++ b/docs/src/rules/newline-after-var.md @@ -52,9 +52,9 @@ var greet = "hello,", name = "world"; console.log(greet, name); -let greet = "hello,", - name = "world"; -console.log(greet, name); +let hello = "hello,", + world = "world"; +console.log(hello, world); var greet = "hello,"; const NAME = "world"; @@ -81,10 +81,10 @@ var greet = "hello,", console.log(greet, name); -let greet = "hello,", - name = "world"; +let hello = "hello,", + world = "world"; -console.log(greet, name); +console.log(hello, world); var greet = "hello,"; const NAME = "world"; @@ -115,10 +115,10 @@ var greet = "hello,", console.log(greet, name); -let greet = "hello,", - name = "world"; +let hello = "hello,", + world = "world"; -console.log(greet, name); +console.log(hello, world); var greet = "hello,"; const NAME = "world"; @@ -146,9 +146,9 @@ var greet = "hello,", name = "world"; console.log(greet, name); -let greet = "hello,", - name = "world"; -console.log(greet, name); +let hello = "hello,", + world = "world"; +console.log(hello, world); var greet = "hello,"; const NAME = "world"; diff --git a/docs/src/rules/no-dupe-class-members.md b/docs/src/rules/no-dupe-class-members.md index 216f3c9fecd3..6565c9f15ee7 100644 --- a/docs/src/rules/no-dupe-class-members.md +++ b/docs/src/rules/no-dupe-class-members.md @@ -34,27 +34,27 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-dupe-class-members: "error"*/ -class Foo { +class A { bar() { } bar() { } } -class Foo { +class B { bar() { } get bar() { } } -class Foo { +class C { bar; bar; } -class Foo { +class D { bar; bar() { } } -class Foo { +class E { static bar() { } static bar() { } } @@ -69,27 +69,27 @@ Examples of **correct** code for this rule: ```js /*eslint no-dupe-class-members: "error"*/ -class Foo { +class A { bar() { } qux() { } } -class Foo { +class B { get bar() { } set bar(value) { } } -class Foo { +class C { bar; qux; } -class Foo { +class D { bar; qux() { } } -class Foo { +class E { static bar() { } bar() { } } diff --git a/docs/src/rules/no-empty-static-block.md b/docs/src/rules/no-empty-static-block.md index 283a4e21b776..1825e5c78321 100644 --- a/docs/src/rules/no-empty-static-block.md +++ b/docs/src/rules/no-empty-static-block.md @@ -42,7 +42,7 @@ class Foo { } } -class Foo { +class Bar { static { // comment } diff --git a/docs/src/rules/no-extra-parens.md b/docs/src/rules/no-extra-parens.md index 1a4d8e2d48a0..5fcbcc639269 100644 --- a/docs/src/rules/no-extra-parens.md +++ b/docs/src/rules/no-extra-parens.md @@ -83,8 +83,6 @@ typeof (a); (Object.prototype.toString.call()); -(function(){} ? a() : b()); - class A { [(x)] = 1; } @@ -218,8 +216,8 @@ Examples of **correct** code for this rule with the `all` and `{ "ignoreJSX": "a ```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "all" }] */ -const Component = (
) -const Component = ( +const ThisComponent = (
) +const ThatComponent = (
@@ -234,8 +232,8 @@ Examples of **incorrect** code for this rule with the `all` and `{ "ignoreJSX": ```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "multi-line" }] */ -const Component = (
) -const Component = (

) +const ThisComponent = (
) +const ThatComponent = (

) ``` ::: @@ -246,12 +244,12 @@ Examples of **correct** code for this rule with the `all` and `{ "ignoreJSX": "m ```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "multi-line" }] */ -const Component = ( +const ThisComponent = (

) -const Component = ( +const ThatComponent = (
@@ -266,12 +264,12 @@ Examples of **incorrect** code for this rule with the `all` and `{ "ignoreJSX": ```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "single-line" }] */ -const Component = ( +const ThisComponent = (

) -const Component = ( +const ThatComponent = (
@@ -286,8 +284,8 @@ Examples of **correct** code for this rule with the `all` and `{ "ignoreJSX": "s ```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "single-line" }] */ -const Component = (
) -const Component = (

) +const ThisComponent = (
) +const ThatComponent = (

) ``` ::: diff --git a/docs/src/rules/no-invalid-this.md b/docs/src/rules/no-invalid-this.md index 0c529a970650..454fdbd7185b 100644 --- a/docs/src/rules/no-invalid-this.md +++ b/docs/src/rules/no-invalid-this.md @@ -114,7 +114,7 @@ function Foo() { baz(() => this); } -class Foo { +class Bar { constructor() { // OK, this is in a constructor. this.a = 0; @@ -182,7 +182,7 @@ Foo.prototype.foo = function foo() { this.a = 0; }; -class Foo { +class Baz { // OK, this is in a class field initializer. a = this.b; diff --git a/docs/src/rules/no-loss-of-precision.md b/docs/src/rules/no-loss-of-precision.md index 1bec49266dc1..6674b15da6bf 100644 --- a/docs/src/rules/no-loss-of-precision.md +++ b/docs/src/rules/no-loss-of-precision.md @@ -18,12 +18,12 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-loss-of-precision: "error"*/ -const x = 9007199254740993 -const x = 5123000000000000000000000000001 -const x = 1230000000000000000000000.0 -const x = .1230000000000000000000000 -const x = 0X20000000000001 -const x = 0X2_000000000_0001; +const a = 9007199254740993 +const b = 5123000000000000000000000000001 +const c = 1230000000000000000000000.0 +const d = .1230000000000000000000000 +const e = 0X20000000000001 +const f = 0X2_000000000_0001; ``` ::: @@ -35,13 +35,13 @@ Examples of **correct** code for this rule: ```js /*eslint no-loss-of-precision: "error"*/ -const x = 12345 -const x = 123.456 -const x = 123e34 -const x = 12300000000000000000000000 -const x = 0x1FFFFFFFFFFFFF -const x = 9007199254740991 -const x = 9007_1992547409_91 +const a = 12345 +const b = 123.456 +const c = 123e34 +const d = 12300000000000000000000000 +const e = 0x1FFFFFFFFFFFFF +const f = 9007199254740991 +const g = 9007_1992547409_91 ``` ::: diff --git a/docs/src/rules/no-misleading-character-class.md b/docs/src/rules/no-misleading-character-class.md index e1b182c2ef50..792760f2641b 100644 --- a/docs/src/rules/no-misleading-character-class.md +++ b/docs/src/rules/no-misleading-character-class.md @@ -17,36 +17,36 @@ This rule reports the regular expressions which include multiple code point char The combining characters are characters which belong to one of `Mc`, `Me`, and `Mn` [Unicode general categories](http://www.unicode.org/L2/L1999/UnicodeData.html#General%20Category). ```js -/^[Aฬ]$/u.test("Aฬ") //โ†’ false -/^[โ‡๏ธ]$/u.test("โ‡๏ธ") //โ†’ false +/^[Aฬ]$/u.test("Aฬ"); //โ†’ false +/^[โ‡๏ธ]$/u.test("โ‡๏ธ"); //โ†’ false ``` **A character with Emoji modifiers:** ```js -/^[๐Ÿ‘ถ๐Ÿป]$/u.test("๐Ÿ‘ถ๐Ÿป") //โ†’ false -/^[๐Ÿ‘ถ๐Ÿฝ]$/u.test("๐Ÿ‘ถ๐Ÿฝ") //โ†’ false +/^[๐Ÿ‘ถ๐Ÿป]$/u.test("๐Ÿ‘ถ๐Ÿป"); //โ†’ false +/^[๐Ÿ‘ถ๐Ÿฝ]$/u.test("๐Ÿ‘ถ๐Ÿฝ"); //โ†’ false ``` **A pair of regional indicator symbols:** ```js -/^[๐Ÿ‡ฏ๐Ÿ‡ต]$/u.test("๐Ÿ‡ฏ๐Ÿ‡ต") //โ†’ false +/^[๐Ÿ‡ฏ๐Ÿ‡ต]$/u.test("๐Ÿ‡ฏ๐Ÿ‡ต"); //โ†’ false ``` **Characters that ZWJ joins:** ```js -/^[๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ]$/u.test("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ") //โ†’ false +/^[๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ]$/u.test("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ"); //โ†’ false ``` **A surrogate pair without Unicode flag:** ```js -/^[๐Ÿ‘]$/.test("๐Ÿ‘") //โ†’ false +/^[๐Ÿ‘]$/.test("๐Ÿ‘"); //โ†’ false // Surrogate pair is OK if with u flag. -/^[๐Ÿ‘]$/u.test("๐Ÿ‘") //โ†’ true +/^[๐Ÿ‘]$/u.test("๐Ÿ‘"); //โ†’ true ``` ## Rule Details @@ -60,12 +60,12 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-misleading-character-class: error */ -/^[Aฬ]$/u -/^[โ‡๏ธ]$/u -/^[๐Ÿ‘ถ๐Ÿป]$/u -/^[๐Ÿ‡ฏ๐Ÿ‡ต]$/u -/^[๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ]$/u -/^[๐Ÿ‘]$/ +/^[Aฬ]$/u; +/^[โ‡๏ธ]$/u; +/^[๐Ÿ‘ถ๐Ÿป]$/u; +/^[๐Ÿ‡ฏ๐Ÿ‡ต]$/u; +/^[๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ]$/u; +/^[๐Ÿ‘]$/; ``` ::: @@ -77,9 +77,9 @@ Examples of **correct** code for this rule: ```js /*eslint no-misleading-character-class: error */ -/^[abc]$/ -/^[๐Ÿ‘]$/u -/^[\q{๐Ÿ‘ถ๐Ÿป}]$/v +/^[abc]$/; +/^[๐Ÿ‘]$/u; +/^[\q{๐Ÿ‘ถ๐Ÿป}]$/v; ``` ::: diff --git a/docs/src/rules/no-multi-assign.md b/docs/src/rules/no-multi-assign.md index 7cc4581d1523..f4f4c2fce5aa 100644 --- a/docs/src/rules/no-multi-assign.md +++ b/docs/src/rules/no-multi-assign.md @@ -31,9 +31,9 @@ var a = b = c = 5; const foo = bar = "baz"; -let a = - b = - c; +let d = + e = + f; class Foo { a = b = 10; @@ -58,8 +58,8 @@ var c = 5; const foo = "baz"; const bar = "baz"; -let a = c; -let b = c; +let d = c; +let e = c; class Foo { a = 10; diff --git a/docs/src/rules/no-restricted-exports.md b/docs/src/rules/no-restricted-exports.md index 2f83643f9cb8..44672021a108 100644 --- a/docs/src/rules/no-restricted-exports.md +++ b/docs/src/rules/no-restricted-exports.md @@ -144,7 +144,25 @@ Examples of **incorrect** code for the `"restrictDefaultExports": { "direct": tr /*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "direct": true } }]*/ export default foo; +``` + +::: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "direct": true } }]*/ + export default 42; +``` + +::: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "direct": true } }]*/ + export default function foo() {} ``` @@ -176,6 +194,15 @@ Examples of **incorrect** code for the `"restrictDefaultExports": { "defaultFrom /*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "defaultFrom": true } }]*/ export { default } from 'foo'; +``` + +::: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { "restrictDefaultExports": { "defaultFrom": true } }]*/ + export { default as default } from 'foo'; ``` diff --git a/docs/src/rules/no-restricted-imports.md b/docs/src/rules/no-restricted-imports.md index 9040170ed133..72d97baf3e70 100644 --- a/docs/src/rules/no-restricted-imports.md +++ b/docs/src/rules/no-restricted-imports.md @@ -207,7 +207,7 @@ import { DisallowedObject } from "foo"; import { DisallowedObject as AllowedObject } from "foo"; -import { "DisallowedObject" as AllowedObject } from "foo"; +import { "DisallowedObject" as SomeObject } from "foo"; ``` ::: diff --git a/docs/src/rules/no-sequences.md b/docs/src/rules/no-sequences.md index 811dae491968..cbacb46ce26f 100644 --- a/docs/src/rules/no-sequences.md +++ b/docs/src/rules/no-sequences.md @@ -87,9 +87,9 @@ Examples of **incorrect** code for arrow functions: /*eslint no-sequences: "error"*/ const foo = (val) => (console.log('bar'), val); -const foo = () => ((bar = 123), 10); +const baz = () => ((bar = 123), 10); -const foo = () => { return (bar = 123), 10 } +const qux = () => { return (bar = 123), 10 } ``` ::: @@ -102,9 +102,9 @@ Examples of **correct** code for arrow functions: /*eslint no-sequences: "error"*/ const foo = (val) => ((console.log('bar'), val)); -const foo = () => (((bar = 123), 10)); +const baz = () => (((bar = 123), 10)); -const foo = () => { return ((bar = 123), 10) } +const qux = () => { return ((bar = 123), 10) } ``` ::: diff --git a/docs/src/rules/no-this-before-super.md b/docs/src/rules/no-this-before-super.md index c1a654796cb6..a06b5735553e 100644 --- a/docs/src/rules/no-this-before-super.md +++ b/docs/src/rules/no-this-before-super.md @@ -24,28 +24,28 @@ Examples of **incorrect** code for this rule: /*eslint no-this-before-super: "error"*/ /*eslint-env es6*/ -class A extends B { +class A1 extends B { constructor() { this.a = 0; super(); } } -class A extends B { +class A2 extends B { constructor() { this.foo(); super(); } } -class A extends B { +class A3 extends B { constructor() { super.foo(); super(); } } -class A extends B { +class A4 extends B { constructor() { super(this.foo()); } @@ -62,20 +62,20 @@ Examples of **correct** code for this rule: /*eslint no-this-before-super: "error"*/ /*eslint-env es6*/ -class A { +class A1 { constructor() { this.a = 0; // OK, this class doesn't have an `extends` clause. } } -class A extends B { +class A2 extends B { constructor() { super(); this.a = 0; // OK, this is after `super()`. } } -class A extends B { +class A3 extends B { foo() { this.a = 0; // OK. this is not in a constructor. } diff --git a/docs/src/rules/no-underscore-dangle.md b/docs/src/rules/no-underscore-dangle.md index c5e7923c2bfb..19157dfb55f7 100644 --- a/docs/src/rules/no-underscore-dangle.md +++ b/docs/src/rules/no-underscore-dangle.md @@ -45,8 +45,8 @@ var obj = _.contains(items, item); obj.__proto__ = {}; var file = __filename; function foo(_bar) {}; -const foo = { onClick(_bar) {} }; -const foo = (_bar) => {}; +const bar = { onClick(_bar) {} }; +const baz = (_bar) => {}; ``` ::: @@ -104,8 +104,12 @@ Examples of **correct** code for this rule with the `{ "allowAfterSuper": true } ```js /*eslint no-underscore-dangle: ["error", { "allowAfterSuper": true }]*/ -var a = super.foo_; -super._bar(); +class Foo extends Bar { + doSomething() { + var a = super.foo_; + super._bar(); + } +} ``` ::: @@ -138,16 +142,16 @@ class Foo { _bar() {} } -class Foo { +class Bar { bar_() {} } -const o = { +const o1 = { _bar() {} }; -const o = { - bar_() = {} +const o2 = { + bar_() {} }; ``` @@ -166,19 +170,19 @@ class Foo { _bar; } -class Foo { +class Bar { _bar = () => {}; } -class Foo { +class Baz { bar_; } -class Foo { +class Qux { #_bar; } -class Foo { +class FooBar { #bar_; } ``` @@ -195,7 +199,7 @@ Examples of **incorrect** code for this rule with the `{ "allowInArrayDestructur /*eslint no-underscore-dangle: ["error", { "allowInArrayDestructuring": false }]*/ const [_foo, _bar] = list; -const [foo_, ..._bar] = list; +const [foo_, ..._qux] = list; const [foo, [bar, _baz]] = list; ``` @@ -211,7 +215,7 @@ Examples of **incorrect** code for this rule with the `{ "allowInObjectDestructu /*eslint no-underscore-dangle: ["error", { "allowInObjectDestructuring": false }]*/ const { foo, bar: _bar } = collection; -const { foo, bar, _baz } = collection; +const { qux, xyz, _baz } = collection; ``` ::: @@ -224,7 +228,7 @@ Examples of **correct** code for this rule with the `{ "allowInObjectDestructuri /*eslint no-underscore-dangle: ["error", { "allowInObjectDestructuring": false }]*/ const { foo, bar, _baz: { a, b } } = collection; -const { foo, bar, _baz: baz } = collection; +const { qux, xyz, _baz: baz } = collection; ``` ::: @@ -238,17 +242,17 @@ Examples of **incorrect** code for this rule with the `{ "allowFunctionParams": ```js /*eslint no-underscore-dangle: ["error", { "allowFunctionParams": false }]*/ -function foo (_bar) {} -function foo (_bar = 0) {} -function foo (..._bar) {} +function foo1 (_bar) {} +function foo2 (_bar = 0) {} +function foo3 (..._bar) {} -const foo = function onClick (_bar) {} -const foo = function onClick (_bar = 0) {} -const foo = function onClick (..._bar) {} +const foo4 = function onClick (_bar) {} +const foo5 = function onClick (_bar = 0) {} +const foo6 = function onClick (..._bar) {} -const foo = (_bar) => {}; -const foo = (_bar = 0) => {}; -const foo = (..._bar) => {}; +const foo7 = (_bar) => {}; +const foo8 = (_bar = 0) => {}; +const foo9 = (..._bar) => {}; ``` ::: diff --git a/docs/src/rules/no-unexpected-multiline.md b/docs/src/rules/no-unexpected-multiline.md index 158a38642a4c..b8f3582fb39d 100644 --- a/docs/src/rules/no-unexpected-multiline.md +++ b/docs/src/rules/no-unexpected-multiline.md @@ -40,11 +40,11 @@ var hello = 'world' let x = function() {} `hello` -let x = function() {} -x +let y = function() {} +y `hello` -let x = foo +let z = foo /regex/g.test(bar) ``` diff --git a/docs/src/rules/no-unused-private-class-members.md b/docs/src/rules/no-unused-private-class-members.md index c92a8827be12..ec6951480b5f 100644 --- a/docs/src/rules/no-unused-private-class-members.md +++ b/docs/src/rules/no-unused-private-class-members.md @@ -20,29 +20,29 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-unused-private-class-members: "error"*/ -class Foo { +class A { #unusedMember = 5; } -class Foo { +class B { #usedOnlyInWrite = 5; method() { this.#usedOnlyInWrite = 42; } } -class Foo { +class C { #usedOnlyToUpdateItself = 5; method() { this.#usedOnlyToUpdateItself++; } } -class Foo { +class D { #unusedMethod() {} } -class Foo { +class E { get #unusedAccessor() {} set #unusedAccessor(value) {} } @@ -57,14 +57,14 @@ Examples of **correct** code for this rule: ```js /*eslint no-unused-private-class-members: "error"*/ -class Foo { +class A { #usedMember = 42; method() { return this.#usedMember; } } -class Foo { +class B { #usedMethod() { return 42; } @@ -73,7 +73,7 @@ class Foo { } } -class Foo { +class C { get #usedAccessor() {} set #usedAccessor(value) {} diff --git a/docs/src/rules/no-useless-constructor.md b/docs/src/rules/no-useless-constructor.md index cfdae434d720..bf2c4c329846 100644 --- a/docs/src/rules/no-useless-constructor.md +++ b/docs/src/rules/no-useless-constructor.md @@ -56,19 +56,19 @@ Examples of **correct** code for this rule: class A { } -class A { +class B { constructor () { doSomething(); } } -class B extends A { +class C extends A { constructor() { super('foo'); } } -class B extends A { +class D extends A { constructor() { super(); doSomething(); diff --git a/docs/src/rules/no-useless-rename.md b/docs/src/rules/no-useless-rename.md index d65541c2ad07..6e79e3e7dc02 100644 --- a/docs/src/rules/no-useless-rename.md +++ b/docs/src/rules/no-useless-rename.md @@ -60,14 +60,14 @@ Examples of **incorrect** code for this rule by default: ```js /*eslint no-useless-rename: "error"*/ -import { foo as foo } from "bar"; -import { "foo" as foo } from "bar"; -export { foo as foo }; -export { foo as "foo" }; -export { foo as foo } from "bar"; -export { "foo" as "foo" } from "bar"; -let { foo: foo } = bar; -let { 'foo': foo } = bar; +import { foo1 as foo1 } from "bar"; +import { "foo2" as foo2 } from "bar"; +export { foo1 as foo1 }; +export { foo2 as "foo2" }; +export { foo3 as foo3 } from "bar"; +export { "foo4" as "foo4" } from "bar"; +let { foo3: foo3 } = bar; +let { 'foo4': foo4 } = bar; function foo({ bar: bar }) {} ({ foo: foo }) => {} ``` @@ -81,23 +81,23 @@ Examples of **correct** code for this rule by default: ```js /*eslint no-useless-rename: "error"*/ -import * as foo from "foo"; -import { foo } from "bar"; -import { foo as bar } from "baz"; -import { "foo" as bar } from "baz"; +import * as foo1 from "foo"; +import { foo2 } from "bar"; +import { foo as bar1 } from "baz"; +import { "foo" as bar2 } from "baz"; export { foo }; -export { foo as bar }; -export { foo as "bar" }; -export { foo as bar } from "foo"; -export { "foo" as "bar" } from "foo"; +export { foo as bar1 }; +export { foo as "bar2" }; +export { foo as bar3 } from "foo"; +export { "foo" as "bar4" } from "foo"; let { foo } = bar; let { foo: bar } = baz; -let { [foo]: foo } = bar; +let { [qux]: qux } = bar; -function foo({ bar }) {} -function foo({ bar: baz }) {} +function foo3({ bar }) {} +function foo4({ bar: baz }) {} ({ foo }) => {} ({ foo: bar }) => {} @@ -124,8 +124,9 @@ Examples of **correct** code for this rule with `{ ignoreExport: true }`: ```js /*eslint no-useless-rename: ["error", { ignoreExport: true }]*/ +const foo = 1; export { foo as foo }; -export { foo as foo } from "bar"; +export { bar as bar } from "bar"; ``` ::: @@ -138,7 +139,7 @@ Examples of **correct** code for this rule with `{ ignoreDestructuring: true }`: /*eslint no-useless-rename: ["error", { ignoreDestructuring: true }]*/ let { foo: foo } = bar; -function foo({ bar: bar }) {} +function baz({ bar: bar }) {} ({ foo: foo }) => {} ``` diff --git a/docs/src/rules/object-curly-newline.md b/docs/src/rules/object-curly-newline.md index a9b8875a5922..4a4f2e14f61e 100644 --- a/docs/src/rules/object-curly-newline.md +++ b/docs/src/rules/object-curly-newline.md @@ -554,9 +554,9 @@ Examples of **incorrect** code for this rule with the `{ "ImportDeclaration": "a /*eslint-env es6*/ import {foo, bar} from 'foo-bar'; -import {foo as f, bar} from 'foo-bar'; -import {foo, - bar} from 'foo-bar'; +import {foo as f, baz} from 'foo-bar'; +import {qux, + foobar} from 'foo-bar'; export { foo, @@ -564,7 +564,7 @@ export { }; export { foo as f, - bar + baz } from 'foo-bar'; ``` @@ -583,15 +583,15 @@ import { bar } from 'foo-bar'; import { - foo, bar + baz, qux } from 'foo-bar'; import { foo as f, - bar + foobar } from 'foo-bar'; export { foo, bar } from 'foo-bar'; -export { foo as f, bar } from 'foo-bar'; +export { foo as f, baz } from 'foo-bar'; ``` ::: diff --git a/docs/src/rules/one-var-declaration-per-line.md b/docs/src/rules/one-var-declaration-per-line.md index e9b159fc992b..659ef73343dc 100644 --- a/docs/src/rules/one-var-declaration-per-line.md +++ b/docs/src/rules/one-var-declaration-per-line.md @@ -46,8 +46,8 @@ Examples of **incorrect** code for this rule with the default `"initializations" var a, b, c = 0; -let a, - b = 0, c; +let d, + e = 0, f; ``` ::: @@ -62,11 +62,11 @@ Examples of **correct** code for this rule with the default `"initializations"` var a, b; -let a, - b; +let c, + d; -let a, - b = 0; +let e, + f = 0; ``` ::: @@ -83,9 +83,9 @@ Examples of **incorrect** code for this rule with the `"always"` option: var a, b; -let a, b = 0; +let c, d = 0; -const a = 0, b = 0; +const e = 0, f = 0; ``` ::: @@ -101,8 +101,8 @@ Examples of **correct** code for this rule with the `"always"` option: var a, b; -let a, - b = 0; +let c, + d = 0; ``` ::: diff --git a/docs/src/rules/one-var.md b/docs/src/rules/one-var.md index 9c41bcd81b21..b44c583dd358 100644 --- a/docs/src/rules/one-var.md +++ b/docs/src/rules/one-var.md @@ -192,14 +192,14 @@ Examples of **incorrect** code for this rule with the `"never"` option: ```js /*eslint one-var: ["error", "never"]*/ -function foo() { +function foo1() { var bar, baz; - const bar = true, - baz = false; + const qux = true, + foobar = false; } -function foo() { +function foo2() { var bar, qux; @@ -208,7 +208,7 @@ function foo() { } } -function foo(){ +function foo3(){ let bar = true, baz = false; } @@ -418,8 +418,8 @@ Examples of **correct** code for this rule with the `{ var: "never" }` option: function foo() { var bar, baz; - const bar = 1; // `const` and `let` declarations are ignored if they are not specified - const baz = 2; + const foobar = 1; // `const` and `let` declarations are ignored if they are not specified + const foobaz = 2; let qux; let norf; } diff --git a/docs/src/rules/prefer-const.md b/docs/src/rules/prefer-const.md index 19801eb952a2..41b144fe4660 100644 --- a/docs/src/rules/prefer-const.md +++ b/docs/src/rules/prefer-const.md @@ -27,9 +27,9 @@ Examples of **incorrect** code for this rule: let a = 3; console.log(a); -let a; -a = 0; -console.log(a); +let b; +b = 0; +console.log(b); class C { static { @@ -63,35 +63,35 @@ Examples of **correct** code for this rule: const a = 0; // it's never initialized. -let a; -console.log(a); +let b; +console.log(b); // it's reassigned after initialized. -let a; -a = 0; -a = 1; -console.log(a); +let c; +c = 0; +c = 1; +console.log(c); // it's initialized in a different block from the declaration. -let a; +let d; if (true) { - a = 0; + d = 0; } -console.log(a); +console.log(d); // it's initialized in a different scope. -let a; +let e; class C { #x; static { - a = obj => obj.#x; + e = obj => obj.#x; } } // it's initialized at a place that we cannot write a variable declaration. -let a; -if (true) a = 0; -console.log(a); +let f; +if (true) f = 0; +console.log(f); // `i` gets a new binding each iteration for (const i in [1, 2, 3]) { @@ -112,14 +112,14 @@ for (let i = 0, end = 10; i < end; ++i) { let predicate; [object.type, predicate] = foo(); -// `a` is only assigned once but cannot be separately declared as `const` -let a; -const b = {}; -({ a, c: b.c } = func()); +// `g` is only assigned once but cannot be separately declared as `const` +let g; +const h = {}; +({ g, c: h.c } = func()); // suggest to use `no-var` rule. -var b = 3; -console.log(b); +var i = 3; +console.log(i); ``` ::: @@ -170,9 +170,9 @@ const {a: a0, b} = obj; const a = a0 + 1; // all variables are reassigned. -let {a, b} = obj; -a = a + 1; -b = b + 1; +let {c, d} = obj; +c = c + 1; +d = d + 1; ``` ::: diff --git a/docs/src/rules/prefer-destructuring.md b/docs/src/rules/prefer-destructuring.md index edb72967b540..34b30c8de32b 100644 --- a/docs/src/rules/prefer-destructuring.md +++ b/docs/src/rules/prefer-destructuring.md @@ -36,6 +36,8 @@ Examples of **incorrect** code for this rule: ::: incorrect ```javascript +/* eslint prefer-destructuring: "error" */ + // With `array` enabled var foo = array[0]; bar.baz = array[0]; @@ -52,6 +54,8 @@ Examples of **correct** code for this rule: ::: correct ```javascript +/* eslint prefer-destructuring: "error" */ + // With `array` enabled var [ foo ] = array; var foo = array[someIndex]; @@ -63,8 +67,8 @@ var { foo } = object; var foo = object.bar; -let foo; -({ foo } = object); +let bar; +({ bar } = object); ``` ::: @@ -74,6 +78,7 @@ Examples of **incorrect** code when `enforceForRenamedProperties` is enabled: ::: incorrect ```javascript +/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ var foo = object.bar; ``` @@ -84,6 +89,7 @@ Examples of **correct** code when `enforceForRenamedProperties` is enabled: ::: correct ```javascript +/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ var { bar: foo } = object; ``` @@ -94,6 +100,7 @@ Examples of additional **correct** code when `enforceForRenamedProperties` is en ::: correct ```javascript +/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ class C { #x; foo() { diff --git a/docs/src/rules/require-unicode-regexp.md b/docs/src/rules/require-unicode-regexp.md index d0069de15963..768f34a71d35 100644 --- a/docs/src/rules/require-unicode-regexp.md +++ b/docs/src/rules/require-unicode-regexp.md @@ -91,7 +91,7 @@ const g = new RegExp("ccc", "v") const h = new RegExp("ddd", "giv") // This rule ignores RegExp calls if the flags could not be evaluated to a static value. -function f(flags) { +function i(flags) { return new RegExp("eee", flags) } ``` diff --git a/docs/src/rules/rest-spread-spacing.md b/docs/src/rules/rest-spread-spacing.md index b27b5378d238..f51ef54be35a 100644 --- a/docs/src/rules/rest-spread-spacing.md +++ b/docs/src/rules/rest-spread-spacing.md @@ -85,8 +85,8 @@ Examples of **incorrect** code for this rule with `"never"`: ```js /*eslint rest-spread-spacing: ["error", "never"]*/ -fn(... args) -[... arr, 4, 5, 6] +fn(... args); +[... arr, 4, 5, 6]; let [a, b, ... arr] = [1, 2, 3, 4, 5]; function fn(... args) { console.log(args); } let { x, y, ... z } = { x: 1, y: 2, a: 3, b: 4 }; @@ -102,8 +102,8 @@ Examples of **correct** code for this rule with `"never"`: ```js /*eslint rest-spread-spacing: ["error", "never"]*/ -fn(...args) -[...arr, 4, 5, 6] +fn(...args); +[...arr, 4, 5, 6]; let [a, b, ...arr] = [1, 2, 3, 4, 5]; function fn(...args) { console.log(args); } let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; @@ -127,8 +127,8 @@ Examples of **incorrect** code for this rule with `"always"`: ```js /*eslint rest-spread-spacing:["error", "always"]*/ -fn(...args) -[...arr, 4, 5, 6] +fn(...args); +[...arr, 4, 5, 6]; let [a, b, ...arr] = [1, 2, 3, 4, 5]; function fn(...args) { console.log(args); } let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; @@ -144,8 +144,8 @@ Examples of **correct** code for this rule with `"always"`: ```js /*eslint rest-spread-spacing: ["error", "always"]*/ -fn(... args) -[... arr, 4, 5, 6] +fn(... args); +[... arr, 4, 5, 6]; let [a, b, ... arr] = [1, 2, 3, 4, 5]; function fn(... args) { console.log(args); } let { x, y, ... z } = { x: 1, y: 2, a: 3, b: 4 }; diff --git a/docs/src/rules/sort-imports.md b/docs/src/rules/sort-imports.md index d48ead6f66e1..762e4acfa439 100644 --- a/docs/src/rules/sort-imports.md +++ b/docs/src/rules/sort-imports.md @@ -82,19 +82,37 @@ import {alpha, beta} from 'alpha.js'; import {delta, gamma} from 'delta.js'; import a from 'baz.js'; import {b} from 'qux.js'; +``` + +::: +::: correct + +```js /*eslint sort-imports: "error"*/ import a from 'foo.js'; import b from 'bar.js'; import c from 'baz.js'; +``` +::: + +::: correct + +```js /*eslint sort-imports: "error"*/ import 'foo.js' import * as bar from 'bar.js'; import {a, b} from 'baz.js'; import c from 'qux.js'; import {d} from 'quux.js'; +``` +::: + +::: correct + +```js /*eslint sort-imports: "error"*/ import {a, b, c} from 'foo.js' ``` @@ -109,27 +127,63 @@ Examples of **incorrect** code for this rule when using default options: /*eslint sort-imports: "error"*/ import b from 'foo.js'; import a from 'bar.js'; +``` + +::: +::: incorrect + +```js /*eslint sort-imports: "error"*/ import a from 'foo.js'; import A from 'bar.js'; +``` + +::: +::: incorrect + +```js /*eslint sort-imports: "error"*/ -import {b, c} from 'foo.js'; +import {c, d} from 'foo.js'; import {a, b} from 'bar.js'; +``` + +::: + +::: incorrect +```js /*eslint sort-imports: "error"*/ import a from 'foo.js'; import {b, c} from 'bar.js'; +``` + +::: + +::: incorrect +```js /*eslint sort-imports: "error"*/ import {a} from 'foo.js'; import {b, c} from 'bar.js'; +``` + +::: +::: incorrect + +```js /*eslint sort-imports: "error"*/ import a from 'foo.js'; import * as b from 'bar.js'; +``` + +::: +::: incorrect + +```js /*eslint sort-imports: "error"*/ import {b, a, c} from 'foo.js' ``` diff --git a/docs/src/rules/sort-keys.md b/docs/src/rules/sort-keys.md index 0fd00aeff7f4..14cdc12f8f30 100644 --- a/docs/src/rules/sort-keys.md +++ b/docs/src/rules/sort-keys.md @@ -21,20 +21,20 @@ Examples of **incorrect** code for this rule: /*eslint sort-keys: "error"*/ /*eslint-env es6*/ -let obj = {a: 1, c: 3, b: 2}; -let obj = {a: 1, "c": 3, b: 2}; +let obj1 = {a: 1, c: 3, b: 2}; +let obj2 = {a: 1, "c": 3, b: 2}; // Case-sensitive by default. -let obj = {a: 1, b: 2, C: 3}; +let obj3 = {a: 1, b: 2, C: 3}; // Non-natural order by default. -let obj = {1: a, 2: c, 10: b}; +let obj4 = {1: a, 2: c, 10: b}; // This rule checks computed properties which have a simple name as well. // Simple names are names which are expressed by an Identifier node or a Literal node. const S = Symbol("s") -let obj = {a: 1, ["c"]: 3, b: 2}; -let obj = {a: 1, [S]: 3, b: 2}; +let obj5 = {a: 1, ["c"]: 3, b: 2}; +let obj6 = {a: 1, [S]: 3, b: 2}; ``` ::: @@ -47,27 +47,27 @@ Examples of **correct** code for this rule: /*eslint sort-keys: "error"*/ /*eslint-env es6*/ -let obj = {a: 1, b: 2, c: 3}; -let obj = {a: 1, "b": 2, c: 3}; +let obj1 = {a: 1, b: 2, c: 3}; +let obj2 = {a: 1, "b": 2, c: 3}; // Case-sensitive by default. -let obj = {C: 3, a: 1, b: 2}; +let obj3 = {C: 3, a: 1, b: 2}; // Non-natural order by default. -let obj = {1: a, 10: b, 2: c}; +let obj4 = {1: a, 10: b, 2: c}; // This rule checks computed properties which have a simple name as well. -let obj = {a: 1, ["b"]: 2, c: 3}; -let obj = {a: 1, [b]: 2, c: 3}; +let obj5 = {a: 1, ["b"]: 2, c: 3}; +let obj6 = {a: 1, [b]: 2, c: 3}; // This rule ignores computed properties which have a non-simple name. -let obj = {a: 1, [c + d]: 3, b: 2}; -let obj = {a: 1, ["c" + "d"]: 3, b: 2}; -let obj = {a: 1, [`${c}`]: 3, b: 2}; -let obj = {a: 1, [tag`c`]: 3, b: 2}; +let obj7 = {a: 1, [c + d]: 3, b: 2}; +let obj8 = {a: 1, ["c" + "d"]: 3, b: 2}; +let obj9 = {a: 1, [`${c}`]: 3, b: 2}; +let obj10 = {a: 1, [tag`c`]: 3, b: 2}; // This rule does not report unsorted properties that are separated by a spread property. -let obj = {b: 1, ...c, a: 2}; +let obj11 = {b: 1, ...c, a: 2}; ``` ::: @@ -118,14 +118,14 @@ Examples of **incorrect** code for the `"desc"` option: /*eslint sort-keys: ["error", "desc"]*/ /*eslint-env es6*/ -let obj = {b: 2, c: 3, a: 1}; -let obj = {"b": 2, c: 3, a: 1}; +let obj1 = {b: 2, c: 3, a: 1}; +let obj2 = {"b": 2, c: 3, a: 1}; // Case-sensitive by default. -let obj = {C: 1, b: 3, a: 2}; +let obj3 = {C: 1, b: 3, a: 2}; // Non-natural order by default. -let obj = {10: b, 2: c, 1: a}; +let obj4 = {10: b, 2: c, 1: a}; ``` ::: @@ -138,14 +138,14 @@ Examples of **correct** code for the `"desc"` option: /*eslint sort-keys: ["error", "desc"]*/ /*eslint-env es6*/ -let obj = {c: 3, b: 2, a: 1}; -let obj = {c: 3, "b": 2, a: 1}; +let obj1 = {c: 3, b: 2, a: 1}; +let obj2 = {c: 3, "b": 2, a: 1}; // Case-sensitive by default. -let obj = {b: 3, a: 2, C: 1}; +let obj3 = {b: 3, a: 2, C: 1}; // Non-natural order by default. -let obj = {2: c, 10: b, 1: a}; +let obj4 = {2: c, 10: b, 1: a}; ``` ::: @@ -160,8 +160,8 @@ Examples of **incorrect** code for the `{caseSensitive: false}` option: /*eslint sort-keys: ["error", "asc", {caseSensitive: false}]*/ /*eslint-env es6*/ -let obj = {a: 1, c: 3, C: 4, b: 2}; -let obj = {a: 1, C: 3, c: 4, b: 2}; +let obj1 = {a: 1, c: 3, C: 4, b: 2}; +let obj2 = {a: 1, C: 3, c: 4, b: 2}; ``` ::: @@ -174,8 +174,8 @@ Examples of **correct** code for the `{caseSensitive: false}` option: /*eslint sort-keys: ["error", "asc", {caseSensitive: false}]*/ /*eslint-env es6*/ -let obj = {a: 1, b: 2, c: 3, C: 4}; -let obj = {a: 1, b: 2, C: 3, c: 4}; +let obj1 = {a: 1, b: 2, c: 3, C: 4}; +let obj2 = {a: 1, b: 2, C: 3, c: 4}; ``` ::: @@ -219,7 +219,7 @@ Examples of **incorrect** code for the `{minKeys: 4}` option: /*eslint-env es6*/ // 4 keys -let obj = { +let obj1 = { b: 2, a: 1, // not sorted correctly (should be 1st key) c: 3, @@ -227,7 +227,7 @@ let obj = { }; // 5 keys -let obj = { +let obj2 = { 2: 'a', 1: 'b', // not sorted correctly (should be 1st key) 3: 'c', @@ -247,14 +247,14 @@ Examples of **correct** code for the `{minKeys: 4}` option: /*eslint-env es6*/ // 3 keys -let obj = { +let obj1 = { b: 2, a: 1, c: 3, }; // 2 keys -let obj = { +let obj2 = { 2: 'b', 1: 'a', }; @@ -318,7 +318,7 @@ Examples of **correct** code for the `{allowLineSeparatedGroups: true}` option: /*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*/ /*eslint-env es6*/ -let obj = { +let obj1 = { e: 1, f: 2, g: 3, @@ -328,7 +328,7 @@ let obj = { c: 6 } -let obj = { +let obj2 = { b: 1, // comment @@ -336,7 +336,7 @@ let obj = { c: 5, } -let obj = { +let obj3 = { c: 1, d: 2, @@ -346,7 +346,7 @@ let obj = { e: 3, } -let obj = { +let obj4 = { c: 1, d: 2, // comment @@ -358,14 +358,14 @@ let obj = { e: 4 } -let obj = { +let obj5 = { b, [foo + bar]: 1, a } -let obj = { +let obj6 = { b: 1 // comment before comma @@ -373,7 +373,7 @@ let obj = { a: 2 }; -var obj = { +var obj7 = { b: 1, a: 2, diff --git a/docs/src/rules/space-before-keywords.md b/docs/src/rules/space-before-keywords.md index 9c2f73581096..5eaa76db7685 100644 --- a/docs/src/rules/space-before-keywords.md +++ b/docs/src/rules/space-before-keywords.md @@ -55,7 +55,7 @@ if (foo) { const foo = 'bar';let baz = 'qux'; -var foo =function bar () {} +var qux =function bar () {} function bar() { if (foo) {return; } @@ -66,7 +66,7 @@ function bar() { Examples of **correct** code for this rule with the default `"always"` option: -::: correct +::: correct { "ecmaFeatures": { "jsx": true } } ```js /*eslint space-before-keywords: ["error", "always"]*/ @@ -76,7 +76,7 @@ if (foo) { // ... } else {} -(function() {})() +(function() {})(); diff --git a/docs/src/rules/space-infix-ops.md b/docs/src/rules/space-infix-ops.md index 984e3b37f418..b278a7c1d781 100644 --- a/docs/src/rules/space-infix-ops.md +++ b/docs/src/rules/space-infix-ops.md @@ -57,7 +57,7 @@ a?b:c const a={b:1}; -var {a=0}=bar; +var {b=0}=bar; function foo(a=0) { } ``` @@ -80,7 +80,7 @@ a ? b : c const a = {b:1}; -var {a = 0} = bar; +var {b = 0} = bar; function foo(a = 0) { } ``` From b7ef2f34fe12b68a366e1b4bf5f64d7332c6e72e Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 11 Oct 2023 17:59:44 -0400 Subject: [PATCH 216/248] docs: Enable pretty code formatter output (#17635) * docs: Enable pretty code formatter output fixes #17505 * Add note about formatted output * Update templates/formatter-examples.md.ejs Co-authored-by: Milos Djermanovic * Update templates/formatter-examples.md.ejs Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- templates/formatter-examples.md.ejs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/templates/formatter-examples.md.ejs b/templates/formatter-examples.md.ejs index 20f46f2d5aa3..e18eb18995fd 100644 --- a/templates/formatter-examples.md.ejs +++ b/templates/formatter-examples.md.ejs @@ -14,7 +14,7 @@ You can specify a formatter using the `--format` or `-f` flag in the CLI. For ex The built-in formatter options are: -<% Object.keys(formatterResults).forEach(function(formatterName) { -%> +<% Object.keys(formatterResults).forEach(formatterName => { -%> * [<%= formatterName %>](#<%= formatterName %>) <% }) -%> @@ -56,19 +56,36 @@ npx eslint --format fullOfProblems.js ``` ## Built-In Formatter Options -<% Object.keys(formatterResults).forEach(function(formatterName) { -%> +<% Object.keys(formatterResults).forEach(formatterName => { -%> ### <%= formatterName %> <%= formatterResults[formatterName].description %> +<% if (formatterName !== "html") { -%> +<% + let codeFormat = "text"; + let output = formatterResults[formatterName].result; + let outputNote = "Example output:"; -Example output: + if (output.startsWith("\u003C?xml")) { + codeFormat = "xml"; + } -<% if (formatterName !== "html") { -%> -```text -<%- formatterResults[formatterName].result %> + if (formatterName.includes("json")) { + codeFormat = "json"; + output = JSON.stringify(JSON.parse(output), null, 4); + outputNote = "Example output (formatted for easier reading):"; + } +%> +<%= outputNote %> + +```<%= codeFormat %> +<%- output %> ``` <% } else {-%> + +Example output: + <% } -%> <% }) -%> From f8e5c30636450d4a8baf51f0e227685e6d77ac64 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Fri, 13 Oct 2023 08:07:15 +0000 Subject: [PATCH 217/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97f165daaa6a..c910134a6f41 100644 --- a/README.md +++ b/README.md @@ -294,7 +294,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Liftoff American Express

Bronze Sponsors

-

bulkfollow ThemeIsle Anagram Solver Icons8 Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

+

ThemeIsle Anagram Solver Icons8 Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

## Technology Sponsors From 179929bd46892f18f2aef0c159d5cc361cb69987 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 13 Oct 2023 10:19:12 +0200 Subject: [PATCH 218/248] docs: Remove trailing newline from the code of Playground links (#17641) * Remove trailing newline from Playground links * Update as per discussion --- docs/.eleventy.js | 6 +++- docs/src/rules/eol-last.md | 3 +- docs/src/rules/no-multiple-empty-lines.md | 37 ++++++----------------- 3 files changed, 17 insertions(+), 29 deletions(-) diff --git a/docs/.eleventy.js b/docs/.eleventy.js index a2a0b71c17bc..75a372d3d5e5 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -208,7 +208,11 @@ module.exports = function(eleventyConfig) { // See https://github.com/eslint/eslint.org/blob/ac38ab41f99b89a8798d374f74e2cce01171be8b/src/playground/App.js#L44 const parserOptionsJSON = tokens[index].info?.split("correct ")[1]?.trim(); const parserOptions = { sourceType: "module", ...(parserOptionsJSON && JSON.parse(parserOptionsJSON)) }; - const { content } = tokens[index + 1]; + + // Remove trailing newline and presentational `โŽ` characters (https://github.com/eslint/eslint/issues/17627): + const content = tokens[index + 1].content + .replace(/\n$/u, "") + .replace(/โŽ(?=\n)/gu, ""); const state = encodeToBase64( JSON.stringify({ options: { parserOptions }, diff --git a/docs/src/rules/eol-last.md b/docs/src/rules/eol-last.md index 4c9d837c5c32..2a8de22258b5 100644 --- a/docs/src/rules/eol-last.md +++ b/docs/src/rules/eol-last.md @@ -42,7 +42,8 @@ Examples of **correct** code for this rule: function doSomething() { var foo = 2; -}\n +} + ``` ::: diff --git a/docs/src/rules/no-multiple-empty-lines.md b/docs/src/rules/no-multiple-empty-lines.md index 5457c54c1f0b..fbb9363485b6 100644 --- a/docs/src/rules/no-multiple-empty-lines.md +++ b/docs/src/rules/no-multiple-empty-lines.md @@ -59,13 +59,13 @@ Examples of **incorrect** code for this rule with the `{ max: 2, maxEOF: 0 }` op ::: incorrect ```js -/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/ - -var foo = 5; - - -var bar = 3; - +/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/โŽ +โŽ +var foo = 5;โŽ +โŽ +โŽ +var bar = 3;โŽ +โŽ ``` @@ -75,36 +75,19 @@ Examples of **correct** code for this rule with the `{ max: 2, maxEOF: 0 }` opti ::: correct -```js -/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/ - -var foo = 5; - - -var bar = 3; -``` - -::: - -**Note**: Although this ensures zero empty lines at the EOF, most editors will still show one empty line at the end if the file ends with a line break, as illustrated below. There is no empty line at the end of a file after the last `\n`, although editors may show an additional line. A true additional line would be represented by `\n\n`. - -**Incorrect**: - -::: incorrect - ```js /*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/โŽ โŽ var foo = 5;โŽ โŽ โŽ -var bar = 3;โŽ -โŽ - +var bar = 3; ``` ::: +**Note**: Although this ensures zero empty lines at the EOF, most editors will still show one empty line at the end if the file ends with a line break, as illustrated below. There is no empty line at the end of a file after the last `\n`, although editors may show an additional line. A true additional line would be represented by `\n\n`. + **Correct**: ::: correct From a58aa200fccedae7e2e9b6129246f2cedab14f8d Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 13 Oct 2023 16:46:08 +0200 Subject: [PATCH 219/248] docs: fix examples for several rules (#17645) --- docs/src/rules/arrow-body-style.md | 4 + docs/src/rules/function-paren-newline.md | 12 +- docs/src/rules/id-match.md | 2 +- docs/src/rules/indent.md | 4 +- docs/src/rules/lines-around-directive.md | 166 +++++++++++++++------- docs/src/rules/max-statements-per-line.md | 2 +- docs/src/rules/max-statements.md | 10 +- docs/src/rules/no-multiple-empty-lines.md | 16 +++ docs/src/rules/one-var.md | 10 +- 9 files changed, 157 insertions(+), 69 deletions(-) diff --git a/docs/src/rules/arrow-body-style.md b/docs/src/rules/arrow-body-style.md index 1bfdb5a73084..1dc6fcc36e83 100644 --- a/docs/src/rules/arrow-body-style.md +++ b/docs/src/rules/arrow-body-style.md @@ -32,6 +32,7 @@ Examples of **incorrect** code for this rule with the `"always"` option: ```js /*eslint arrow-body-style: ["error", "always"]*/ /*eslint-env es6*/ + let foo = () => 0; ``` @@ -42,6 +43,9 @@ Examples of **correct** code for this rule with the `"always"` option: :::correct ```js +/*eslint arrow-body-style: ["error", "always"]*/ +/*eslint-env es6*/ + let foo = () => { return 0; }; diff --git a/docs/src/rules/function-paren-newline.md b/docs/src/rules/function-paren-newline.md index e6a067cf5f23..ad44aaec3650 100644 --- a/docs/src/rules/function-paren-newline.md +++ b/docs/src/rules/function-paren-newline.md @@ -339,11 +339,15 @@ var barbaz = function( bar, baz ) {}; -var barbaz = (bar, - baz) => {}; +var barbaz = ( + bar, + baz +) => {}; -foo(bar, - baz); +foo( + bar, + baz +); ``` ::: diff --git a/docs/src/rules/id-match.md b/docs/src/rules/id-match.md index 6d15d19e859b..59954fc2a48a 100644 --- a/docs/src/rules/id-match.md +++ b/docs/src/rules/id-match.md @@ -143,7 +143,7 @@ Examples of **correct** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$", { ```js /*eslint id-match: [2, "^[a-z]+([A-Z][a-z]+)*$", { "onlyDeclarations": true }]*/ -do_something(__dirname); +foo = __dirname; ``` ::: diff --git a/docs/src/rules/indent.md b/docs/src/rules/indent.md index e53a1f9a6e1b..72efad265683 100644 --- a/docs/src/rules/indent.md +++ b/docs/src/rules/indent.md @@ -192,7 +192,7 @@ Examples of **correct** code for this rule with the `4, { "ignoredNodes": ["Call foo(); bar(); -}) +})(); ``` ::: @@ -403,7 +403,7 @@ function foo(x) { })(); if (y) { - console.log('foo'); + console.log('foo'); } ``` diff --git a/docs/src/rules/lines-around-directive.md b/docs/src/rules/lines-around-directive.md index 8a83df1afd7c..7c18b517446d 100644 --- a/docs/src/rules/lines-around-directive.md +++ b/docs/src/rules/lines-around-directive.md @@ -65,14 +65,8 @@ Examples of **incorrect** code for this rule with the `"always"` option: ```js /* eslint lines-around-directive: ["error", "always"] */ -/* Top of file */ -"use strict"; -var foo; - -/* Top of file */ // comment "use strict"; -"use asm"; var foo; function foo() { @@ -90,23 +84,29 @@ function foo() { ::: -Examples of **correct** code for this rule with the `"always"` option: - -::: correct { "sourceType": "script" } +::: incorrect { "sourceType": "script" } ```js /* eslint lines-around-directive: ["error", "always"] */ -/* Top of file */ +// comment "use strict"; - +"use asm"; var foo; +``` + +::: + +Examples of **correct** code for this rule with the `"always"` option: + +::: correct { "sourceType": "script" } + +```js +/* eslint lines-around-directive: ["error", "always"] */ -/* Top of file */ // comment "use strict"; -"use asm"; var foo; @@ -128,6 +128,21 @@ function foo() { ::: +::: correct { "sourceType": "script" } + +```js +/* eslint lines-around-directive: ["error", "always"] */ + +// comment + +"use strict"; +"use asm"; + +var foo; +``` + +::: + ### never Examples of **incorrect** code for this rule with the `"never"` option: @@ -137,17 +152,9 @@ Examples of **incorrect** code for this rule with the `"never"` option: ```js /* eslint lines-around-directive: ["error", "never"] */ -/* Top of file */ - -"use strict"; - -var foo; - -/* Top of file */ // comment "use strict"; -"use asm"; var foo; @@ -169,21 +176,30 @@ function foo() { ::: -Examples of **correct** code for this rule with the `"never"` option: - -::: correct { "sourceType": "script" } +::: incorrect { "sourceType": "script" } ```js /* eslint lines-around-directive: ["error", "never"] */ -/* Top of file */ +// comment + "use strict"; +"use asm"; + var foo; +``` + +::: + +Examples of **correct** code for this rule with the `"never"` option: + +::: correct { "sourceType": "script" } + +```js +/* eslint lines-around-directive: ["error", "never"] */ -/* Top of file */ // comment "use strict"; -"use asm"; var foo; function foo() { @@ -201,6 +217,19 @@ function foo() { ::: +::: correct { "sourceType": "script" } + +```js +/* eslint lines-around-directive: ["error", "never"] */ + +// comment +"use strict"; +"use asm"; +var foo; +``` + +::: + ### before & after Examples of **incorrect** code for this rule with the `{ "before": "never", "after": "always" }` option: @@ -210,16 +239,9 @@ Examples of **incorrect** code for this rule with the `{ "before": "never", "aft ```js /* eslint lines-around-directive: ["error", { "before": "never", "after": "always" }] */ -/* Top of file */ - -"use strict"; -var foo; - -/* Top of file */ // comment "use strict"; -"use asm"; var foo; function foo() { @@ -238,22 +260,29 @@ function foo() { ::: -Examples of **correct** code for this rule with the `{ "before": "never", "after": "always" }` option: - -::: correct { "sourceType": "script" } +::: incorrect { "sourceType": "script" } ```js /* eslint lines-around-directive: ["error", { "before": "never", "after": "always" }] */ -/* Top of file */ -"use strict"; +// comment +"use strict"; +"use asm"; var foo; +``` + +::: + +Examples of **correct** code for this rule with the `{ "before": "never", "after": "always" }` option: + +::: correct { "sourceType": "script" } + +```js +/* eslint lines-around-directive: ["error", { "before": "never", "after": "always" }] */ -/* Top of file */ // comment "use strict"; -"use asm"; var foo; @@ -274,22 +303,29 @@ function foo() { ::: -Examples of **incorrect** code for this rule with the `{ "before": "always", "after": "never" }` option: - -::: incorrect { "sourceType": "script" } +::: correct { "sourceType": "script" } ```js -/* eslint lines-around-directive: ["error", { "before": "always", "after": "never" }] */ +/* eslint lines-around-directive: ["error", { "before": "never", "after": "always" }] */ -/* Top of file */ +// comment "use strict"; +"use asm"; var foo; +``` + +::: + +Examples of **incorrect** code for this rule with the `{ "before": "always", "after": "never" }` option: + +::: incorrect { "sourceType": "script" } + +```js +/* eslint lines-around-directive: ["error", { "before": "always", "after": "never" }] */ -/* Top of file */ // comment "use strict"; -"use asm"; var foo; @@ -310,22 +346,30 @@ function foo() { ::: -Examples of **correct** code for this rule with the `{ "before": "always", "after": "never" }` option: - -::: correct { "sourceType": "script" } +::: incorrect { "sourceType": "script" } ```js /* eslint lines-around-directive: ["error", { "before": "always", "after": "never" }] */ -/* Top of file */ +// comment "use strict"; +"use asm"; + var foo; +``` + +::: + +Examples of **correct** code for this rule with the `{ "before": "always", "after": "never" }` option: + +::: correct { "sourceType": "script" } + +```js +/* eslint lines-around-directive: ["error", { "before": "always", "after": "never" }] */ -/* Top of file */ // comment "use strict"; -"use asm"; var foo; function foo() { @@ -344,6 +388,20 @@ function foo() { ::: +::: correct { "sourceType": "script" } + +```js +/* eslint lines-around-directive: ["error", { "before": "always", "after": "never" }] */ + +// comment + +"use strict"; +"use asm"; +var foo; +``` + +::: + ## When Not To Use It You can safely disable this rule if you do not have any strict conventions about whether or not directive prologues should have blank newlines before or after them. diff --git a/docs/src/rules/max-statements-per-line.md b/docs/src/rules/max-statements-per-line.md index 76a49e3a09b1..d42d2ef55c1a 100644 --- a/docs/src/rules/max-statements-per-line.md +++ b/docs/src/rules/max-statements-per-line.md @@ -76,7 +76,7 @@ if (condition) { bar = 1; } else { baz = 2; } for (var i = 0; i < length; ++i) { bar = 1; baz = 2; } switch (discriminant) { case 'test': break; default: break; } function foo() { bar = 1; baz = 2; } -var qux = function qux() { bar = 1; }; +var qux = function qux() { bar = 1; baz = 2; }; (function foo() { bar = 1; baz = 2; })(); ``` diff --git a/docs/src/rules/max-statements.md b/docs/src/rules/max-statements.md index 4462011db742..5bdc7e8c2f49 100644 --- a/docs/src/rules/max-statements.md +++ b/docs/src/rules/max-statements.md @@ -99,12 +99,13 @@ function foo() { var foo7 = 7; var foo8 = 8; var foo9 = 9; - var foo10 = 10; - return function () { + return function () { // 10 // The number of statements in the inner function does not count toward the // statement maximum. + var bar; + var baz; return 42; }; } @@ -119,12 +120,13 @@ let bar = () => { var foo7 = 7; var foo8 = 8; var foo9 = 9; - var foo10 = 10; - return function () { + return function () { // 10 // The number of statements in the inner function does not count toward the // statement maximum. + var bar; + var baz; return 42; }; } diff --git a/docs/src/rules/no-multiple-empty-lines.md b/docs/src/rules/no-multiple-empty-lines.md index fbb9363485b6..e3d7327c2e43 100644 --- a/docs/src/rules/no-multiple-empty-lines.md +++ b/docs/src/rules/no-multiple-empty-lines.md @@ -111,6 +111,8 @@ Examples of **incorrect** code for this rule with the `{ max: 2, maxBOF: 1 }` op ::: incorrect ```js + + /*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxBOF": 1 }]*/ @@ -132,6 +134,20 @@ Examples of **correct** code for this rule with the `{ max: 2, maxBOF: 1 }` opti var foo = 5; +var bar = 3; +``` + +::: + +::: correct + +```js + +/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxBOF": 1}]*/ + +var foo = 5; + + var bar = 3; ``` diff --git a/docs/src/rules/one-var.md b/docs/src/rules/one-var.md index b44c583dd358..cb3bc09b955a 100644 --- a/docs/src/rules/one-var.md +++ b/docs/src/rules/one-var.md @@ -416,12 +416,16 @@ Examples of **correct** code for this rule with the `{ var: "never" }` option: /*eslint-env es6*/ function foo() { - var bar, - baz; - const foobar = 1; // `const` and `let` declarations are ignored if they are not specified + var bar; + var baz; + + // `const` and `let` declarations are ignored if they are not specified + const foobar = 1; const foobaz = 2; + const barfoo = 1, bazfoo = 2; let qux; let norf; + let fooqux, foonorf; } ``` From 660ed3afd128ad529234a855345629982caf1bc7 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 16 Oct 2023 11:40:56 -0400 Subject: [PATCH 220/248] docs: Plugin flat config migration guide (#17640) * docs: Plugin flat config migration guide Fixes #17242 * Switch exports to default * Update docs/src/extend/plugin-migration-flat-config.md Co-authored-by: Milos Djermanovic * Meta information * CommonJS syntax * Backwards compatibility * Update docs/src/extend/plugin-migration-flat-config.md Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- .../extend/plugin-migration-flat-config.md | 293 ++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 docs/src/extend/plugin-migration-flat-config.md diff --git a/docs/src/extend/plugin-migration-flat-config.md b/docs/src/extend/plugin-migration-flat-config.md new file mode 100644 index 000000000000..6c277596fc46 --- /dev/null +++ b/docs/src/extend/plugin-migration-flat-config.md @@ -0,0 +1,293 @@ +--- +title: Plugin Migration to Flat Config +eleventyNavigation: + key: plugin flat config + parent: create plugins + title: Migration to Flat Config + order: 4 + +--- + +Beginning in ESLint v9.0.0, the default configuration system will be the new flat config system. In order for your plugins to work with flat config files, you'll need to make some changes to your existing plugins. + +## Recommended Plugin Structure + +To make it easier to work with your plugin in the flat config system, it's recommended that you switch your existing plugin entrypoint to look like this: + +```js +const plugin = { + meta: {}, + configs: {}, + rules: {}, + processors: {} +}; + +// for ESM +export default plugin; + +// OR for CommonJS +module.exports = plugin; +``` + +This structure allows the most flexibility when making other changes discussed on this page. + +## Adding Plugin Meta Information + +With the old eslintrc configuration system, ESLint could pull information about the plugin from the package name, but with flat config, ESLint no longer has access to the name of the plugin package. To replace that missing information, you should add a `meta` key that contains at least a `name` key, and ideally, a `version` key, such as: + +```js +const plugin = { + meta: { + name: "eslint-plugin-example", + version: "1.0.0" + }, + configs: {}, + rules: {}, + processors: {} +}; + +// for ESM +export default plugin; + +// OR for CommonJS +module.exports = plugin; +``` + +If your plugin is published as an npm package, the `name` and `version` should be the same as in your `package.json` file; otherwise, you can assign any value you'd like. + +Without this meta information, your plugin will not be usable with the `--cache` and `--print-config` command line options. + +## Migrating Rules for Flat Config + +No changes are necessary for the `rules` key in your plugin. Everything works the same as with the old eslintrc configuration system. + +## Migrating Processors for Flat Config + +No changes are necessary for the `processors` key in your plugin as long as you aren't using file extension-named processors. If you have any [file extension-named processors](custom-processors#file-extension-named-processor), you must update the name to a valid identifier (numbers and letters). File extension-named processors were automatically applied in the old configuration system but are not automatically applied when using flat config. Here is an example of a file extension-named processor: + +```js +const plugin = { + configs: {}, + rules: {}, + processors: { + + // no longer supported + ".md": { + preprocess() {}, + postprocess() {} + } + } +}; + +// for ESM +export default plugin; + +// OR for CommonJS +module.exports = plugin; +``` + +The name `".md"` is no longer valid for a processor, so it must be replaced with a valid identifier such as `markdown`: + +```js +const plugin = { + configs: {}, + rules: {}, + processors: { + + // works in both old and new config systems + "markdown": { + preprocess() {}, + postprocess() {} + } + } +}; + +// for ESM +export default plugin; + +// OR for CommonJS +module.exports = plugin; +``` + +In order to use this renamed processor, you'll also need to manually specify it inside of a config, such as: + +```js +import example from "eslint-plugin-example"; + +export default [ + { + plugins: { + example + }, + processor: "example/markdown" + } +]; +``` + +You should update your plugin's documentation to advise your users if you have renamed a file extension-named processor. + +## Migrating Configs for Flat Config + +If your plugin is exporting configs that refer back to your plugin, then you'll need to update your configs to flat config format. As part of the migration, you'll need to reference your plugin directly in the `plugins` key. For example, here is an exported config in the old configuration system format for a plugin named `eslint-plugin-example`: + +```js +// plugin name: eslint-plugin-example +module.exports = { + configs: { + + // the config referenced by example/recommended + recommended: { + plugins: ["example"], + rules: { + "example/rule1": "error", + "example/rule2": "error" + } + } + }, + rules: { + rule1: {}, + rule2: {}; + } +}; +``` + +To migrate to flat config format, you'll need to move the configs to after the definition of the `plugin` variable in the recommended plugin structure, like this: + +```js +const plugin = { + configs: {}, + rules: {}, + processors: {} +}; + +// assign configs here so we can reference `plugin` +Object.assign(plugin.configs, { + recommended: { + plugins: { + example: plugin + }, + rules: { + "example/rule1": "error", + "example/rule2": "error" + } + } +}) + +// for ESM +export default plugin; + +// OR for CommonJS +module.exports = plugin; +``` + +Your users can then use this exported config like this: + +```js +import example from "eslint-plugin-example"; + +export default [ + + // use recommended config + example.configs.recommended, + + // and provide your own overrides + { + rules: { + "example/rule1": "warn" + } + } +]; +``` + +You should update our documentation so your plugin users know how to reference the exported configs. + +## Migrating Environments for Flat Config + +Environments are no longer supported in flat config, and so we recommend transitioning your environments into exported configs. For example, suppose you export a `mocha` environment like this: + +```js +// plugin name: eslint-plugin-example +module.exports = { + environments: { + mocha: { + globals: { + it: true, + xit: true, + describe: true, + xdescribe: true + } + } + }, + rules: { + rule1: {}, + rule2: {}; + } +}; +``` + +To migrate this environment into a config, you need to add a new key in the `plugin.configs` object that has a flat config object containing the same information, like this: + +```js +const plugin = { + configs: {}, + rules: {}, + processors: {} +}; + +// assign configs here so we can reference `plugin` +Object.assign(plugin.configs, { + mocha: { + languageOptions: { + globals: { + it: "writeable", + xit: "writeable", + describe: "writeable", + xdescribe: "writeable" + } + } + } +}) + +// for ESM +export default plugin; + +// OR for CommonJS +module.exports = plugin; +``` + +Your users can then use this exported config like this: + +```js +import example from "eslint-plugin-example"; + +export default [ + + // use the mocha globals + example.configs.mocha, + + // and provide your own overrides + { + languageOptions: { + globals: { + it: "readonly" + } + } + } +]; +``` + +You should update your documentation so your plugin users know how to reference the exported configs. + +## Backwards Compatibility + +If your plugin needs to work with both the old and new configuration systems, then you'll need to: + +1. **Export a CommonJS entrypoint.** The old configuration system cannot load plugins that are published only in ESM format. If your source code is in ESM, then you'll need to use a bundler that can generate a CommonJS version and use the [`exports`](https://nodejs.org/api/packages.html#package-entry-points) key in your `package.json` file to ensure the CommonJS version can be found by Node.js. +1. **Keep the `environments` key.** If your plugin exports custom environments, you should keep those as they are and also export the equivalent flat configs as described above. The `environments` key is ignored when ESLint is running in flat config mode. +1. **Export both eslintrc and flat configs.** The `configs` key is only validated when a config is used, so you can provide both formats of configs in the `configs` key. We recommend that you append older format configs with `-legacy` to make it clear that these configs will not be supported in the future. For example, if your primary config is called `recommended` and is in flat config format, then you can also have a config named `recommended-legacy` that is the eslintrc config format. + +## Further Reading + +* [Overview of the flat config file format blog post](https://eslint.org/blog/2022/08/new-config-system-part-2/) +* [API usage of new configuration system blog post](https://eslint.org/blog/2022/08/new-config-system-part-3/) +* [Background to new configuration system blog post](https://eslint.org/blog/2022/08/new-config-system-part-1/) From dcfe5739c374c9d7ed21f14027870ec0fd453661 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Tue, 17 Oct 2023 16:50:14 +0200 Subject: [PATCH 221/248] fix: add preceding semicolon in suggestions of `no-object-constructor` (#17649) * Add semicolon in suggestions of `no-object-constructor` * Apply suggestions from code review * `output` in test suggestions * Apply suggestions --- lib/rules/no-object-constructor.js | 108 +++++++++- tests/lib/rules/no-object-constructor.js | 258 ++++++++++++++++++++++- 2 files changed, 360 insertions(+), 6 deletions(-) diff --git a/lib/rules/no-object-constructor.js b/lib/rules/no-object-constructor.js index 1299779f7ec8..0659d1633816 100644 --- a/lib/rules/no-object-constructor.js +++ b/lib/rules/no-object-constructor.js @@ -9,12 +9,51 @@ // Requirements //------------------------------------------------------------------------------ -const { getVariableByName, isArrowToken } = require("./utils/ast-utils"); +const { getVariableByName, isArrowToken, isClosingBraceToken, isClosingParenToken } = require("./utils/ast-utils"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ +const BREAK_OR_CONTINUE = new Set(["BreakStatement", "ContinueStatement"]); + +// Declaration types that must contain a string Literal node at the end. +const DECLARATIONS = new Set(["ExportAllDeclaration", "ExportNamedDeclaration", "ImportDeclaration"]); + +const IDENTIFIER_OR_KEYWORD = new Set(["Identifier", "Keyword"]); + +// Keywords that can immediately precede an ExpressionStatement node, mapped to the their node types. +const NODE_TYPES_BY_KEYWORD = { + __proto__: null, + break: "BreakStatement", + continue: "ContinueStatement", + debugger: "DebuggerStatement", + do: "DoWhileStatement", + else: "IfStatement", + return: "ReturnStatement", + yield: "YieldExpression" +}; + +/* + * Before an opening parenthesis, `>` (for JSX), and postfix `++` and `--` always trigger ASI; + * the tokens `:`, `;`, `{` and `=>` don't expect a semicolon, as that would count as an empty statement. + */ +const PUNCTUATORS = new Set([":", ";", ">", "{", "=>", "++", "--"]); + +/* + * Statements that can contain an `ExpressionStatement` after a closing parenthesis. + * DoWhileStatement is an exception in that it always triggers ASI after the closing parenthesis. + */ +const STATEMENTS = new Set([ + "DoWhileStatement", + "ForInStatement", + "ForOfStatement", + "ForStatement", + "IfStatement", + "WhileStatement", + "WithStatement" +]); + /** * Tests if a node appears at the beginning of an ancestor ExpressionStatement node. * @param {ASTNode} node The node to check. @@ -53,7 +92,8 @@ module.exports = { messages: { preferLiteral: "The object literal notation {} is preferable.", - useLiteral: "Replace with '{{replacement}}'." + useLiteral: "Replace with '{{replacement}}'.", + useLiteralAfterSemicolon: "Replace with '{{replacement}}', add preceding semicolon." } }, @@ -80,6 +120,50 @@ module.exports = { return false; } + /** + * Determines whether a parenthesized object literal that replaces a specified node needs to be preceded by a semicolon. + * @param {ASTNode} node The node to be replaced. This node should be at the start of an `ExpressionStatement` or at the start of the body of an `ArrowFunctionExpression`. + * @returns {boolean} Whether a semicolon is required before the parenthesized object literal. + */ + function needsSemicolon(node) { + const prevToken = sourceCode.getTokenBefore(node); + + if (!prevToken || prevToken.type === "Punctuator" && PUNCTUATORS.has(prevToken.value)) { + return false; + } + + const prevNode = sourceCode.getNodeByRangeIndex(prevToken.range[0]); + + if (isClosingParenToken(prevToken)) { + return !STATEMENTS.has(prevNode.type); + } + + if (isClosingBraceToken(prevToken)) { + return ( + prevNode.type === "BlockStatement" && prevNode.parent.type === "FunctionExpression" || + prevNode.type === "ClassBody" && prevNode.parent.type === "ClassExpression" || + prevNode.type === "ObjectExpression" + ); + } + + if (IDENTIFIER_OR_KEYWORD.has(prevToken.type)) { + if (BREAK_OR_CONTINUE.has(prevNode.parent.type)) { + return false; + } + + const keyword = prevToken.value; + const nodeType = NODE_TYPES_BY_KEYWORD[keyword]; + + return prevNode.type !== nodeType; + } + + if (prevToken.type === "String") { + return !DECLARATIONS.has(prevNode.parent.type); + } + + return true; + } + /** * Reports on nodes where the `Object` constructor is called without arguments. * @param {ASTNode} node The node to evaluate. @@ -93,16 +177,30 @@ module.exports = { const variable = getVariableByName(sourceCode.getScope(node), "Object"); if (variable && variable.identifiers.length === 0) { - const replacement = needsParentheses(node) ? "({})" : "{}"; + let replacement; + let fixText; + let messageId = "useLiteral"; + + if (needsParentheses(node)) { + replacement = "({})"; + if (needsSemicolon(node)) { + fixText = ";({})"; + messageId = "useLiteralAfterSemicolon"; + } else { + fixText = "({})"; + } + } else { + replacement = fixText = "{}"; + } context.report({ node, messageId: "preferLiteral", suggest: [ { - messageId: "useLiteral", + messageId, data: { replacement }, - fix: fixer => fixer.replaceText(node, replacement) + fix: fixer => fixer.replaceText(node, fixText) } ] }); diff --git a/tests/lib/rules/no-object-constructor.js b/tests/lib/rules/no-object-constructor.js index 789b9e84cb7e..4d51f2a21d69 100644 --- a/tests/lib/rules/no-object-constructor.js +++ b/tests/lib/rules/no-object-constructor.js @@ -104,6 +104,262 @@ ruleTester.run("no-object-constructor", rule, { output: "({} instanceof Object);" }] }] - } + }, + + ...[ + + // Semicolon required before `({})` to compensate for ASI + { + code: ` + foo + Object() + ` + }, + { + code: ` + foo() + Object() + ` + }, + { + code: ` + new foo + Object() + ` + }, + { + code: ` + (a++) + Object() + ` + }, + { + code: ` + ++a + Object() + ` + }, + { + code: ` + const foo = function() {} + Object() + ` + }, + { + code: ` + const foo = class {} + Object() + ` + }, + { + code: ` + foo = this.return + Object() + ` + }, + { + code: ` + var yield = bar.yield + Object() + ` + }, + { + code: ` + var foo = { bar: baz } + Object() + ` + } + ].map(props => ({ + ...props, + errors: [{ + messageId: "preferLiteral", + suggestions: [{ + desc: "Replace with '({})', add preceding semicolon.", + messageId: "useLiteralAfterSemicolon", + output: props.code.replace(/(new )?Object\(\)/u, ";({})") + }] + }] + })), + + ...[ + + // No semicolon required before `({})` because ASI does not occur + { code: "Object()" }, + { + code: ` + {} + Object() + ` + }, + { + code: ` + function foo() {} + Object() + ` + }, + { + code: ` + class Foo {} + Object() + ` + }, + { code: "foo: Object();" }, + { code: "foo();Object();" }, + { code: "{ Object(); }" }, + { code: "if (a) Object();" }, + { code: "if (a); else Object();" }, + { code: "while (a) Object();" }, + { + code: ` + do Object(); + while (a); + ` + }, + { code: "for (let i = 0; i < 10; i++) Object();" }, + { code: "for (const prop in obj) Object();" }, + { code: "for (const element of iterable) Object();" }, + { code: "with (obj) Object();" }, + + // No semicolon required before `({})` because ASI still occurs + { + code: ` + const foo = () => {} + Object() + ` + }, + { + code: ` + a++ + Object() + ` + }, + { + code: ` + a-- + Object() + ` + }, + { + code: ` + function foo() { + return + Object(); + } + ` + }, + { + code: ` + function * foo() { + yield + Object(); + } + ` + }, + { + code: ` + do {} + while (a) + Object() + ` + }, + { + code: ` + debugger + Object() + ` + }, + { + code: ` + for (;;) { + break + Object() + } + ` + }, + { + code: ` + for (;;) { + continue + Object() + } + ` + }, + { + code: ` + foo: break foo + Object() + ` + }, + { + code: ` + foo: while (true) continue foo + Object() + ` + }, + { + code: ` + + Object() + `, + parserOptions: { ecmaFeatures: { jsx: true } } + }, + { + code: ` + + Object() + `, + parserOptions: { ecmaFeatures: { jsx: true } } + }, + { + code: ` + const foo = bar + export { foo } + Object() + `, + parserOptions: { sourceType: "module" } + }, + { + code: ` + export { foo } from 'bar' + Object() + `, + parserOptions: { sourceType: "module" } + }, + { + code: ` + export * as foo from 'bar' + Object() + `, + parserOptions: { sourceType: "module" } + }, + { + code: ` + import foo from 'bar' + Object() + `, + parserOptions: { sourceType: "module" } + }, + { + code: ` + var yield = 5; + + yield: while (foo) { + if (bar) + break yield + new Object(); + } + ` + } + ].map(props => ({ + ...props, + errors: [{ + messageId: "preferLiteral", + suggestions: [{ + desc: "Replace with '({})'.", + messageId: "useLiteral", + output: props.code.replace(/(new )?Object\(\)/u, "({})") + }] + }] + })) ] }); From 70648ee49c07f7b533d09f6bf8a5291e5a5a8601 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Wed, 18 Oct 2023 00:08:38 +0900 Subject: [PATCH 222/248] feat: report-unused-disable-directive to report unused eslint-enable (#17611) * feat: report-unused-disable-directive to report unused eslint-enable * test: fix test case * chore: refactor * test: add test cases * chore: update order * chore: fix test case order * fix: revert test case * docs: update docs * chore: minor refactor * fix: false positive * chore: update comment * Update lib/linter/apply-disable-directives.js Co-authored-by: Milos Djermanovic * chore: refactored collectUsedEnableDirectives to return a Set * Update lib/linter/apply-disable-directives.js Co-authored-by: Milos Djermanovic * Update lib/linter/apply-disable-directives.js Co-authored-by: Nicholas C. Zakas * fix: doc comments * test: fix test cases * fix: remove withoutRuleIdKey and fix jsdoc * chore: refactor jsdoc * Update lib/linter/apply-disable-directives.js Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic Co-authored-by: Nicholas C. Zakas --- docs/src/integrate/nodejs-api.md | 4 +- docs/src/use/command-line-interface.md | 4 +- .../use/configure/configuration-files-new.md | 6 +- lib/linter/apply-disable-directives.js | 138 +- lib/options.js | 4 +- tests/lib/linter/apply-disable-directives.js | 1807 ++++++++++++++--- tests/lib/linter/linter.js | 1252 +++++++++++- 7 files changed, 2847 insertions(+), 368 deletions(-) diff --git a/docs/src/integrate/nodejs-api.md b/docs/src/integrate/nodejs-api.md index 5560a8080a0e..744f98295f49 100644 --- a/docs/src/integrate/nodejs-api.md +++ b/docs/src/integrate/nodejs-api.md @@ -152,7 +152,7 @@ The `ESLint` constructor takes an `options` object. If you omit the `options` ob * `options.plugins` (`Record | null`)
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.reportUnusedDisableDirectives` (`"error" | "warn" | "off" | null`)
- Default is `null`. The severity to report unused eslint-disable directives. If this option is a severity, it overrides the `reportUnusedDisableDirectives` setting in your configurations. + Default is `null`. The severity to report unused eslint-disable and eslint-enable directives. If this option is a severity, it overrides the `reportUnusedDisableDirectives` setting in your configurations. * `options.resolvePluginsRelativeTo` (`string` | `null`)
Default is `null`. The path to a directory where plugins should be resolved from. If `null` is present, ESLint loads plugins from the location of the configuration file that contains the plugin setting. If a path is present, ESLint loads all plugins from there. * `options.rulePaths` (`string[]`)
@@ -537,7 +537,7 @@ The most important method on `Linter` is `verify()`, which initiates linting of * `filterCodeBlock` - (optional) A function that decides which code blocks the linter should adopt. The function receives two arguments. The first argument is the virtual filename of a code block. The second argument is the text of the code block. If the function returned `true` then the linter adopts the code block. If the function was omitted, the linter adopts only `*.js` code blocks. If you provided a `filterCodeBlock` function, it overrides this default behavior, so the linter doesn't adopt `*.js` code blocks automatically. * `disableFixes` - (optional) when set to `true`, the linter doesn't make either the `fix` or `suggestions` property of the lint result. * `allowInlineConfig` - (optional) set to `false` to disable inline comments from changing ESLint rules. - * `reportUnusedDisableDirectives` - (optional) when set to `true`, adds reported errors for unused `eslint-disable` directives when no problems would be reported in the disabled area anyway. + * `reportUnusedDisableDirectives` - (optional) when set to `true`, adds reported errors for unused `eslint-disable` and `eslint-enable` directives when no problems would be reported in the disabled area anyway. If the third argument is a string, it is interpreted as the `filename`. diff --git a/docs/src/use/command-line-interface.md b/docs/src/use/command-line-interface.md index da4faf70ebae..6087aae4fcc6 100644 --- a/docs/src/use/command-line-interface.md +++ b/docs/src/use/command-line-interface.md @@ -97,7 +97,7 @@ 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 directives + --report-unused-disable-directives Adds reported errors for unused eslint-disable and eslint-enable directives Caching: --cache Only check changed files - default: false @@ -577,7 +577,7 @@ This option causes ESLint to report directive comments like `// eslint-disable-l * **Argument Type**: No argument. -This can be useful to prevent future errors from unexpectedly being suppressed, by cleaning up old `eslint-disable` comments which are no longer applicable. +This can be useful to prevent future errors from unexpectedly being suppressed, by cleaning up old `eslint-disable` and `eslint-enable` comments which are no longer applicable. ::: warning When using this option, it is possible that new errors start being reported whenever ESLint or custom rules are upgraded. diff --git a/docs/src/use/configure/configuration-files-new.md b/docs/src/use/configure/configuration-files-new.md index 2d472a4e7e15..d3ba4cd2bcfb 100644 --- a/docs/src/use/configure/configuration-files-new.md +++ b/docs/src/use/configure/configuration-files-new.md @@ -76,7 +76,7 @@ Each configuration object contains all of the information ESLint needs to execut * `parserOptions` - An object specifying additional options that are passed directly to the `parse()` or `parseForESLint()` method on the parser. The available options are parser-dependent. * `linterOptions` - An object containing settings related to the linting process. * `noInlineConfig` - A Boolean value indicating if inline configuration is allowed. - * `reportUnusedDisableDirectives` - A Boolean value indicating if unused disable directives should be tracked and reported. + * `reportUnusedDisableDirectives` - A Boolean value indicating if unused disable and enable directives should be tracked and reported. * `processor` - Either an object containing `preprocess()` and `postprocess()` methods or a string indicating the name of a processor inside of a plugin (i.e., `"pluginName/processorName"`). * `plugins` - An object containing a name-value mapping of plugin names to plugin objects. When `files` is specified, these plugins are only available to the matching files. * `rules` - An object containing the configured rules. When `files` or `ignores` are specified, these rule configurations are only available to the matching files. @@ -244,7 +244,7 @@ export default [ #### Reporting unused disable directives -Disable directives such as `/*eslint-disable*/` and `/*eslint-disable-next-line*/` are used to disable ESLint rules around certain portions of code. As code changes, it's possible for these directives to no longer be needed because the code has changed in such a way that the rule is no longer triggered. You can enable reporting of these unused disable directives by setting the `reportUnusedDisableDirectives` option to `true`, as in this example: +Disable and enable directives such as `/*eslint-disable*/`, `/*eslint-enable*/` and `/*eslint-disable-next-line*/` are used to disable ESLint rules around certain portions of code. As code changes, it's possible for these directives to no longer be needed because the code has changed in such a way that the rule is no longer triggered. You can enable reporting of these unused disable directives by setting the `reportUnusedDisableDirectives` option to `true`, as in this example: ```js export default [ @@ -257,7 +257,7 @@ export default [ ]; ``` -By default, unused disable directives are reported as warnings. You can change this setting using the `--report-unused-disable-directives` command line option. +By default, unused disable and enable directives are reported as warnings. You can change this setting using the `--report-unused-disable-directives` command line option. ### Configuring language options diff --git a/lib/linter/apply-disable-directives.js b/lib/linter/apply-disable-directives.js index 55f7683f3f53..c5e3c9ddc1ce 100644 --- a/lib/linter/apply-disable-directives.js +++ b/lib/linter/apply-disable-directives.js @@ -30,7 +30,7 @@ function compareLocations(itemA, itemB) { /** * Groups a set of directives into sub-arrays by their parent comment. - * @param {Directive[]} directives Unused directives to be removed. + * @param {Iterable} directives Unused directives to be removed. * @returns {Directive[][]} Directives grouped by their parent comment. */ function groupByParentComment(directives) { @@ -177,10 +177,10 @@ function createCommentRemoval(directives, commentToken) { /** * Parses details from directives to create output Problems. - * @param {Directive[]} allDirectives Unused directives to be removed. + * @param {Iterable} allDirectives Unused directives to be removed. * @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems. */ -function processUnusedDisableDirectives(allDirectives) { +function processUnusedDirectives(allDirectives) { const directiveGroups = groupByParentComment(allDirectives); return directiveGroups.flatMap( @@ -199,6 +199,95 @@ function processUnusedDisableDirectives(allDirectives) { ); } +/** + * Collect eslint-enable comments that are removing suppressions by eslint-disable comments. + * @param {Directive[]} directives The directives to check. + * @returns {Set} The used eslint-enable comments + */ +function collectUsedEnableDirectives(directives) { + + /** + * A Map of `eslint-enable` keyed by ruleIds that may be marked as used. + * If `eslint-enable` does not have a ruleId, the key will be `null`. + * @type {Map} + */ + const enabledRules = new Map(); + + /** + * A Set of `eslint-enable` marked as used. + * It is also the return value of `collectUsedEnableDirectives` function. + * @type {Set} + */ + const usedEnableDirectives = new Set(); + + /* + * Checks the directives backwards to see if the encountered `eslint-enable` is used by the previous `eslint-disable`, + * and if so, stores the `eslint-enable` in `usedEnableDirectives`. + */ + for (let index = directives.length - 1; index >= 0; index--) { + const directive = directives[index]; + + if (directive.type === "disable") { + if (enabledRules.size === 0) { + continue; + } + if (directive.ruleId === null) { + + // If encounter `eslint-disable` without ruleId, + // mark all `eslint-enable` currently held in enabledRules as used. + // e.g. + // /* eslint-disable */ <- current directive + // /* eslint-enable rule-id1 */ <- used + // /* eslint-enable rule-id2 */ <- used + // /* eslint-enable */ <- used + for (const enableDirective of enabledRules.values()) { + usedEnableDirectives.add(enableDirective); + } + enabledRules.clear(); + } else { + const enableDirective = enabledRules.get(directive.ruleId); + + if (enableDirective) { + + // If encounter `eslint-disable` with ruleId, and there is an `eslint-enable` with the same ruleId in enabledRules, + // mark `eslint-enable` with ruleId as used. + // e.g. + // /* eslint-disable rule-id */ <- current directive + // /* eslint-enable rule-id */ <- used + usedEnableDirectives.add(enableDirective); + } else { + const enabledDirectiveWithoutRuleId = enabledRules.get(null); + + if (enabledDirectiveWithoutRuleId) { + + // If encounter `eslint-disable` with ruleId, and there is no `eslint-enable` with the same ruleId in enabledRules, + // mark `eslint-enable` without ruleId as used. + // e.g. + // /* eslint-disable rule-id */ <- current directive + // /* eslint-enable */ <- used + usedEnableDirectives.add(enabledDirectiveWithoutRuleId); + } + } + } + } else if (directive.type === "enable") { + if (directive.ruleId === null) { + + // If encounter `eslint-enable` without ruleId, the `eslint-enable` that follows it are unused. + // So clear enabledRules. + // e.g. + // /* eslint-enable */ <- current directive + // /* eslint-enable rule-id *// <- unused + // /* eslint-enable */ <- unused + enabledRules.clear(); + enabledRules.set(null, directive); + } else { + enabledRules.set(directive.ruleId, directive); + } + } + } + return usedEnableDirectives; +} + /** * This is the same as the exported function, except that it * doesn't handle disable-line and disable-next-line directives, and it always reports unused @@ -206,7 +295,7 @@ function processUnusedDisableDirectives(allDirectives) { * @param {Object} options options for applying directives. This is the same as the options * for the exported function, except that `reportUnusedDisableDirectives` is not supported * (this function always reports unused disable directives). - * @returns {{problems: LintMessage[], unusedDisableDirectives: LintMessage[]}} An object with a list + * @returns {{problems: LintMessage[], unusedDirectives: LintMessage[]}} An object with a list * of problems (including suppressed ones) and unused eslint-disable directives */ function applyDirectives(options) { @@ -258,17 +347,42 @@ function applyDirectives(options) { const unusedDisableDirectivesToReport = options.directives .filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive)); - const processed = processUnusedDisableDirectives(unusedDisableDirectivesToReport); - const unusedDisableDirectives = processed + const unusedEnableDirectivesToReport = new Set( + options.directives.filter(directive => directive.unprocessedDirective.type === "enable") + ); + + /* + * If directives has the eslint-enable directive, + * check whether the eslint-enable comment is used. + */ + if (unusedEnableDirectivesToReport.size > 0) { + for (const directive of collectUsedEnableDirectives(options.directives)) { + unusedEnableDirectivesToReport.delete(directive); + } + } + + const processed = processUnusedDirectives(unusedDisableDirectivesToReport) + .concat(processUnusedDirectives(unusedEnableDirectivesToReport)); + + const unusedDirectives = processed .map(({ description, fix, unprocessedDirective }) => { const { parentComment, type, line, column } = unprocessedDirective; + let message; + + if (type === "enable") { + message = description + ? `Unused eslint-enable directive (no matching eslint-disable directives were found for ${description}).` + : "Unused eslint-enable directive (no matching eslint-disable directives were found)."; + } else { + message = description + ? `Unused eslint-disable directive (no problems were reported from ${description}).` + : "Unused eslint-disable directive (no problems were reported)."; + } return { ruleId: null, - message: description - ? `Unused eslint-disable directive (no problems were reported from ${description}).` - : "Unused eslint-disable directive (no problems were reported).", + message, line: type === "disable-next-line" ? parentComment.commentToken.loc.start.line : line, column: type === "disable-next-line" ? parentComment.commentToken.loc.start.column + 1 : column, severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2, @@ -277,7 +391,7 @@ function applyDirectives(options) { }; }); - return { problems, unusedDisableDirectives }; + return { problems, unusedDirectives }; } /** @@ -344,8 +458,8 @@ module.exports = ({ directives, disableFixes, problems, reportUnusedDisableDirec return reportUnusedDisableDirectives !== "off" ? lineDirectivesResult.problems - .concat(blockDirectivesResult.unusedDisableDirectives) - .concat(lineDirectivesResult.unusedDisableDirectives) + .concat(blockDirectivesResult.unusedDirectives) + .concat(lineDirectivesResult.unusedDirectives) .sort(compareLocations) : lineDirectivesResult.problems; }; diff --git a/lib/options.js b/lib/options.js index ae9a5d5552a2..81c0fa60ab98 100644 --- a/lib/options.js +++ b/lib/options.js @@ -47,7 +47,7 @@ const optionator = require("optionator"); * @property {Object} [parserOptions] Specify parser options * @property {string[]} [plugin] Specify plugins * @property {string} [printConfig] Print the configuration for the given file - * @property {boolean | undefined} reportUnusedDisableDirectives Adds reported errors for unused eslint-disable directives + * @property {boolean | undefined} reportUnusedDisableDirectives Adds reported errors for unused eslint-disable and eslint-enable directives * @property {string} [resolvePluginsRelativeTo] A folder where plugins should be resolved from, CWD by default * @property {Object} [rule] Specify rules * @property {string[]} [rulesdir] Load additional rules from this directory. Deprecated: Use rules from plugins @@ -304,7 +304,7 @@ module.exports = function(usingFlatConfig) { option: "report-unused-disable-directives", type: "Boolean", default: void 0, - description: "Adds reported errors for unused eslint-disable directives" + description: "Adds reported errors for unused eslint-disable and eslint-enable directives" }, { heading: "Caching" diff --git a/tests/lib/linter/apply-disable-directives.js b/tests/lib/linter/apply-disable-directives.js index de56f729627f..d56bf5bc1b0f 100644 --- a/tests/lib/linter/apply-disable-directives.js +++ b/tests/lib/linter/apply-disable-directives.js @@ -680,7 +680,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { parentComment: createParentComment([0, 31]), type: "disable-next-line", line: 1, column: 1, ruleId: null, justification: "j1" }, - { type: "enable", line: 1, column: 5, ruleId: null, justification: "j2" } + { parentComment: createParentComment([31, 50]), type: "enable", line: 1, column: 31, ruleId: null, justification: "j2" } ], problems: [{ line: 2, column: 2, ruleId: "foo" }] }), @@ -1365,28 +1365,140 @@ describe("apply-disable-directives", () => { ); }); - it("Adds a problem for // eslint-disable-line", () => { + it("Adds a problem for /* eslint-enable */", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [{ - parentComment: createParentComment([0, 22]), - type: "disable-line", + parentComment: createParentComment([0, 20]), + type: "enable", line: 1, column: 1, - ruleId: null, justification: "justification" }], problems: [], reportUnusedDisableDirectives: "error" }), + [{ + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " " + }, + severity: 2, + nodeType: null + }] + ); + }); + + it("Does not fix a problem for /* eslint-enable */ when disableFixes is enabled", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [{ + parentComment: createParentComment([0, 20]), + type: "enable", + line: 1, + column: 1, + justification: "justification" + }], + disableFixes: true, + problems: [], + reportUnusedDisableDirectives: "error" + }), + [{ + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 1, + column: 1, + severity: 2, + nodeType: null + }] + ); + }); + + it("Does not add a problem for /* eslint-disable */ /* (problem) */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { type: "disable", line: 1, column: 1, ruleId: null, justification: "justification" }, + { type: "enable", line: 3, column: 1, ruleId: null, justification: "justification" } + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error" + }), + [{ line: 2, column: 1, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] + ); + }); + + it("Adds a problem for /* eslint-enable foo */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [{ + parentComment: createParentComment([0, 21]), + type: "enable", + line: 1, + column: 1, + ruleId: "foo", + justification: "justification" + }], + problems: [], + reportUnusedDisableDirectives: "error" + }), + [{ + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", + line: 1, + column: 1, + fix: { + range: [0, 21], + text: " " + }, + severity: 2, + nodeType: null + }] + ); + }); + + it("Adds a problem for /* eslint-disable not-foo */ /* (problem from not-foo) */ /* eslint-enable foo */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment: createParentComment([0, 24]), + type: "disable", + line: 1, + column: 1, + ruleId: "not-foo", + justification: "justification" + }, + { + parentComment: createParentComment([48, 72]), + type: "enable", + line: 3, + column: 1, + ruleId: "foo", + justification: "justification" + } + ], + problems: [{ line: 2, column: 1, ruleId: "not-foo" }], + reportUnusedDisableDirectives: "error" + }), [ + { + ruleId: "not-foo", + line: 2, + column: 1, + suppressions: [{ justification: "justification", kind: "directive" }] + }, { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", + line: 3, column: 1, fix: { - range: [0, 22], + range: [48, 72], text: " " }, severity: 2, @@ -1396,40 +1508,1155 @@ describe("apply-disable-directives", () => { ); }); + it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable */ /* eslint-enable */ /* eslint-enable foo */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment: createParentComment([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1" + }, + { + parentComment: createParentComment([42, 63]), + type: "enable", + line: 3, + column: 1, + ruleId: null, + justification: "j1" + }, + { + parentComment: createParentComment([63, 84]), + type: "enable", + line: 4, + column: 1, + ruleId: null, + justification: "j1" + }, + { + parentComment: createParentComment([84, 105]), + type: "enable", + line: 5, + column: 1, + ruleId: "foo", + justification: "j2" + } + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: "foo", + line: 2, + column: 1, + suppressions: [{ justification: "j1", kind: "directive" }] + }, + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", + fix: { + range: [63, 84], + text: " " + }, + line: 4, + column: 1, + severity: 2, + nodeType: null + }, + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", + fix: { + range: [84, 105], + text: " " + }, + line: 5, + column: 1, + severity: 2, + nodeType: null + } + ] + ); + }); - it("Does not add a problem for // eslint-disable-line (problem)", () => { + it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable */ /* eslint-enable */", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-line", line: 1, column: 1, ruleId: null, justification: "justification" }], - problems: [{ line: 1, column: 10, ruleId: "foo" }], + directives: [ + { + parentComment: createParentComment([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1" + }, + { + parentComment: createParentComment([42, 63]), + type: "enable", + line: 3, + column: 1, + ruleId: null, + justification: "j1" + }, + { + parentComment: createParentComment([63, 84]), + type: "enable", + line: 4, + column: 1, + ruleId: null, + justification: "j1" + } + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], reportUnusedDisableDirectives: "error" }), - [{ line: 1, column: 10, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] + [ + { + ruleId: "foo", + line: 2, + column: 1, + suppressions: [{ justification: "j1", kind: "directive" }] + }, + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", + fix: { + range: [63, 84], + text: " " + }, + line: 4, + column: 1, + severity: 2, + nodeType: null + } + ] ); }); - it("Adds a problem for // eslint-disable-next-line", () => { + it("Adds two problems for /* eslint-enable */ /* eslint-enable */", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 27]), - type: "disable-next-line", + directives: [ + { + parentComment: createParentComment([0, 21]), + type: "enable", + line: 1, + column: 1, + ruleId: null, + justification: "j1" + }, + { + parentComment: createParentComment([21, 42]), + type: "enable", + line: 2, + column: 1, + ruleId: null, + justification: "j2" + } + ], + problems: [], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", line: 1, - column: 2, + column: 1, + fix: { + range: [0, 21], + text: " " + }, + severity: 2, + nodeType: null + }, + { ruleId: null, - justification: "justification" - }], + message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 2, + column: 1, + fix: { + range: [21, 42], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Adds a problem for /* eslint-enable */ /* eslint-disable */ /* (problem) */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment: createParentComment([0, 21]), + type: "enable", + line: 1, + column: 1, + ruleId: null, + justification: "j1" + }, + { + parentComment: createParentComment([21, 42]), + type: "disable", + line: 2, + column: 1, + ruleId: null, + justification: "j2" + }, + { + parentComment: createParentComment([63, 84]), + type: "enable", + line: 4, + column: 1, + ruleId: null, + justification: "j3" + } + ], + problems: [{ line: 3, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 1, + column: 1, + fix: { + range: [0, 21], + text: " " + }, + severity: 2, + nodeType: null + }, + { + line: 3, + column: 1, + ruleId: "foo", + suppressions: [{ kind: "directive", justification: "j2" }] + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable */ /* eslint-enable foo */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment: createParentComment([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1" + }, + { + parentComment: createParentComment([42, 63]), + type: "enable", + line: 3, + column: 1, + ruleId: null, + justification: "j2" + }, + { + parentComment: createParentComment([63, 84]), + type: "enable", + line: 4, + column: 1, + ruleId: "foo", + justification: "j3" + } + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + line: 2, + column: 1, + ruleId: "foo", + suppressions: [{ kind: "directive", justification: "j1" }] + }, + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", + line: 4, + column: 1, + fix: { + range: [63, 84], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Does not add a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable foo */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { type: "disable", line: 1, column: 1, ruleId: "foo", justification: "j1" }, + { type: "enable", line: 3, column: 1, ruleId: "foo", justification: "j2" } + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error" + }), + [{ line: 2, column: 1, ruleId: "foo", suppressions: [{ kind: "directive", justification: "j1" }] }] + ); + }); + + it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable foo */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment: createParentComment([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1" + }, + { + parentComment: createParentComment([42, 63]), + type: "enable", + line: 3, + column: 1, + ruleId: "foo", + justification: "j2" + }, + { + parentComment: createParentComment([63, 84]), + type: "enable", + line: 4, + column: 1, + ruleId: null, + justification: "j3" + } + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + line: 2, + column: 1, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j1" } + ] + }, + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 4, + column: 1, + fix: { + range: [63, 84], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable bar */ /* (problem from bar) */ /* eslint-enable foo */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment: createParentComment([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: "bar", + justification: "j1" + }, + { + parentComment: createParentComment([60, 80]), + type: "enable", + line: 3, + column: 1, + ruleId: "foo", + justification: "j2" + }, + { + parentComment: createParentComment([80, 100]), + type: "enable", + line: 4, + column: 1, + ruleId: null, + justification: "j3" + } + ], + problems: [{ line: 2, column: 1, ruleId: "bar" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + line: 2, + column: 1, + ruleId: "bar", + suppressions: [{ kind: "directive", justification: "j1" }] + }, + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", + line: 3, + column: 1, + fix: { + range: [60, 80], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable foo */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment: createParentComment([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1" + }, + { + parentComment: createParentComment([60, 80]), + type: "enable", + line: 3, + column: 1, + ruleId: "foo", + justification: "j2" + }, + { + parentComment: createParentComment([80, 100]), + type: "enable", + line: 4, + column: 1, + ruleId: "foo", + justification: "j3" + } + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + line: 2, + column: 1, + ruleId: "foo", + suppressions: [{ kind: "directive", justification: "j1" }] + }, + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", + line: 4, + column: 1, + fix: { + range: [80, 100], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Adds two problems for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable foo */ /* eslint-enable foo */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment: createParentComment([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1" + }, + { + parentComment: createParentComment([40, 60]), + type: "enable", + line: 3, + column: 1, + ruleId: "foo", + justification: "j2" + }, + { + parentComment: createParentComment([60, 80]), + type: "enable", + line: 4, + column: 1, + ruleId: "foo", + justification: "j3" + }, + { + parentComment: createParentComment([80, 100]), + type: "enable", + line: 5, + column: 1, + ruleId: null, + justification: "j4" + } + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: "foo", + line: 2, + column: 1, + suppressions: [{ kind: "directive", justification: "j1" }] + }, + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", + line: 4, + column: 1, + fix: { + range: [60, 80], + text: " " + }, + severity: 2, + nodeType: null + }, + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 5, + column: 1, + fix: { + range: [80, 100], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-disable used */ /* (problem from used) */ /* eslint-enable used */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment: createParentComment([0, 20]), + ruleId: "used", + type: "disable", + line: 1, + column: 1, + justification: "j1" + }, + { + parentComment: createParentComment([40, 60]), + ruleId: "used", + type: "disable", + line: 3, + column: 1, + justification: "j2" + }, + { + parentComment: createParentComment([80, 100]), + ruleId: "used", + type: "enable", + line: 5, + column: 1, + justification: "j3" + }, + { + parentComment: createParentComment([100, 120]), + ruleId: null, + type: "enable", + line: 6, + column: 1, + justification: "j4" + } + ], + problems: [{ line: 2, column: 1, ruleId: "used" }, { line: 4, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [{ kind: "directive", justification: "j1" }] + }, + { + line: 4, + column: 1, + ruleId: "used", + suppressions: [{ kind: "directive", justification: "j1" }, { kind: "directive", justification: "j2" }] + }, + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 6, + column: 1, + fix: { + range: [100, 120], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Adds a problem for // eslint-disable-line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [{ + parentComment: createParentComment([0, 22]), + type: "disable-line", + line: 1, + column: 1, + ruleId: null, + justification: "justification" + }], + problems: [], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 22], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + + it("Does not add a problem for // eslint-disable-line (problem)", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [{ type: "disable-line", line: 1, column: 1, ruleId: null, justification: "justification" }], + problems: [{ line: 1, column: 10, ruleId: "foo" }], + reportUnusedDisableDirectives: "error" + }), + [{ line: 1, column: 10, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] + ); + }); + + it("Adds a problem for // eslint-disable-next-line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [{ + parentComment: createParentComment([0, 27]), + type: "disable-next-line", + line: 1, + column: 2, + ruleId: null, + justification: "justification" + }], + problems: [], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 2, + fix: { + range: [0, 27], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Does not add a problem for // eslint-disable-next-line \\n (problem)", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: null, justification: "justification" }], + problems: [{ line: 2, column: 10, ruleId: "foo" }], + reportUnusedDisableDirectives: "error" + }), + [{ line: 2, column: 10, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] + ); + }); + + it("adds two problems for /* eslint-disable */ // eslint-disable-line", () => { + 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 } + ], + problems: [], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " " + }, + severity: 2, + nodeType: null + }, + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 22, + fix: { + range: [20, 43], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Does not add problems when reportUnusedDisableDirectives: \"off\" is used", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [{ + parentComment: createParentComment([0, 27]), + type: "disable-next-line", + line: 1, + column: 1, + ruleId: null, + justification: "justification" + }], + problems: [], + reportUnusedDisableDirectives: "off" + }), + [] + ); + }); + }); + + describe("unused rules within directives", () => { + it("Adds a problem for /* eslint-disable used, unused */", () => { + const parentComment = createParentComment([0, 32], " eslint-disable used, unused ", ["used", "unused"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "used", + type: "disable", + line: 1, + column: 18, + justification: "j1" + }, + { + parentComment, + ruleId: "unused", + type: "disable", + line: 1, + column: 22, + justification: "j2" + } + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused').", + line: 1, + column: 22, + fix: { + range: [22, 30], + text: "" + }, + severity: 2, + nodeType: null + }, + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [{ kind: "directive", justification: "j1" }] + } + ] + ); + }); + it("Adds a problem for /* eslint-disable used , unused , -- unused and used are ok */", () => { + const parentComment = createParentComment([0, 62], " eslint-disable used , unused , -- unused and used are ok ", ["used", "unused"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "used", + type: "disable", + line: 1, + column: 18, + justification: "j1" + }, + { + parentComment, + ruleId: "unused", + type: "disable", + line: 1, + column: 24, + justification: "j2" + } + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused').", + line: 1, + column: 24, + fix: { + range: [23, 32], + text: "" + }, + severity: 2, + nodeType: null + }, + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [{ kind: "directive", justification: "j1" }] + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable unused, used */", () => { + const parentComment = createParentComment([0, 32], " eslint-disable unused, used ", ["unused", "used"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "unused", + type: "disable", + line: 1, + column: 18, + justification: "j1" + }, + { + parentComment, + ruleId: "used", + type: "disable", + line: 1, + column: 25, + justification: "j2" + } + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused').", + line: 1, + column: 18, + fix: { + range: [18, 26], + text: "" + }, + severity: 2, + nodeType: null + }, + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [{ kind: "directive", justification: "j2" }] + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable unused,, ,, used */", () => { + const parentComment = createParentComment([0, 37], " eslint-disable unused,, ,, used ", ["unused", "used"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "unused", + type: "disable", + line: 1, + column: 18, + justification: "j1" + }, + { + parentComment, + ruleId: "used", + type: "disable", + line: 1, + column: 29, + justification: "j2" + } + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused').", + line: 1, + column: 18, + fix: { + range: [18, 25], + text: "" + }, + severity: 2, + nodeType: null + }, + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [{ kind: "directive", justification: "j2" }] + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable unused-1, unused-2, used */", () => { + const parentComment = createParentComment([0, 45], " eslint-disable unused-1, unused-2, used ", ["unused-1", "unused-2", "used"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "unused-1", + type: "disable", + line: 1, + column: 18, + justification: "j1" + }, + { + parentComment, + ruleId: "unused-2", + type: "disable", + line: 1, + column: 28, + justification: "j2" + }, + { + parentComment, + ruleId: "used", + type: "disable", + line: 1, + column: 38, + justification: "j3" + } + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused-1').", + line: 1, + column: 18, + fix: { + range: [18, 28], + text: "" + }, + severity: 2, + nodeType: null + }, + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused-2').", + line: 1, + column: 28, + fix: { + range: [26, 36], + text: "" + }, + severity: 2, + nodeType: null + }, + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [{ kind: "directive", justification: "j3" }] + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable unused-1, unused-2, used, unused-3 */", () => { + const parentComment = createParentComment([0, 55], " eslint-disable unused-1, unused-2, used, unused-3 ", ["unused-1", "unused-2", "used", "unused-3"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "unused-1", + type: "disable", + line: 1, + column: 18, + justification: "j1" + }, + { + parentComment, + ruleId: "unused-2", + type: "disable", + line: 1, + column: 28, + justification: "j2" + }, + { + parentComment, + ruleId: "used", + type: "disable", + line: 1, + column: 38, + justification: "j3" + }, + { + parentComment, + ruleId: "unused-3", + type: "disable", + line: 1, + column: 43, + justification: "j4" + } + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused-1').", + line: 1, + column: 18, + fix: { + range: [18, 28], + text: "" + }, + severity: 2, + nodeType: null + }, + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused-2').", + line: 1, + column: 28, + fix: { + range: [26, 36], + text: "" + }, + severity: 2, + nodeType: null + }, + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused-3').", + line: 1, + column: 43, + fix: { + range: [42, 52], + text: "" + }, + severity: 2, + nodeType: null + }, + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [{ kind: "directive", justification: "j3" }] + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable unused-1, unused-2 */", () => { + const parentComment = createParentComment([0, 39], " eslint-disable unused-1, unused-2 ", ["unused-1", "unused-2"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "unused-1", + type: "disable", + line: 1, + column: 18, + justification: "j1" + }, + { + parentComment, + ruleId: "unused-2", + type: "disable", + line: 1, + column: 28, + justification: "j2" + } + ], problems: [], reportUnusedDisableDirectives: "error" }), [ { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", + message: "Unused eslint-disable directive (no problems were reported from 'unused-1' or 'unused-2').", line: 1, - column: 2, + column: 18, fix: { - range: [0, 27], + range: [0, 39], text: " " }, severity: 2, @@ -1439,23 +2666,33 @@ describe("apply-disable-directives", () => { ); }); - it("Does not add a problem for // eslint-disable-next-line \\n (problem)", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: null, justification: "justification" }], - problems: [{ line: 2, column: 10, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [{ line: 2, column: 10, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); + 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"]); - it("adds two problems for /* eslint-disable */ // eslint-disable-line", () => { 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 } + { + parentComment, + ruleId: "unused-1", + type: "disable", + line: 1, + column: 18 + }, + { + parentComment, + ruleId: "unused-2", + type: "disable", + line: 1, + column: 28 + }, + { + parentComment, + ruleId: "unused-3", + type: "disable", + line: 1, + column: 38 + } ], problems: [], reportUnusedDisableDirectives: "error" @@ -1463,23 +2700,11 @@ describe("apply-disable-directives", () => { [ { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 2, - nodeType: null - }, - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", + message: "Unused eslint-disable directive (no problems were reported from 'unused-1', 'unused-2', or 'unused-3').", line: 1, - column: 22, + column: 18, fix: { - range: [20, 43], + range: [0, 49], text: " " }, severity: 2, @@ -1489,263 +2714,356 @@ describe("apply-disable-directives", () => { ); }); - it("Does not add problems when reportUnusedDisableDirectives: \"off\" is used", () => { + it("Adds a problem for /* eslint-disable foo */ \\n (problem from foo and bar) // eslint-disable-line foo, bar", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 27]), - type: "disable-next-line", - line: 1, + directives: [ + { + parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), + ruleId: "foo", + type: "disable", + line: 1, + column: 1, + justification: "j1" + }, + { + parentComment: createParentComment([41, 81], " eslint-disable-line foo, bar", ["foo", "bar"]), + ruleId: "foo", + type: "disable-line", + line: 2, + column: 11, + justification: "j2" + }, + { + parentComment: createParentComment([41, 81], " eslint-disable-line foo, bar ", ["foo", "bar"]), + ruleId: "bar", + type: "disable-line", + line: 2, + column: 11, + justification: "j2" + } + ], + problems: [ + { line: 2, column: 1, ruleId: "bar" }, + { line: 2, column: 6, ruleId: "foo" } + ], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: "bar", + line: 2, column: 1, + suppressions: [{ kind: "directive", justification: "j2" }] + }, + { + ruleId: "foo", + line: 2, + column: 6, + suppressions: [ + { kind: "directive", justification: "j1" }, + { kind: "directive", justification: "j2" } + ] + }, + { ruleId: null, - justification: "justification" - }], - problems: [], - reportUnusedDisableDirectives: "off" - }), - [] + message: "Unused eslint-disable directive (no problems were reported from 'foo').", + line: 2, + column: 11, + fix: { + range: [64, 69], + text: "" + }, + severity: 2, + nodeType: null + } + ] ); }); - }); - 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"]); + 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"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, + parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), ruleId: "used", type: "disable", line: 1, - column: 18, + column: 1, justification: "j1" }, { parentComment, - ruleId: "unused", - type: "disable", - line: 1, - column: 22, + ruleId: "used", + type: "enable", + line: 3, + column: 1, justification: "j2" + }, + { + parentComment, + ruleId: "unused", + type: "enable", + line: 4, + column: 1, + justification: "j3" } ], problems: [{ line: 2, column: 1, ruleId: "used" }], reportUnusedDisableDirectives: "error" }), [ + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [{ kind: "directive", justification: "j1" }] + }, { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused').", - line: 1, - column: 22, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused').", + line: 4, + column: 1, fix: { - range: [22, 30], + range: [21, 29], text: "" }, severity: 2, nodeType: null - }, - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j1" }] } ] ); }); - 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"]); + 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"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, + parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), ruleId: "used", type: "disable", line: 1, - column: 18, + column: 1, justification: "j1" }, { parentComment, - ruleId: "unused", - type: "disable", - line: 1, - column: 24, + ruleId: "used", + type: "enable", + line: 3, + column: 1, justification: "j2" + }, + { + parentComment, + ruleId: "unused", + type: "enable", + line: 4, + column: 1, + justification: "j3" } ], problems: [{ line: 2, column: 1, ruleId: "used" }], reportUnusedDisableDirectives: "error" }), [ + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [{ kind: "directive", justification: "j1" }] + }, { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused').", - line: 1, - column: 24, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused').", + line: 4, + column: 1, fix: { - range: [23, 32], + range: [22, 31], text: "" }, severity: 2, nodeType: null - }, - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j1" }] } ] ); }); - it("Adds a problem for /* eslint-disable unused, used */", () => { - const parentComment = createParentComment([0, 32], " eslint-disable unused, used ", ["unused", "used"]); + 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"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, - ruleId: "unused", + parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), + ruleId: "used", type: "disable", line: 1, - column: 18, + column: 1, justification: "j1" }, { parentComment, - ruleId: "used", - type: "disable", - line: 1, - column: 25, + ruleId: "unused", + type: "enable", + line: 3, + column: 1, justification: "j2" + }, + { + parentComment, + ruleId: "used", + type: "enable", + line: 4, + column: 1, + justification: "j3" } ], problems: [{ line: 2, column: 1, ruleId: "used" }], reportUnusedDisableDirectives: "error" }), [ + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [{ kind: "directive", justification: "j1" }] + }, { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused').", - line: 1, - column: 18, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused').", + line: 3, + column: 1, fix: { - range: [18, 26], + range: [17, 25], text: "" }, severity: 2, nodeType: null - }, - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j2" }] } ] ); }); - it("Adds a problem for /* eslint-disable unused,, ,, used */", () => { - const parentComment = createParentComment([0, 37], " eslint-disable unused,, ,, used ", ["unused", "used"]); + 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"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, - ruleId: "unused", + parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), + ruleId: "used", type: "disable", line: 1, - column: 18, + column: 1, justification: "j1" }, { parentComment, - ruleId: "used", - type: "disable", - line: 1, - column: 29, + ruleId: "unused", + type: "enable", + line: 3, + column: 1, justification: "j2" + }, + { + parentComment, + ruleId: "used", + type: "enable", + line: 4, + column: 1, + justification: "j3" } ], problems: [{ line: 2, column: 1, ruleId: "used" }], reportUnusedDisableDirectives: "error" }), [ + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [{ kind: "directive", justification: "j1" }] + }, { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused').", - line: 1, - column: 18, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused').", + line: 3, + column: 1, fix: { - range: [18, 25], + range: [17, 24], text: "" }, severity: 2, nodeType: null - }, - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j2" }] } ] ); }); - 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"]); + 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"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, - ruleId: "unused-1", + parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), + ruleId: "used", type: "disable", line: 1, - column: 18, + column: 1, justification: "j1" }, { parentComment, - ruleId: "unused-2", - type: "disable", - line: 1, - column: 28, + ruleId: "unused-1", + type: "enable", + line: 3, + column: 1, justification: "j2" }, { parentComment, - ruleId: "used", - type: "disable", - line: 1, - column: 38, + ruleId: "unused-2", + type: "enable", + line: 4, + column: 1, justification: "j3" + }, + { + parentComment, + ruleId: "used", + type: "enable", + line: 5, + column: 1, + justification: "j4" } ], problems: [{ line: 2, column: 1, ruleId: "used" }], reportUnusedDisableDirectives: "error" }), [ + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [{ kind: "directive", justification: "j1" }] + }, { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused-1').", - line: 1, - column: 18, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-1').", + line: 3, + column: 1, fix: { - range: [18, 28], + range: [17, 27], text: "" }, severity: 2, @@ -1753,76 +3071,84 @@ describe("apply-disable-directives", () => { }, { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused-2').", - line: 1, - column: 28, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-2').", + line: 4, + column: 1, fix: { - range: [26, 36], + range: [25, 35], text: "" }, severity: 2, nodeType: null - }, - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j3" }] } ] ); }); - 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"]); + 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"]); assert.deepStrictEqual( applyDisableDirectives({ directives: [ { - parentComment, - ruleId: "unused-1", + parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), + ruleId: "used", type: "disable", line: 1, - column: 18, + column: 1, justification: "j1" }, { parentComment, - ruleId: "unused-2", - type: "disable", - line: 1, - column: 28, + ruleId: "unused-1", + type: "enable", + line: 3, + column: 1, justification: "j2" }, { parentComment, - ruleId: "used", - type: "disable", - line: 1, - column: 38, + ruleId: "unused-2", + type: "enable", + line: 4, + column: 1, justification: "j3" }, { parentComment, - ruleId: "unused-3", - type: "disable", - line: 1, - column: 43, + ruleId: "used", + type: "enable", + line: 5, + column: 1, justification: "j4" + }, + { + parentComment, + ruleId: "unused-3", + type: "enable", + line: 6, + column: 1, + justification: "j5" } ], problems: [{ line: 2, column: 1, ruleId: "used" }], reportUnusedDisableDirectives: "error" }), [ + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [{ kind: "directive", justification: "j1" }] + }, { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused-1').", - line: 1, - column: 18, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-1').", + line: 3, + column: 1, fix: { - range: [18, 28], + range: [17, 27], text: "" }, severity: 2, @@ -1830,11 +3156,11 @@ describe("apply-disable-directives", () => { }, { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused-2').", - line: 1, - column: 28, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-2').", + line: 4, + column: 1, fix: { - range: [26, 36], + range: [25, 35], text: "" }, severity: 2, @@ -1842,28 +3168,22 @@ describe("apply-disable-directives", () => { }, { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused-3').", - line: 1, - column: 43, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-3').", + line: 6, + column: 1, fix: { - range: [42, 52], + range: [41, 51], text: "" }, severity: 2, nodeType: null - }, - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j3" }] } ] ); }); - 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"]); + 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"]); assert.deepStrictEqual( applyDisableDirectives({ @@ -1871,7 +3191,7 @@ describe("apply-disable-directives", () => { { parentComment, ruleId: "unused-1", - type: "disable", + type: "enable", line: 1, column: 18, justification: "j1" @@ -1879,7 +3199,7 @@ describe("apply-disable-directives", () => { { parentComment, ruleId: "unused-2", - type: "disable", + type: "enable", line: 1, column: 28, justification: "j2" @@ -1891,7 +3211,7 @@ describe("apply-disable-directives", () => { [ { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused-1' or 'unused-2').", + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-1' or 'unused-2').", line: 1, column: 18, fix: { @@ -1905,8 +3225,8 @@ 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"]); + 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"]); assert.deepStrictEqual( applyDisableDirectives({ @@ -1914,21 +3234,21 @@ describe("apply-disable-directives", () => { { parentComment, ruleId: "unused-1", - type: "disable", + type: "enable", line: 1, column: 18 }, { parentComment, ruleId: "unused-2", - type: "disable", + type: "enable", line: 1, column: 28 }, { parentComment, ruleId: "unused-3", - type: "disable", + type: "enable", line: 1, column: 38 } @@ -1939,7 +3259,7 @@ describe("apply-disable-directives", () => { [ { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused-1', 'unused-2', or 'unused-3').", + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-1', 'unused-2', or 'unused-3').", line: 1, column: 18, fix: { @@ -1952,72 +3272,5 @@ describe("apply-disable-directives", () => { ] ); }); - - it("Adds a problem for /* eslint-disable foo */ \\n (problem from foo and bar) // eslint-disable-line foo, bar", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), - ruleId: "foo", - type: "disable", - line: 1, - column: 1, - justification: "j1" - }, - { - parentComment: createParentComment([41, 81], " eslint-disable-line foo, bar", ["foo", "bar"]), - ruleId: "foo", - type: "disable-line", - line: 2, - column: 11, - justification: "j2" - }, - { - parentComment: createParentComment([41, 81], " eslint-disable-line foo, bar ", ["foo", "bar"]), - ruleId: "bar", - type: "disable-line", - line: 2, - column: 11, - justification: "j2" - } - ], - problems: [ - { line: 2, column: 1, ruleId: "bar" }, - { line: 2, column: 6, ruleId: "foo" } - ], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: "bar", - line: 2, - column: 1, - suppressions: [{ kind: "directive", justification: "j2" }] - }, - { - ruleId: "foo", - line: 2, - column: 6, - suppressions: [ - { kind: "directive", justification: "j1" }, - { kind: "directive", justification: "j2" } - ] - }, - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'foo').", - line: 2, - column: 11, - fix: { - range: [64, 69], - text: "" - }, - severity: 2, - nodeType: null - } - ] - ); - }); }); }); diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index c8fd4134caa6..b89b073ecaf9 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -4323,6 +4323,166 @@ var a = "test2"; assert.strictEqual(suppressedMessages.length, 0); }); + it("reports problems for unused eslint-enable comments", () => { + const messages = linter.verify("/* eslint-enable */", {}, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual( + messages, + [ + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 1, + column: 1, + fix: { + range: [0, 19], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-enable comments with ruleId", () => { + const messages = linter.verify("/* eslint-enable no-alert */", {}, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual( + messages, + [ + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'no-alert').", + line: 1, + column: 1, + fix: { + range: [0, 28], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-enable comments with mismatch ruleId", () => { + const code = [ + "/* eslint-disable no-alert */", + "alert(\"test\");", + "/* eslint-enable no-console */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual( + messages, + [ + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'no-console').", + line: 3, + column: 1, + fix: { + range: [45, 75], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 1); + }); + + it("reports problems for unused eslint-enable comments with used eslint-enable comments", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + "alert(\"test\");", + "/* eslint-disable no-alert -- j2 */", + "alert(\"test\");", + "/* eslint-enable no-alert -- j3 */", + "/* eslint-enable -- j4 */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual( + messages, + [ + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 6, + column: 1, + fix: { + range: [137, 162], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual(suppressedMessages[1].suppressions.length, 2); + }); + + it("reports problems for unused eslint-disable comments with used eslint-enable comments", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + "console.log(\"test\"); //", + "/* eslint-enable no-alert -- j2 */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual( + messages, + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'no-alert').", + line: 1, + column: 1, + fix: { + range: [0, 35], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + it("reports problems for unused eslint-disable comments (in config)", () => { const messages = linter.verify("/* eslint-disable */", { reportUnusedDisableDirectives: true }); const suppressedMessages = linter.getSuppressedMessages(); @@ -4407,12 +4567,156 @@ var a = "test2"; assert.strictEqual(suppressedMessages[0].ruleId, "no-fallthrough"); }); + it("reports problems for multiple eslint-enable comments with same ruleId", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + "alert(\"test\"); //", + "/* eslint-enable no-alert -- j2 */", + "/* eslint-enable no-alert -- j3 */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 4); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + }); + + it("reports problems for multiple eslint-enable comments without ruleId (Rule is already enabled)", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + "alert(\"test\"); //", + "/* eslint-enable no-alert -- j2 */", + "/* eslint-enable -- j3 */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 4); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + }); + + it("reports problems for multiple eslint-enable comments with ruleId (Rule is already enabled by eslint-enable comments without ruleId)", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + "alert(\"test\"); //", + "/* eslint-enable -- j3 */", + "/* eslint-enable no-alert -- j2 */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 4); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + }); + + it("reports problems for eslint-enable comments without ruleId (Two rules are already enabled)", () => { + const code = [ + "/* eslint-disable no-alert, no-console -- j1 */", + "alert(\"test\"); //", + "console.log(\"test\"); //", + "/* eslint-enable no-alert -- j2 */", + "/* eslint-enable no-console -- j3 */", + "/* eslint-enable -- j4 */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + "no-console": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 6); + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual(suppressedMessages[1].suppressions.length, 1); + }); + + it("reports problems for multiple eslint-enable comments with ruleId (Two rules are already enabled by eslint-enable comments without ruleId)", () => { + const code = [ + "/* eslint-disable no-alert, no-console -- j1 */", + "alert(\"test\"); //", + "console.log(\"test\"); //", + "/* eslint-enable -- j2 */", + "/* eslint-enable no-console -- j3 */", + "/* eslint-enable no-alert -- j4 */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + "no-console": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].line, 5); + assert.strictEqual(messages[1].line, 6); + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual(suppressedMessages[1].suppressions.length, 1); + }); + + it("reports problems for multiple eslint-enable comments", () => { + const code = [ + "/* eslint-disable no-alert, no-console -- j1 */", + "alert(\"test\"); //", + "console.log(\"test\"); //", + "/* eslint-enable no-console -- j2 */", + "/* eslint-enable -- j3 */", + "/* eslint-enable no-alert -- j4 */", + "/* eslint-enable -- j5 */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + "no-console": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].line, 6); + assert.strictEqual(messages[1].line, 7); + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual(suppressedMessages[1].suppressions.length, 1); + }); + describe("autofix", () => { const alwaysReportsRule = { create(context) { return { Program(node) { context.report({ message: "bad code", loc: node.loc.end }); + }, + "Identifier[name=bad]"(node) { + context.report({ message: "bad id", loc: node.loc }); } }; } @@ -4482,6 +4786,10 @@ var a = "test2"; code: "/* eslint-disable \nunused\n*/", output: " " }, + { + code: "/* eslint-enable \nunused\n*/", + output: " " + }, //----------------------------------------------- // Removing only individual rules @@ -4520,6 +4828,26 @@ var a = "test2"; code: "/*\u00A0eslint-disable unused, used*/", output: "/*\u00A0eslint-disable used*/" }, + { + code: "/* eslint-disable used*/ bad /*\neslint-enable unused, used*/", + output: "/* eslint-disable used*/ bad /*\neslint-enable used*/" + }, + { + code: "/* eslint-disable used*/ bad /*\n eslint-enable unused, used*/", + output: "/* eslint-disable used*/ bad /*\n eslint-enable used*/" + }, + { + code: "/* eslint-disable used*/ bad /*\r\neslint-enable unused, used*/", + output: "/* eslint-disable used*/ bad /*\r\neslint-enable used*/" + }, + { + code: "/* eslint-disable used*/ bad /*\u2028eslint-enable unused, used*/", + output: "/* eslint-disable used*/ bad /*\u2028eslint-enable used*/" + }, + { + code: "/* eslint-disable used*/ bad /*\u00A0eslint-enable unused, used*/", + output: "/* eslint-disable used*/ bad /*\u00A0eslint-enable used*/" + }, { code: "// eslint-disable-line unused, used", output: "// eslint-disable-line used" @@ -4544,6 +4872,26 @@ var a = "test2"; code: "/* eslint-disable\u00A0unused, used*/", output: "/* eslint-disable\u00A0used*/" }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable\nunused, used*/", + output: "/* eslint-disable used*/ bad /* eslint-enable\nused*/" + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable\n unused, used*/", + output: "/* eslint-disable used*/ bad /* eslint-enable\n used*/" + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable\r\nunused, used*/", + output: "/* eslint-disable used*/ bad /* eslint-enable\r\nused*/" + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable\u2028unused, used*/", + output: "/* eslint-disable used*/ bad /* eslint-enable\u2028used*/" + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable\u00A0unused, used*/", + output: "/* eslint-disable used*/ bad /* eslint-enable\u00A0used*/" + }, // when removing the first rule, the comma and all whitespace up to the next rule (or next lone comma) should also be removed { @@ -4578,6 +4926,18 @@ var a = "test2"; code: "/* eslint-disable unused\u2028,\u2028used */", output: "/* eslint-disable used */" }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable unused\n,\nused */", + output: "/* eslint-disable used*/ bad /* eslint-enable used */" + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable unused \n \n,\n\n used */", + output: "/* eslint-disable used*/ bad /* eslint-enable used */" + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable unused\u2028,\u2028used */", + output: "/* eslint-disable used*/ bad /* eslint-enable used */" + }, { code: "// eslint-disable-line unused\u00A0,\u00A0used", output: "// eslint-disable-line used" @@ -4632,6 +4992,18 @@ var a = "test2"; code: "/* eslint-disable used-1,\u2028unused\u2028,used-2 */", output: "/* eslint-disable used-1,used-2 */" }, + { + code: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,\nunused\n,used-2 */", + output: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,used-2 */" + }, + { + code: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,\n\n unused \n \n ,used-2 */", + output: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,used-2 */" + }, + { + code: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,\u2028unused\u2028,used-2 */", + output: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,used-2 */" + }, { code: "// eslint-disable-line used-1,\u00A0unused\u00A0,used-2", output: "// eslint-disable-line used-1,used-2" @@ -4666,6 +5038,14 @@ var a = "test2"; code: "/* eslint-disable used-1\u2028,unused,\u2028used-2 */", output: "/* eslint-disable used-1\u2028,\u2028used-2 */" }, + { + code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1\n,unused,\nused-2 */", + output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1\n,\nused-2 */" + }, + { + code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1\u2028,unused,\u2028used-2 */", + output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1\u2028,\u2028used-2 */" + }, { code: "// eslint-disable-line used-1\u00A0,unused,\u00A0used-2", output: "// eslint-disable-line used-1\u00A0,\u00A0used-2" @@ -4690,6 +5070,18 @@ var a = "test2"; code: "/* eslint-disable used-1,\n,unused,\n,used-2 */", output: "/* eslint-disable used-1,\n,\n,used-2 */" }, + { + code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,unused,used-2 */", + output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,used-2 */" + }, + { + code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,unused,\n,used-2 */", + output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,used-2 */" + }, + { + code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,unused,\n,used-2 */", + output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,\n,used-2 */" + }, { code: "// eslint-disable-line used, unused,", output: "// eslint-disable-line used," @@ -4710,6 +5102,10 @@ var a = "test2"; code: "/* eslint-disable used, unused,\n*/", output: "/* eslint-disable used,\n*/" }, + { + code: "/* eslint-disable used */ bad /* eslint-enable used, unused,\n*/", + output: "/* eslint-disable used */ bad /* eslint-enable used,\n*/" + }, // when removing the last rule, the comma and all whitespace up to the previous rule (or previous lone comma) should also be removed { @@ -4748,6 +5144,18 @@ var a = "test2"; code: "/* eslint-disable used\u2028,\u2028unused */", output: "/* eslint-disable used */" }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable used\n,\nunused */", + output: "/* eslint-disable used*/ bad /* eslint-enable used */" + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable used \n \n,\n\n unused */", + output: "/* eslint-disable used*/ bad /* eslint-enable used */" + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable used\u2028,\u2028unused */", + output: "/* eslint-disable used*/ bad /* eslint-enable used */" + }, { code: "// eslint-disable-line used\u00A0,\u00A0unused", output: "// eslint-disable-line used" @@ -4768,6 +5176,14 @@ var a = "test2"; code: "/* eslint-disable used\n, ,unused */", output: "/* eslint-disable used\n, */" }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable used,\n,unused */", + output: "/* eslint-disable used*/ bad /* eslint-enable used, */" + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable used\n, ,unused */", + output: "/* eslint-disable used*/ bad /* eslint-enable used\n, */" + }, // content after the last rule should not be changed { @@ -4798,6 +5214,10 @@ var a = "test2"; code: "/* eslint-disable used,unused\u2028*/", output: "/* eslint-disable used\u2028*/" }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable used,unused\u2028*/", + output: "/* eslint-disable used*/ bad /* eslint-enable used\u2028*/" + }, { code: "// eslint-disable-line used,unused\u00A0", output: "// eslint-disable-line used\u00A0" @@ -4813,34 +5233,172 @@ var a = "test2"; output: "// eslint-disable-line used" }, { - code: "// eslint-disable-line unused-1, unused-2, used", - output: "// eslint-disable-line used" + code: "// eslint-disable-line unused-1, unused-2, used", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line used-1, unused-1, used-2, unused-2", + output: "// eslint-disable-line used-1, used-2" + }, + { + code: "// eslint-disable-line unused-1, used-1, unused-2, used-2", + output: "// eslint-disable-line used-1, used-2" + }, + { + code: ` + /* eslint-disable unused-1, + used-1, + unused-2, + used-2 + */ + `, + output: ` + /* eslint-disable used-1, + used-2 + */ + ` + }, + { + code: ` + /* eslint-disable + unused-1, + used-1, + unused-2, + used-2 + */ + `, + output: ` + /* eslint-disable + used-1, + used-2 + */ + ` + }, + { + code: ` + /* eslint-disable + used-1, + unused-1, + used-2, + unused-2 + */ + `, + output: ` + /* eslint-disable + used-1, + used-2 + */ + ` + }, + { + code: ` + /* eslint-disable + used-1, + unused-1, + used-2, + unused-2, + */ + `, + output: ` + /* eslint-disable + used-1, + used-2, + */ + ` + }, + { + code: ` + /* eslint-disable + ,unused-1 + ,used-1 + ,unused-2 + ,used-2 + */ + `, + output: ` + /* eslint-disable + ,used-1 + ,used-2 + */ + ` + }, + { + code: ` + /* eslint-disable + ,used-1 + ,unused-1 + ,used-2 + ,unused-2 + */ + `, + output: ` + /* eslint-disable + ,used-1 + ,used-2 + */ + ` }, { - code: "// eslint-disable-line used-1, unused-1, used-2, unused-2", - output: "// eslint-disable-line used-1, used-2" + code: ` + /* eslint-disable + used-1, + unused-1, + used-2, + unused-2 + + -- comment + */ + `, + output: ` + /* eslint-disable + used-1, + used-2 + + -- comment + */ + ` }, { - code: "// eslint-disable-line unused-1, used-1, unused-2, used-2", - output: "// eslint-disable-line used-1, used-2" + code: ` + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable unused-1, + used-1, + unused-2, + used-2 + */ + `, + output: ` + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable used-1, + used-2 + */ + ` }, { code: ` - /* eslint-disable unused-1, + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable unused-1, used-1, unused-2, used-2 */ `, output: ` - /* eslint-disable used-1, + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable used-1, used-2 */ ` }, { code: ` - /* eslint-disable + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable unused-1, used-1, unused-2, @@ -4848,7 +5406,9 @@ var a = "test2"; */ `, output: ` - /* eslint-disable + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable used-1, used-2 */ @@ -4856,7 +5416,9 @@ var a = "test2"; }, { code: ` - /* eslint-disable + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable used-1, unused-1, used-2, @@ -4864,7 +5426,9 @@ var a = "test2"; */ `, output: ` - /* eslint-disable + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable used-1, used-2 */ @@ -4872,7 +5436,9 @@ var a = "test2"; }, { code: ` - /* eslint-disable + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable used-1, unused-1, used-2, @@ -4880,7 +5446,9 @@ var a = "test2"; */ `, output: ` - /* eslint-disable + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable used-1, used-2, */ @@ -4888,7 +5456,9 @@ var a = "test2"; }, { code: ` - /* eslint-disable + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable ,unused-1 ,used-1 ,unused-2 @@ -4896,7 +5466,9 @@ var a = "test2"; */ `, output: ` - /* eslint-disable + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable ,used-1 ,used-2 */ @@ -4904,7 +5476,9 @@ var a = "test2"; }, { code: ` - /* eslint-disable + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable ,used-1 ,unused-1 ,used-2 @@ -4912,7 +5486,9 @@ var a = "test2"; */ `, output: ` - /* eslint-disable + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable ,used-1 ,used-2 */ @@ -4920,7 +5496,9 @@ var a = "test2"; }, { code: ` - /* eslint-disable + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable used-1, unused-1, used-2, @@ -4930,7 +5508,9 @@ var a = "test2"; */ `, output: ` - /* eslint-disable + /* eslint-disable used-1, used-2*/ + bad + /* eslint-enable used-1, used-2 @@ -15015,82 +15595,373 @@ var a = "test2"; ); }); - it("reports problems for partially unused eslint-disable-next-line comments (in config)", () => { - const code = "// eslint-disable-next-line no-alert, no-redeclare \nalert('test');"; + it("reports problems for partially unused eslint-disable-next-line comments (in config)", () => { + const code = "// eslint-disable-next-line no-alert, no-redeclare \nalert('test');"; + const config = { + linterOptions: { + reportUnusedDisableDirectives: true + }, + rules: { + "no-alert": 1, + "no-redeclare": 1 + } + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true + }); + + assert.deepStrictEqual( + messages, + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'no-redeclare').", + line: 1, + column: 1, + fix: { + range: [36, 50], + text: "" + }, + severity: 1, + nodeType: null + } + ] + ); + }); + + it("reports problems for partially unused multiline eslint-disable-next-line comments (in config)", () => { + const code = ` + /* eslint-disable-next-line no-alert, no-redeclare -- + * Here's a very long description about why this configuration is necessary + * along with some additional information + **/ + alert('test'); + `; + const config = { + linterOptions: { + reportUnusedDisableDirectives: true + }, + rules: { + "no-alert": 1, + "no-redeclare": 1 + } + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true + }); + + assert.deepStrictEqual( + messages, + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'no-redeclare').", + line: 2, + column: 21, + fix: { + range: [57, 71], + text: "" + }, + severity: 1, + nodeType: null + } + ] + ); + }); + + it("reports problems for unused eslint-enable comments", () => { + const messages = linter.verify("/* eslint-enable */", {}, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual( + messages, + [ + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 1, + column: 1, + fix: { + range: [0, 19], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-enable comments with ruleId", () => { + const messages = linter.verify("/* eslint-enable no-alert */", {}, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual( + messages, + [ + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'no-alert').", + line: 1, + column: 1, + fix: { + range: [0, 28], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-enable comments with mismatch ruleId", () => { + const code = [ + "/* eslint-disable no-alert */", + "alert(\"test\");", + "/* eslint-enable no-console */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual( + messages, + [ + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'no-console').", + line: 3, + column: 1, + fix: { + range: [45, 75], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 1); + }); + + it("reports problems for unused eslint-enable comments with used eslint-enable comments", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + "alert(\"test\");", + "/* eslint-disable no-alert -- j2 */", + "alert(\"test\");", + "/* eslint-enable no-alert -- j3 */", + "/* eslint-enable -- j4 */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual( + messages, + [ + { + ruleId: null, + message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 6, + column: 1, + fix: { + range: [137, 162], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual(suppressedMessages[1].suppressions.length, 2); + }); + + it("reports problems for multiple eslint-enable comments with same ruleId", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + "alert(\"test\"); //", + "/* eslint-enable no-alert -- j2 */", + "/* eslint-enable no-alert -- j3 */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 4); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + }); + + it("reports problems for multiple eslint-enable comments without ruleId (Rule is already enabled)", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + "alert(\"test\"); //", + "/* eslint-enable no-alert -- j2 */", + "/* eslint-enable -- j3 */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 4); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + }); + + it("reports problems for multiple eslint-enable comments with ruleId (Rule is already enabled by eslint-enable comments without ruleId)", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + "alert(\"test\"); //", + "/* eslint-enable -- j3 */", + "/* eslint-enable no-alert -- j2 */" + ].join("\n"); const config = { - linterOptions: { - reportUnusedDisableDirectives: true - }, rules: { - "no-alert": 1, - "no-redeclare": 1 + "no-alert": 2 } }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: true - }); + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 4); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + }); - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'no-redeclare').", - line: 1, - column: 1, - fix: { - range: [36, 50], - text: "" - }, - severity: 1, - nodeType: null - } - ] - ); + it("reports problems for eslint-enable comments without ruleId (Two rules are already enabled)", () => { + const code = [ + "/* eslint-disable no-alert, no-console -- j1 */", + "alert(\"test\"); //", + "console.log(\"test\"); //", + "/* eslint-enable no-alert -- j2 */", + "/* eslint-enable no-console -- j3 */", + "/* eslint-enable -- j4 */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + "no-console": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 6); + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual(suppressedMessages[1].suppressions.length, 1); }); - it("reports problems for partially unused multiline eslint-disable-next-line comments (in config)", () => { - const code = ` - /* eslint-disable-next-line no-alert, no-redeclare -- - * Here's a very long description about why this configuration is necessary - * along with some additional information - **/ - alert('test'); - `; + it("reports problems for multiple eslint-enable comments with ruleId (Two rules are already enabled by eslint-enable comments without ruleId)", () => { + const code = [ + "/* eslint-disable no-alert, no-console -- j1 */", + "alert(\"test\"); //", + "console.log(\"test\"); //", + "/* eslint-enable -- j2 */", + "/* eslint-enable no-console -- j3 */", + "/* eslint-enable no-alert -- j4 */" + ].join("\n"); const config = { - linterOptions: { - reportUnusedDisableDirectives: true - }, rules: { - "no-alert": 1, - "no-redeclare": 1 + "no-alert": 2, + "no-console": 2 } }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: true - }); + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].line, 5); + assert.strictEqual(messages[1].line, 6); + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual(suppressedMessages[1].suppressions.length, 1); + }); + + it("reports problems for multiple eslint-enable comments", () => { + const code = [ + "/* eslint-disable no-alert, no-console -- j1 */", + "alert(\"test\"); //", + "console.log(\"test\"); //", + "/* eslint-enable no-console -- j2 */", + "/* eslint-enable -- j3 */", + "/* eslint-enable no-alert -- j4 */", + "/* eslint-enable -- j5 */" + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + "no-console": 2 + } + }; + const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].line, 6); + assert.strictEqual(messages[1].line, 7); + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual(suppressedMessages[1].suppressions.length, 1); + }); + + it("reports problems for unused eslint-disable comments (warn)", () => { + const messages = linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: "warn" }); + const suppressedMessages = linter.getSuppressedMessages(); assert.deepStrictEqual( messages, [ { ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'no-redeclare').", - line: 2, - column: 21, + message: "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, fix: { - range: [57, 71], - text: "" + range: [0, 20], + text: " " }, severity: 1, nodeType: null } ] ); + + assert.strictEqual(suppressedMessages.length, 0); }); describe("autofix", () => { @@ -15099,6 +15970,9 @@ var a = "test2"; return { Program(node) { context.report({ message: "bad code", loc: node.loc.end }); + }, + "Identifier[name=bad]"(node) { + context.report({ message: "bad id", loc: node.loc }); } }; } @@ -15177,6 +16051,10 @@ var a = "test2"; code: "/* eslint-disable \ntest/unused\n*/", output: " " }, + { + code: "/* eslint-enable \ntest/unused\n*/", + output: " " + }, //----------------------------------------------- // Removing only individual rules @@ -15215,6 +16093,26 @@ var a = "test2"; code: "/*\u00A0eslint-disable test/unused, test/used*/", output: "/*\u00A0eslint-disable test/used*/" }, + { + code: "/* eslint-disable test/used */ bad /*\neslint-enable test/unused, test/used*/", + output: "/* eslint-disable test/used */ bad /*\neslint-enable test/used*/" + }, + { + code: "/* eslint-disable test/used */ bad /*\n eslint-enable test/unused, test/used*/", + output: "/* eslint-disable test/used */ bad /*\n eslint-enable test/used*/" + }, + { + code: "/* eslint-disable test/used */ bad /*\r\neslint-enable test/unused, test/used*/", + output: "/* eslint-disable test/used */ bad /*\r\neslint-enable test/used*/" + }, + { + code: "/* eslint-disable test/used */ bad /*\u2028eslint-enable test/unused, test/used*/", + output: "/* eslint-disable test/used */ bad /*\u2028eslint-enable test/used*/" + }, + { + code: "/* eslint-disable test/used */ bad /*\u00A0eslint-enable test/unused, test/used*/", + output: "/* eslint-disable test/used */ bad /*\u00A0eslint-enable test/used*/" + }, { code: "// eslint-disable-line test/unused, test/used", output: "// eslint-disable-line test/used" @@ -15273,6 +16171,18 @@ var a = "test2"; code: "/* eslint-disable test/unused\u2028,\u2028test/used */", output: "/* eslint-disable test/used */" }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/unused\n,\ntest/used */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */" + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/unused \n \n,\n\n test/used */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */" + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/unused\u2028,\u2028test/used */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */" + }, { code: "// eslint-disable-line test/unused\u00A0,\u00A0test/used", output: "// eslint-disable-line test/used" @@ -15327,6 +16237,18 @@ var a = "test2"; code: "/* eslint-disable test/used-1,\u2028test/unused\u2028,test/used-2 */", output: "/* eslint-disable test/used-1,test/used-2 */" }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\ntest/unused\n,test/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,test/used-2 */" + }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n\n test/unused \n \n ,test/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,test/used-2 */" + }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\u2028test/unused\u2028,test/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,test/used-2 */" + }, { code: "// eslint-disable-line test/used-1,\u00A0test/unused\u00A0,test/used-2", output: "// eslint-disable-line test/used-1,test/used-2" @@ -15361,6 +16283,14 @@ var a = "test2"; code: "/* eslint-disable test/used-1\u2028,test/unused,\u2028test/used-2 */", output: "/* eslint-disable test/used-1\u2028,\u2028test/used-2 */" }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1\n,test/unused,\ntest/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1\n,\ntest/used-2 */" + }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1\u2028,test/unused,\u2028test/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1\u2028,\u2028test/used-2 */" + }, { code: "// eslint-disable-line test/used-1\u00A0,test/unused,\u00A0test/used-2", output: "// eslint-disable-line test/used-1\u00A0,\u00A0test/used-2" @@ -15385,6 +16315,18 @@ var a = "test2"; code: "/* eslint-disable test/used-1,\n,test/unused,\n,test/used-2 */", output: "/* eslint-disable test/used-1,\n,\n,test/used-2 */" }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,test/unused,test/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,test/used-2 */" + }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,test/unused,\n,test/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,test/used-2 */" + }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,test/unused,\n,test/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,\n,test/used-2 */" + }, { code: "// eslint-disable-line test/used, test/unused,", output: "// eslint-disable-line test/used," @@ -15405,6 +16347,10 @@ var a = "test2"; code: "/* eslint-disable test/used, test/unused,\n*/", output: "/* eslint-disable test/used,\n*/" }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/used, test/unused,\n*/", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used,\n*/" + }, // when removing the last rule, the comma and all whitespace up to the previous rule (or previous lone comma) should also be removed { @@ -15443,6 +16389,18 @@ var a = "test2"; code: "/* eslint-disable test/used\u2028,\u2028test/unused */", output: "/* eslint-disable test/used */" }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/used\n,\ntest/unused */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */" + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/used \n \n,\n\n test/unused */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */" + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/used\u2028,\u2028test/unused */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */" + }, { code: "// eslint-disable-line test/used\u00A0,\u00A0test/unused", output: "// eslint-disable-line test/used" @@ -15463,6 +16421,14 @@ var a = "test2"; code: "/* eslint-disable test/used\n, ,test/unused */", output: "/* eslint-disable test/used\n, */" }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/used,\n,test/unused */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used, */" + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/used\n, ,test/unused */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used\n, */" + }, // content after the last rule should not be changed { @@ -15493,6 +16459,10 @@ var a = "test2"; code: "/* eslint-disable test/used,test/unused\u2028*/", output: "/* eslint-disable test/used\u2028*/" }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/used,test/unused\u2028*/", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used\u2028*/" + }, { code: "// eslint-disable-line test/used,test/unused\u00A0", output: "// eslint-disable-line test/used\u00A0" @@ -15633,6 +16603,148 @@ var a = "test2"; */ ` }, + { + code: ` + /* eslint-disable test/used-1, test/used-2 */ + bad + /* eslint-enable test/unused-1, + test/used-1, + test/unused-2, + test/used-2 + */ + `, + output: ` + /* eslint-disable test/used-1, test/used-2 */ + bad + /* eslint-enable test/used-1, + test/used-2 + */ + ` + }, + { + code: ` + /* eslint-disable test/used-1, test/used-2 */ + bad + /* eslint-enable + test/unused-1, + test/used-1, + test/unused-2, + test/used-2 + */ + `, + output: ` + /* eslint-disable test/used-1, test/used-2 */ + bad + /* eslint-enable + test/used-1, + test/used-2 + */ + ` + }, + { + code: ` + /* eslint-disable test/used-1, test/used-2 */ + bad + /* eslint-enable + test/used-1, + test/unused-1, + test/used-2, + test/unused-2 + */ + `, + output: ` + /* eslint-disable test/used-1, test/used-2 */ + bad + /* eslint-enable + test/used-1, + test/used-2 + */ + ` + }, + { + code: ` + /* eslint-disable test/used-1, test/used-2 */ + bad + /* eslint-enable + test/used-1, + test/unused-1, + test/used-2, + test/unused-2, + */ + `, + output: ` + /* eslint-disable test/used-1, test/used-2 */ + bad + /* eslint-enable + test/used-1, + test/used-2, + */ + ` + }, + { + code: ` + /* eslint-disable test/used-1, test/used-2 */ + bad + /* eslint-enable + ,test/unused-1 + ,test/used-1 + ,test/unused-2 + ,test/used-2 + */ + `, + output: ` + /* eslint-disable test/used-1, test/used-2 */ + bad + /* eslint-enable + ,test/used-1 + ,test/used-2 + */ + ` + }, + { + code: ` + /* eslint-disable test/used-1, test/used-2 */ + bad + /* eslint-enable + ,test/used-1 + ,test/unused-1 + ,test/used-2 + ,test/unused-2 + */ + `, + output: ` + /* eslint-disable test/used-1, test/used-2 */ + bad + /* eslint-enable + ,test/used-1 + ,test/used-2 + */ + ` + }, + { + code: ` + /* eslint-disable test/used-1, test/used-2 */ + bad + /* eslint-enable + test/used-1, + test/unused-1, + test/used-2, + test/unused-2 + + -- comment + */ + `, + output: ` + /* eslint-disable test/used-1, test/used-2 */ + bad + /* eslint-enable + test/used-1, + test/used-2 + + -- comment + */ + ` + }, // duplicates in the list { From ef650cb612510bcfa1379c1f0af56dd563b3a705 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 18 Oct 2023 14:04:11 +0200 Subject: [PATCH 223/248] test: update tests for no-promise-executor-return (#17661) --- tests/lib/rules/no-promise-executor-return.js | 694 ++++++++++++++---- 1 file changed, 531 insertions(+), 163 deletions(-) diff --git a/tests/lib/rules/no-promise-executor-return.js b/tests/lib/rules/no-promise-executor-return.js index 4054edc72441..ee8a53929fbb 100644 --- a/tests/lib/rules/no-promise-executor-return.js +++ b/tests/lib/rules/no-promise-executor-return.js @@ -12,49 +12,6 @@ const rule = require("../../../lib/rules/no-promise-executor-return"); const { RuleTester } = require("../../../lib/rule-tester"); -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -/** - * Creates an error object. - * @param {number} [column] Reported column. - * @param {string} [type="ReturnStatement"] Reported node type. - * @returns {Object} The error object. - */ -function eReturnsValue(column, type = "ReturnStatement") { - const errorObject = { - messageId: "returnsValue", - type - }; - - if (column) { - errorObject.column = column; - } - - return errorObject; -} - - -/** - * Creates invalid object - * @param {Object} [properties] suggestion properties - * @param {string} [properties.code] code - * @param {number} [properties.options] rule options - * @param {string[]} [fixes] Code suggestions - * @returns {Object} The invalid object. - */ -function suggestion(properties, fixes = []) { - return { - ...properties, - errors: [{ - suggestions: fixes.map(fix => ({ - output: fix - })) - }] - }; -} - //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ @@ -207,7 +164,13 @@ ruleTester.run("no-promise-executor-return", rule, { // full error tests { code: "new Promise(function (resolve, reject) { return 1; })", - errors: [{ message: "Return values from promise executor functions cannot be read.", type: "ReturnStatement", column: 42, endColumn: 51 }] + errors: [{ + message: "Return values from promise executor functions cannot be read.", + type: "ReturnStatement", + column: 42, + endColumn: 51, + suggestions: null + }] }, { code: "new Promise((resolve, reject) => resolve(1))", @@ -220,8 +183,14 @@ ruleTester.run("no-promise-executor-return", rule, { column: 34, endColumn: 44, suggestions: [ - { output: "new Promise((resolve, reject) => void resolve(1))" }, - { output: "new Promise((resolve, reject) => {resolve(1)})" } + { + messageId: "prependVoid", + output: "new Promise((resolve, reject) => void resolve(1))" + }, + { + messageId: "wrapBraces", + output: "new Promise((resolve, reject) => {resolve(1)})" + } ] }] }, @@ -236,351 +205,750 @@ ruleTester.run("no-promise-executor-return", rule, { column: 36, endColumn: 44, suggestions: [ - { output: "new Promise((resolve, reject) => { return void 1 })" } + { + messageId: "prependVoid", + output: "new Promise((resolve, reject) => { return void 1 })" + } ] }] }, // suggestions arrow function expression - suggestion({ + { code: "new Promise(r => 1)", options: [{ allowVoid: true + }], + errors: [{ + messageId: "returnsValue", + type: "Literal", + suggestions: [ + { + messageId: "prependVoid", + output: "new Promise(r => void 1)" + }, + { + messageId: "wrapBraces", + output: "new Promise(r => {1})" + } + ] }] - }, [ - "new Promise(r => void 1)", - "new Promise(r => {1})" - ]), - suggestion({ + }, + { code: "new Promise(r => 1 ? 2 : 3)", options: [{ allowVoid: true + }], + errors: [{ + messageId: "returnsValue", + type: "ConditionalExpression", + suggestions: [ + { + messageId: "prependVoid", + output: "new Promise(r => void (1 ? 2 : 3))" + }, + { + messageId: "wrapBraces", + output: "new Promise(r => {1 ? 2 : 3})" + } + ] }] - }, [ - "new Promise(r => void (1 ? 2 : 3))", - "new Promise(r => {1 ? 2 : 3})" - ]), - suggestion({ + }, + { code: "new Promise(r => (1 ? 2 : 3))", options: [{ allowVoid: true + }], + errors: [{ + messageId: "returnsValue", + type: "ConditionalExpression", + suggestions: [ + { + messageId: "prependVoid", + output: "new Promise(r => void (1 ? 2 : 3))" + }, + { + messageId: "wrapBraces", + output: "new Promise(r => {(1 ? 2 : 3)})" + } + ] }] - }, [ - "new Promise(r => void (1 ? 2 : 3))", - "new Promise(r => {(1 ? 2 : 3)})" - ]), - suggestion({ + }, + { code: "new Promise(r => (1))", options: [{ allowVoid: true + }], + errors: [{ + messageId: "returnsValue", + type: "Literal", + suggestions: [ + { + messageId: "prependVoid", + output: "new Promise(r => void (1))" + }, + { + messageId: "wrapBraces", + output: "new Promise(r => {(1)})" + } + ] }] - }, [ - "new Promise(r => void (1))", - "new Promise(r => {(1)})" - ]), - suggestion({ + }, + { code: "new Promise(r => () => {})", options: [{ allowVoid: true + }], + errors: [{ + messageId: "returnsValue", + type: "ArrowFunctionExpression", + suggestions: [ + { + messageId: "prependVoid", + output: "new Promise(r => void (() => {}))" + }, + { + messageId: "wrapBraces", + output: "new Promise(r => {() => {}})" + } + ] }] - }, [ - "new Promise(r => void (() => {}))", - "new Promise(r => {() => {}})" - ]), + }, // primitives - suggestion({ + { code: "new Promise(r => null)", options: [{ allowVoid: true + }], + errors: [{ + messageId: "returnsValue", + type: "Literal", + suggestions: [ + { + messageId: "prependVoid", + output: "new Promise(r => void null)" + }, + { + messageId: "wrapBraces", + output: "new Promise(r => {null})" + } + ] }] - }, [ - "new Promise(r => void null)", - "new Promise(r => {null})" - ]), - suggestion({ + }, + { code: "new Promise(r => null)", options: [{ allowVoid: false + }], + errors: [{ + messageId: "returnsValue", + type: "Literal", + suggestions: [ + { + messageId: "wrapBraces", + output: "new Promise(r => {null})" + } + ] }] - }, [ - "new Promise(r => {null})" - ]), + }, // inline comments - suggestion({ + { code: "new Promise(r => /*hi*/ ~0)", options: [{ allowVoid: true + }], + errors: [{ + messageId: "returnsValue", + type: "UnaryExpression", + suggestions: [ + { + messageId: "prependVoid", + output: "new Promise(r => /*hi*/ void ~0)" + }, + { + messageId: "wrapBraces", + output: "new Promise(r => /*hi*/ {~0})" + } + ] }] - }, [ - "new Promise(r => /*hi*/ void ~0)", - "new Promise(r => /*hi*/ {~0})" - ]), - suggestion({ + }, + { code: "new Promise(r => /*hi*/ ~0)", options: [{ allowVoid: false + }], + errors: [{ + messageId: "returnsValue", + type: "UnaryExpression", + suggestions: [ + { + messageId: "wrapBraces", + output: "new Promise(r => /*hi*/ {~0})" + } + ] }] - }, [ - "new Promise(r => /*hi*/ {~0})" - ]), + }, // suggestions function - suggestion({ + { code: "new Promise(r => { return 0 })", options: [{ allowVoid: true + }], + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: [ + { + messageId: "prependVoid", + output: "new Promise(r => { return void 0 })" + } + ] }] - }, [ - "new Promise(r => { return void 0 })" - ]), - suggestion({ - code: - "new Promise(r => { return 0 })", + }, + { + code: "new Promise(r => { return 0 })", options: [{ allowVoid: false + }], + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null }] - }), + }, // multiple returns - suggestion({ + { code: "new Promise(r => { if (foo) { return void 0 } return 0 })", options: [{ allowVoid: true + }], + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: [ + { + messageId: "prependVoid", + output: "new Promise(r => { if (foo) { return void 0 } return void 0 })" + } + ] }] - }, [ - "new Promise(r => { if (foo) { return void 0 } return void 0 })" - ]), + }, // return assignment - suggestion({ + { code: "new Promise(resolve => { return (foo = resolve(1)); })", options: [{ allowVoid: true + }], + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: [ + { + messageId: "prependVoid", + output: "new Promise(resolve => { return void (foo = resolve(1)); })" + } + ] }] - }, [ - "new Promise(resolve => { return void (foo = resolve(1)); })" - ]), - suggestion({ + }, + { code: "new Promise(resolve => r = resolve)", options: [{ allowVoid: true + }], + errors: [{ + messageId: "returnsValue", + type: "AssignmentExpression", + suggestions: [ + { + messageId: "prependVoid", + output: "new Promise(resolve => void (r = resolve))" + }, + { + messageId: "wrapBraces", + output: "new Promise(resolve => {r = resolve})" + } + ] }] - }, [ - "new Promise(resolve => void (r = resolve))", - "new Promise(resolve => {r = resolve})" - ]), + }, // return (range check) - suggestion({ + { code: "new Promise(r => { return(1) })", options: [{ allowVoid: true + }], + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: [ + { + messageId: "prependVoid", + output: "new Promise(r => { return void (1) })" + } + ] }] - }, [ - "new Promise(r => { return void (1) })" - ]), - suggestion({ + }, + { code: "new Promise(r =>1)", options: [{ allowVoid: true + }], + errors: [{ + messageId: "returnsValue", + type: "Literal", + suggestions: [ + { + messageId: "prependVoid", + output: "new Promise(r =>void 1)" + }, + { + messageId: "wrapBraces", + output: "new Promise(r =>{1})" + } + ] }] - }, [ - "new Promise(r =>void 1)", - "new Promise(r =>{1})" - ]), + }, // snapshot - suggestion({ + { code: "new Promise(r => ((1)))", options: [{ allowVoid: true + }], + errors: [{ + messageId: "returnsValue", + type: "Literal", + suggestions: [ + { + messageId: "prependVoid", + output: "new Promise(r => void ((1)))" + }, + { + messageId: "wrapBraces", + output: "new Promise(r => {((1))})" + } + ] }] - }, [ - "new Promise(r => void ((1)))", - "new Promise(r => {((1))})" - ]), + }, // other basic tests { code: "new Promise(function foo(resolve, reject) { return 1; })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, { code: "new Promise((resolve, reject) => { return 1; })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, // any returned value { code: "new Promise(function (resolve, reject) { return undefined; })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, { code: "new Promise((resolve, reject) => { return null; })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, { code: "new Promise(function (resolve, reject) { return false; })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, { code: "new Promise((resolve, reject) => resolve)", - errors: [eReturnsValue(34, "Identifier")] + errors: [{ + messageId: "returnsValue", + type: "Identifier", + column: 34, + suggestions: [ + { + messageId: "wrapBraces", + output: "new Promise((resolve, reject) => {resolve})" + } + ] + }] }, { code: "new Promise((resolve, reject) => null)", - errors: [eReturnsValue(34, "Literal")] + errors: [{ + messageId: "returnsValue", + type: "Literal", + column: 34, + suggestions: [ + { + messageId: "wrapBraces", + output: "new Promise((resolve, reject) => {null})" + } + ] + }] }, { code: "new Promise(function (resolve, reject) { return resolve(foo); })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, { code: "new Promise((resolve, reject) => { return reject(foo); })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, { code: "new Promise((resolve, reject) => x + y)", - errors: [eReturnsValue(34, "BinaryExpression")] + errors: [{ + messageId: "returnsValue", + type: "BinaryExpression", + column: 34, + suggestions: [ + { + messageId: "wrapBraces", + output: "new Promise((resolve, reject) => {x + y})" + } + ] + }] }, { code: "new Promise((resolve, reject) => { return Promise.resolve(42); })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, // any return statement location { code: "new Promise(function (resolve, reject) { if (foo) { return 1; } })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, { code: "new Promise((resolve, reject) => { try { return 1; } catch(e) {} })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, { code: "new Promise(function (resolve, reject) { while (foo){ if (bar) break; else return 1; } })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, // `return void` is not allowed without `allowVoid: true` { code: "new Promise(() => { return void 1; })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, { code: "new Promise(() => (1))", - errors: [eReturnsValue(20, "Literal")] + errors: [{ + messageId: "returnsValue", + type: "Literal", + column: 20, + suggestions: [ + { + messageId: "wrapBraces", + output: "new Promise(() => {(1)})" + } + ] + }] }, { code: "() => new Promise(() => ({}));", - errors: [eReturnsValue(26, "ObjectExpression")] + errors: [{ + messageId: "returnsValue", + type: "ObjectExpression", + column: 26, + suggestions: [ + { + messageId: "wrapBraces", + output: "() => new Promise(() => {({})});" + } + ] + }] }, // absence of arguments has no effect { code: "new Promise(function () { return 1; })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, { code: "new Promise(() => { return 1; })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, { code: "new Promise(() => 1)", - errors: [eReturnsValue(19, "Literal")] + errors: [{ + messageId: "returnsValue", + type: "Literal", + column: 19, + suggestions: [ + { + messageId: "wrapBraces", + output: "new Promise(() => {1})" + } + ] + }] }, // various scope tracking tests { code: "function foo() {} new Promise(function () { return 1; });", - errors: [eReturnsValue(45)] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + column: 45, + suggestions: null + }] }, { code: "function foo() { return; } new Promise(() => { return 1; });", - errors: [eReturnsValue(48)] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + column: 48, + suggestions: null + }] }, { code: "function foo() { return 1; } new Promise(() => { return 2; });", - errors: [eReturnsValue(50)] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + column: 50, + suggestions: null + }] }, { code: "function foo () { return new Promise(function () { return 1; }); }", - errors: [eReturnsValue(52)] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + column: 52, + suggestions: null + }] }, { code: "function foo() { return new Promise(() => { bar(() => { return 1; }); return false; }); }", - errors: [eReturnsValue(71)] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + column: 71, + suggestions: null + }] }, { code: "() => new Promise(() => { if (foo) { return 0; } else bar(() => { return 1; }); })", - errors: [eReturnsValue(38)] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + column: 38, + suggestions: null + }] }, { code: "function foo () { return 1; return new Promise(function () { return 2; }); return 3;}", - errors: [eReturnsValue(62)] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + column: 62, + suggestions: null + }] }, { code: "() => 1; new Promise(() => { return 1; })", - errors: [eReturnsValue(30)] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + column: 30, + suggestions: null + }] }, { code: "new Promise(function () { return 1; }); function foo() { return 1; } ", - errors: [eReturnsValue(27)] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + column: 27, + suggestions: null + }] }, { code: "() => new Promise(() => { return 1; });", - errors: [eReturnsValue(27)] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + column: 27, + suggestions: null + }] }, { code: "() => new Promise(() => 1);", - errors: [eReturnsValue(25, "Literal")] + errors: [{ + messageId: "returnsValue", + type: "Literal", + column: 25, + suggestions: [ + { + messageId: "wrapBraces", + output: "() => new Promise(() => {1});" + } + ] + }] }, { code: "() => new Promise(() => () => 1);", - errors: [eReturnsValue(25, "ArrowFunctionExpression")] + errors: [{ + messageId: "returnsValue", + type: "ArrowFunctionExpression", + column: 25, + suggestions: [ + { + messageId: "wrapBraces", + output: "() => new Promise(() => {() => 1});" + } + ] + }] }, { code: "() => new Promise(() => async () => 1);", parserOptions: { ecmaVersion: 2017 }, // for async - errors: [eReturnsValue(25, "ArrowFunctionExpression")] + errors: [{ + messageId: "returnsValue", + type: "ArrowFunctionExpression", + column: 25, + suggestions: [ + { + messageId: "wrapBraces", + output: "() => new Promise(() => {async () => 1});" + } + ] + }] }, { code: "() => new Promise(() => function () {});", - errors: [eReturnsValue(25, "FunctionExpression")] + errors: [{ + messageId: "returnsValue", + type: "FunctionExpression", + column: 25, + suggestions: [ + { + messageId: "wrapBraces", + output: "() => new Promise(() => {function () {}});" + } + ] + }] }, { code: "() => new Promise(() => function foo() {});", - errors: [eReturnsValue(25, "FunctionExpression")] + errors: [{ + messageId: "returnsValue", + type: "FunctionExpression", + column: 25, + suggestions: [ + { + messageId: "wrapBraces", + output: "() => new Promise(() => {function foo() {}});" + } + ] + }] }, { code: "() => new Promise(() => []);", - errors: [eReturnsValue(25, "ArrayExpression")] + errors: [{ + messageId: "returnsValue", + type: "ArrayExpression", + column: 25, + suggestions: [ + { + messageId: "wrapBraces", + output: "() => new Promise(() => {[]});" + } + ] + }] }, // edge cases for global Promise reference { code: "new Promise((Promise) => { return 1; })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] }, { code: "new Promise(function Promise(resolve, reject) { return 1; })", - errors: [eReturnsValue()] + errors: [{ + messageId: "returnsValue", + type: "ReturnStatement", + suggestions: null + }] } ] }); From f30cefee6bda2789ede18e1664b84c2638ea1bb5 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 18 Oct 2023 17:54:20 +0200 Subject: [PATCH 224/248] test: fix FlatESLint tests for caching (#17658) --- tests/lib/eslint/flat-eslint.js | 387 +++++++++++++++++++------------- 1 file changed, 228 insertions(+), 159 deletions(-) diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index d20dce0c3195..8fdb96ca7bcc 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -2215,33 +2215,43 @@ describe("FlatESLint", () => { } } - /** - * helper method to delete the cache files created during testing - * @returns {void} - */ - function deleteCache() { - doDelete(path.resolve(".eslintcache")); - doDelete(path.resolve(".cache/custom-cache")); - } + let cacheFilePath; beforeEach(() => { - deleteCache(); + cacheFilePath = null; }); afterEach(() => { sinon.restore(); - deleteCache(); + if (cacheFilePath) { + doDelete(cacheFilePath); + } }); - describe("when the cacheFile is a directory or looks like a directory", () => { + describe("when cacheLocation is a directory or looks like a directory", () => { + + const cwd = getFixturePath(); /** - * helper method to delete the cache files created during testing + * helper method to delete the directory used in testing * @returns {void} */ function deleteCacheDir() { try { - fs.unlinkSync("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory"); + + /* + * `fs.rmdir(path, { recursive: true })` is deprecated and will be removed. + * Use `fs.rm(path, { recursive: true })` instead. + * When supporting Node.js 14.14.0+, the compatibility condition can be removed for `fs.rmdir`. + */ + if (typeof fsp.rm === "function") { + + // eslint-disable-next-line n/no-unsupported-features/node-builtins -- conditionally used + fs.rmSync(path.resolve(cwd, "tmp/.cacheFileDir/"), { recursive: true, force: true }); + } else { + fs.rmdirSync(path.resolve(cwd, "tmp/.cacheFileDir/"), { recursive: true, force: true }); + } + } catch { /* @@ -2258,11 +2268,12 @@ describe("FlatESLint", () => { deleteCacheDir(); }); - it("should create the cache file inside the provided directory", async () => { - assert(!shell.test("-d", path.resolve("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory")), "the cache for eslint does not exist"); + it("should create the directory and the cache file inside it when cacheLocation ends with a slash", async () => { + assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); eslint = new FlatESLint({ overrideConfigFile: true, + cwd, // specifying cache true the cache will be created cache: true, @@ -2279,41 +2290,71 @@ describe("FlatESLint", () => { await eslint.lintFiles([file]); - assert(shell.test("-f", path.resolve(`./tmp/.cacheFileDir/.cache_${hash(process.cwd())}`)), "the cache for eslint was created"); - - sinon.restore(); + assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); }); - }); - it("should create the cache file inside the provided directory using the cacheLocation option", async () => { - assert(!shell.test("-d", path.resolve("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory")), "the cache for eslint does not exist"); + it("should create the cache file inside existing cacheLocation directory when cacheLocation ends with a slash", async () => { + assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); - eslint = new FlatESLint({ - overrideConfigFile: true, + fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/")); - // specifying cache true the cache will be created - cache: true, - cacheLocation: "./tmp/.cacheFileDir/", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false + eslint = new FlatESLint({ + overrideConfigFile: true, + cwd, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: "./tmp/.cacheFileDir/", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + ignore: false + }); + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); }); - const file = getFixturePath("cache/src", "test-file.js"); - await eslint.lintFiles([file]); + it("should create the cache file inside existing cacheLocation directory when cacheLocation doesn't end with a path separator", async () => { + assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); - assert(shell.test("-f", path.resolve(`./tmp/.cacheFileDir/.cache_${hash(process.cwd())}`)), "the cache for eslint was created"); + fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/")); - sinon.restore(); + eslint = new FlatESLint({ + overrideConfigFile: true, + cwd, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: "./tmp/.cacheFileDir", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + ignore: false + }); + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); + }); }); it("should create the cache file inside cwd when no cacheLocation provided", async () => { const cwd = path.resolve(getFixturePath("cli-engine")); + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + eslint = new FlatESLint({ overrideConfigFile: true, cache: true, @@ -2329,14 +2370,15 @@ describe("FlatESLint", () => { await eslint.lintFiles([file]); - assert(shell.test("-f", path.resolve(cwd, ".eslintcache")), "the cache for eslint was created at provided cwd"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created at provided cwd"); }); it("should invalidate the cache if the overrideConfig changed between executions", async () => { const cwd = getFixturePath("cache/src"); - const cacheLocation = path.resolve(cwd, ".eslintcache"); - assert(!shell.test("-f", cacheLocation), "the cache for eslint does not exist"); + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new FlatESLint({ overrideConfigFile: true, @@ -2361,11 +2403,11 @@ describe("FlatESLint", () => { const results = await eslint.lintFiles([file]); for (const { errorCount, warningCount } of results) { - assert.strictEqual(errorCount + warningCount, 0, "the file passed without errors or warnings"); + assert.strictEqual(errorCount + warningCount, 0, "the file should have passed linting without errors or warnings"); } - assert(spy.calledWith(file), "ESLint should have read the file because it's considered changed"); - assert(shell.test("-f", cacheLocation), "the cache for eslint should still exist"); + assert(spy.calledWith(file), "ESLint should have read the file because there was no cache file"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); // destroy the spy sinon.restore(); @@ -2388,17 +2430,20 @@ describe("FlatESLint", () => { // create a new spy spy = sinon.spy(fs.promises, "readFile"); - const [cachedResult] = await eslint.lintFiles([file]); + const [newResult] = await eslint.lintFiles([file]); - assert(spy.calledWith(file), "ESLint should have read the file again because is considered changed because the config changed"); - assert.strictEqual(cachedResult.errorCount, 1, "since configuration changed the cache was not used and one error was reported"); - assert(shell.test("-f", cacheLocation), "The cache for ESLint should still exist (2)"); + assert(spy.calledWith(file), "ESLint should have read the file again because it's considered changed because the config changed"); + assert.strictEqual(newResult.errorCount, 1, "since configuration changed the cache should have not been used and one error should have been reported"); + assert.strictEqual(newResult.messages[0].ruleId, "no-console"); + assert(shell.test("-f", cacheFilePath), "The cache for ESLint should still exist"); }); it("should remember the files from a previous run and do not operate on them if not changed", async () => { - const cwd = getFixturePath("cache/src"); - const cacheLocation = path.resolve(cwd, ".eslintcache"); + + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new FlatESLint({ overrideConfigFile: true, @@ -2423,8 +2468,8 @@ describe("FlatESLint", () => { const result = await eslint.lintFiles([file]); - assert(spy.calledWith(file), "the module read the file because is considered changed"); - assert(shell.test("-f", cacheLocation), "the cache for eslint was created"); + assert(spy.calledWith(file), "ESLint should have read the file because there was no cache file"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); // destroy the spy sinon.restore(); @@ -2449,20 +2494,23 @@ describe("FlatESLint", () => { const cachedResult = await eslint.lintFiles([file]); - assert.deepStrictEqual(result, cachedResult, "the result is the same regardless of using cache or not"); + assert.deepStrictEqual(result, cachedResult, "the result should have been the same"); // assert the file was not processed because the cache was used - assert(!spy.calledWith(file), "the file was not loaded because it used the cache"); + assert(!spy.calledWith(file), "the file should not have been reloaded"); }); - it("should remember the files from a previous run and do not operate on then if not changed", async () => { - const cacheLocation = getFixturePath(".eslintcache"); + it("when `cacheLocation` is specified, should create the cache file with `cache:true` and then delete it with `cache:false`", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + const eslintOptions = { overrideConfigFile: true, // specifying cache true the cache will be created cache: true, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2472,8 +2520,6 @@ describe("FlatESLint", () => { cwd: path.join(fixtureDir, "..") }; - assert(!shell.test("-f", cacheLocation), "the cache for eslint does not exist"); - eslint = new FlatESLint(eslintOptions); let file = getFixturePath("cache/src", "test-file.js"); @@ -2482,20 +2528,20 @@ describe("FlatESLint", () => { await eslint.lintFiles([file]); - assert(shell.test("-f", cacheLocation), "the cache for eslint was created"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); eslintOptions.cache = false; eslint = new FlatESLint(eslintOptions); await eslint.lintFiles([file]); - assert(!shell.test("-f", cacheLocation), "the cache for eslint was deleted since last run did not used the cache"); + assert(!shell.test("-f", cacheFilePath), "the cache for eslint should have been deleted since last run did not use the cache"); }); - it("should store in the cache a file that failed the test", async () => { - const cacheLocation = getFixturePath(".eslintcache"); - - assert(!shell.test("-f", cacheLocation), "the cache for eslint does not exist"); + it("should store in the cache a file that has lint messages and a file that doesn't have lint messages", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new FlatESLint({ cwd: path.join(fixtureDir, ".."), @@ -2503,7 +2549,7 @@ describe("FlatESLint", () => { // specifying cache true the cache will be created cache: true, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2514,22 +2560,27 @@ describe("FlatESLint", () => { const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); const result = await eslint.lintFiles([badFile, goodFile]); + const [badFileResult, goodFileResult] = result; + + assert.notStrictEqual(badFileResult.errorCount + badFileResult.warningCount, 0, "the bad file should have some lint errors or warnings"); + assert.strictEqual(goodFileResult.errorCount + badFileResult.warningCount, 0, "the good file should have passed linting without errors or warnings"); + + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - assert(shell.test("-f", cacheLocation), "the cache for eslint was created"); - const fileCache = fCache.createFromFile(cacheLocation); + const fileCache = fCache.createFromFile(cacheFilePath); const { cache } = fileCache; - assert.strictEqual(typeof cache.getKey(goodFile), "object", "the entry for the good file is in the cache"); - assert.strictEqual(typeof cache.getKey(badFile), "object", "the entry for the bad file is in the cache"); + assert.strictEqual(typeof cache.getKey(goodFile), "object", "the entry for the good file should have been in the cache"); + assert.strictEqual(typeof cache.getKey(badFile), "object", "the entry for the bad file should have been in the cache"); const cachedResult = await eslint.lintFiles([badFile, goodFile]); - assert.deepStrictEqual(result, cachedResult, "result is the same with or without cache"); + assert.deepStrictEqual(result, cachedResult, "result should be the same with or without cache"); }); it("should not contain in the cache a file that was deleted", async () => { - const cacheLocation = getFixturePath(".eslintcache"); - - doDelete(cacheLocation); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new FlatESLint({ cwd: path.join(fixtureDir, ".."), @@ -2537,7 +2588,7 @@ describe("FlatESLint", () => { // specifying cache true the cache will be created cache: true, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2550,10 +2601,10 @@ describe("FlatESLint", () => { const toBeDeletedFile = fs.realpathSync(getFixturePath("cache/src", "file-to-delete.js")); await eslint.lintFiles([badFile, goodFile, toBeDeletedFile]); - const fileCache = fCache.createFromFile(cacheLocation); + const fileCache = fCache.createFromFile(cacheFilePath); let { cache } = fileCache; - assert.strictEqual(typeof cache.getKey(toBeDeletedFile), "object", "the entry for the file to be deleted is in the cache"); + assert.strictEqual(typeof cache.getKey(toBeDeletedFile), "object", "the entry for the file to be deleted should have been in the cache"); // delete the file from the file system fs.unlinkSync(toBeDeletedFile); @@ -2564,15 +2615,19 @@ describe("FlatESLint", () => { */ await eslint.lintFiles([badFile, goodFile]); - cache = JSON.parse(fs.readFileSync(cacheLocation)); + cache = JSON.parse(fs.readFileSync(cacheFilePath)); + + assert.strictEqual(typeof cache[0][toBeDeletedFile], "undefined", "the entry for the file to be deleted should not have been in the cache"); - assert.strictEqual(typeof cache[toBeDeletedFile], "undefined", "the entry for the file to be deleted is not in the cache"); + // make sure that the previos assertion checks the right place + assert.notStrictEqual(typeof cache[0][badFile], "undefined", "the entry for the bad file should have been in the cache"); + assert.notStrictEqual(typeof cache[0][goodFile], "undefined", "the entry for the good file should have been in the cache"); }); it("should contain files that were not visited in the cache provided they still exist", async () => { - const cacheLocation = getFixturePath(".eslintcache"); - - doDelete(cacheLocation); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new FlatESLint({ cwd: path.join(fixtureDir, ".."), @@ -2580,7 +2635,7 @@ describe("FlatESLint", () => { // specifying cache true the cache will be created cache: true, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2594,31 +2649,35 @@ describe("FlatESLint", () => { await eslint.lintFiles([badFile, goodFile, testFile2]); - let fileCache = fCache.createFromFile(cacheLocation); + let fileCache = fCache.createFromFile(cacheFilePath); let { cache } = fileCache; - assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 is in the cache"); + assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 should have been in the cache"); /* - * we pass a different set of files minus test-file2 + * we pass a different set of files (minus test-file2) * previous version of file-entry-cache would remove the non visited * entries. 2.0.0 version will keep them unless they don't exist */ await eslint.lintFiles([badFile, goodFile]); - fileCache = fCache.createFromFile(cacheLocation); + fileCache = fCache.createFromFile(cacheFilePath); cache = fileCache.cache; - assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 is in the cache"); + assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 should have been in the cache"); }); it("should not delete cache when executing on text", async () => { - const cacheLocation = getFixturePath(".eslintcache"); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used eslint = new FlatESLint({ cwd: path.join(fixtureDir, ".."), overrideConfigFile: true, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2627,20 +2686,24 @@ describe("FlatESLint", () => { } }); - assert(shell.test("-f", cacheLocation), "the cache for eslint exists"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); await eslint.lintText("var foo = 'bar';"); - assert(shell.test("-f", cacheLocation), "the cache for eslint still exists"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); }); it("should not delete cache when executing on text with a provided filename", async () => { - const cacheLocation = getFixturePath(".eslintcache"); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used eslint = new FlatESLint({ cwd: path.join(fixtureDir, ".."), overrideConfigFile: true, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2649,21 +2712,25 @@ describe("FlatESLint", () => { } }); - assert(shell.test("-f", cacheLocation), "the cache for eslint exists"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); await eslint.lintText("var bar = foo;", { filePath: "fixtures/passing.js" }); - assert(shell.test("-f", cacheLocation), "the cache for eslint still exists"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); }); it("should not delete cache when executing on files with --cache flag", async () => { - const cacheLocation = getFixturePath(".eslintcache"); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + fs.writeFileSync(cacheFilePath, ""); eslint = new FlatESLint({ cwd: path.join(fixtureDir, ".."), overrideConfigFile: true, cache: true, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2673,82 +2740,84 @@ describe("FlatESLint", () => { }); const file = getFixturePath("cli-engine", "console.js"); - assert(shell.test("-f", cacheLocation), "the cache for eslint exists"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); await eslint.lintFiles([file]); - assert(shell.test("-f", cacheLocation), "the cache for eslint still exists"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); }); it("should delete cache when executing on files without --cache flag", async () => { - const cacheLocation = getFixturePath(".eslintcache"); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used eslint = new FlatESLint({ cwd: path.join(fixtureDir, ".."), overrideConfigFile: true, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, "no-unused-vars": 2 } } - }); const file = getFixturePath("cli-engine", "console.js"); - assert(shell.test("-f", cacheLocation), "the cache for eslint exists"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); await eslint.lintFiles([file]); - assert(!shell.test("-f", cacheLocation), "the cache for eslint has been deleted"); + assert(!shell.test("-f", cacheFilePath), "the cache for eslint should have been deleted"); }); - describe("cacheFile", () => { - it("should use the specified cache file", async () => { - const customCacheFile = path.resolve(".cache/custom-cache"); + it("should use the specified cache file", async () => { + cacheFilePath = path.resolve(".cache/custom-cache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - assert(!shell.test("-f", customCacheFile), "the cache for eslint does not exist"); + eslint = new FlatESLint({ + overrideConfigFile: true, - eslint = new FlatESLint({ - overrideConfigFile: true, + // specify a custom cache file + cacheLocation: cacheFilePath, - // specify a custom cache file - cacheLocation: customCacheFile, + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, + cwd: path.join(fixtureDir, "..") + }); + const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); + const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); + const result = await eslint.lintFiles([badFile, goodFile]); - cwd: path.join(fixtureDir, "..") - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const result = await eslint.lintFiles([badFile, goodFile]); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - assert(shell.test("-f", customCacheFile), "the cache for eslint was created"); - const fileCache = fCache.createFromFile(customCacheFile); - const { cache } = fileCache; + const fileCache = fCache.createFromFile(cacheFilePath); + const { cache } = fileCache; - assert(typeof cache.getKey(goodFile) === "object", "the entry for the good file is in the cache"); + assert(typeof cache.getKey(goodFile) === "object", "the entry for the good file should have been in the cache"); + assert(typeof cache.getKey(badFile) === "object", "the entry for the bad file should have been in the cache"); - assert(typeof cache.getKey(badFile) === "object", "the entry for the bad file is in the cache"); - const cachedResult = await eslint.lintFiles([badFile, goodFile]); + const cachedResult = await eslint.lintFiles([badFile, goodFile]); - assert.deepStrictEqual(result, cachedResult, "result is the same with or without cache"); - }); + assert.deepStrictEqual(result, cachedResult, "result should be the same with or without cache"); }); describe("cacheStrategy", () => { it("should detect changes using a file's modification time when set to 'metadata'", async () => { - const cacheLocation = getFixturePath(".eslintcache"); - - doDelete(cacheLocation); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new FlatESLint({ cwd: path.join(fixtureDir, ".."), @@ -2756,7 +2825,7 @@ describe("FlatESLint", () => { // specifying cache true the cache will be created cache: true, - cacheLocation, + cacheLocation: cacheFilePath, cacheStrategy: "metadata", overrideConfig: { rules: { @@ -2770,24 +2839,24 @@ describe("FlatESLint", () => { const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); await eslint.lintFiles([badFile, goodFile]); - let fileCache = fCache.createFromFile(cacheLocation); + let fileCache = fCache.createFromFile(cacheFilePath); const entries = fileCache.normalizeEntries([badFile, goodFile]); entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} is initially unchanged`); + assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); }); // this should result in a changed entry shell.touch(goodFile); - fileCache = fCache.createFromFile(cacheLocation); - assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} is unchanged`); - assert(fileCache.getFileDescriptor(goodFile).changed === true, `the entry for ${goodFile} is changed`); + fileCache = fCache.createFromFile(cacheFilePath); + assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} should have been unchanged`); + assert(fileCache.getFileDescriptor(goodFile).changed === true, `the entry for ${goodFile} should have been changed`); }); it("should not detect changes using a file's modification time when set to 'content'", async () => { - const cacheLocation = getFixturePath(".eslintcache"); - - doDelete(cacheLocation); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new FlatESLint({ cwd: path.join(fixtureDir, ".."), @@ -2795,7 +2864,7 @@ describe("FlatESLint", () => { // specifying cache true the cache will be created cache: true, - cacheLocation, + cacheLocation: cacheFilePath, cacheStrategy: "content", overrideConfig: { rules: { @@ -2809,26 +2878,26 @@ describe("FlatESLint", () => { const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); await eslint.lintFiles([badFile, goodFile]); - let fileCache = fCache.createFromFile(cacheLocation, true); + let fileCache = fCache.createFromFile(cacheFilePath, true); let entries = fileCache.normalizeEntries([badFile, goodFile]); entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} is initially unchanged`); + assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); }); // this should NOT result in a changed entry shell.touch(goodFile); - fileCache = fCache.createFromFile(cacheLocation, true); + fileCache = fCache.createFromFile(cacheFilePath, true); entries = fileCache.normalizeEntries([badFile, goodFile]); entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} remains unchanged`); + assert(entry.changed === false, `the entry for ${entry.key} should have remained unchanged`); }); }); it("should detect changes using a file's contents when set to 'content'", async () => { - const cacheLocation = getFixturePath(".eslintcache"); - - doDelete(cacheLocation); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new FlatESLint({ cwd: path.join(fixtureDir, ".."), @@ -2836,7 +2905,7 @@ describe("FlatESLint", () => { // specifying cache true the cache will be created cache: true, - cacheLocation, + cacheLocation: cacheFilePath, cacheStrategy: "content", overrideConfig: { rules: { @@ -2853,18 +2922,18 @@ describe("FlatESLint", () => { shell.cp(goodFile, goodFileCopy); await eslint.lintFiles([badFile, goodFileCopy]); - let fileCache = fCache.createFromFile(cacheLocation, true); + let fileCache = fCache.createFromFile(cacheFilePath, true); const entries = fileCache.normalizeEntries([badFile, goodFileCopy]); entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} is initially unchanged`); + assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); }); // this should result in a changed entry shell.sed("-i", "abc", "xzy", goodFileCopy); - fileCache = fCache.createFromFile(cacheLocation, true); - assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} is unchanged`); - assert(fileCache.getFileDescriptor(goodFileCopy).changed === true, `the entry for ${goodFileCopy} is changed`); + fileCache = fCache.createFromFile(cacheFilePath, true); + assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} should have been unchanged`); + assert(fileCache.getFileDescriptor(goodFileCopy).changed === true, `the entry for ${goodFileCopy} should have been changed`); }); }); }); From 5de9637fc925729a83d5a5e9e868a41792a184e3 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 20 Oct 2023 13:00:16 -0400 Subject: [PATCH 225/248] fix: Ensure shared references in rule configs are separated (#17666) * fix: Ensure shared references in rule configs are separated. In eslint.config.js files, it's possible for two rule configs to contain references to the same object. That object may be modified during validation, thus affecting all configs, and may create validation errors. This clones rule configs before validation to ensure that each rule is using unique references to manage its configs. Fixes #12592 * Update tests/lib/config/flat-config-array.js Co-authored-by: Milos Djermanovic * Update tests/lib/config/flat-config-array.js Co-authored-by: Milos Djermanovic * Update lib/config/flat-config-schema.js Co-authored-by: Milos Djermanovic * Update lib/config/flat-config-schema.js Co-authored-by: Milos Djermanovic * Upgrade config-array and update tests --------- Co-authored-by: Milos Djermanovic --- lib/config/flat-config-schema.js | 91 ++++++++++++++++----------- package.json | 3 +- tests/lib/config/flat-config-array.js | 67 ++++++++++++++++++++ 3 files changed, 124 insertions(+), 37 deletions(-) diff --git a/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js index df850995d87f..911d159d93b8 100644 --- a/lib/config/flat-config-schema.js +++ b/lib/config/flat-config-schema.js @@ -5,6 +5,16 @@ "use strict"; +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + +/* + * Note: This can be removed in ESLint v9 because structuredClone is available globally + * starting in Node.js v17. + */ +const structuredClone = require("@ungap/structured-clone").default; + //----------------------------------------------------------------------------- // Type Definitions //----------------------------------------------------------------------------- @@ -119,7 +129,7 @@ function normalizeRuleOptions(ruleOptions) { : [ruleOptions]; finalOptions[0] = ruleSeverities.get(finalOptions[0]); - return finalOptions; + return structuredClone(finalOptions); } //----------------------------------------------------------------------------- @@ -378,48 +388,57 @@ const rulesSchema = { ...second }; - for (const ruleId of Object.keys(result)) { - - // avoid hairy edge case - if (ruleId === "__proto__") { - - /* eslint-disable-next-line no-proto -- Though deprecated, may still be present */ - delete result.__proto__; - continue; - } - - result[ruleId] = normalizeRuleOptions(result[ruleId]); - - /* - * If either rule config is missing, then the correct - * config is already present and we just need to normalize - * the severity. - */ - if (!(ruleId in first) || !(ruleId in second)) { - continue; - } - const firstRuleOptions = normalizeRuleOptions(first[ruleId]); - const secondRuleOptions = normalizeRuleOptions(second[ruleId]); + for (const ruleId of Object.keys(result)) { - /* - * If the second rule config only has a severity (length of 1), - * then use that severity and keep the rest of the options from - * the first rule config. - */ - if (secondRuleOptions.length === 1) { - result[ruleId] = [secondRuleOptions[0], ...firstRuleOptions.slice(1)]; - continue; + try { + + // avoid hairy edge case + if (ruleId === "__proto__") { + + /* eslint-disable-next-line no-proto -- Though deprecated, may still be present */ + delete result.__proto__; + continue; + } + + result[ruleId] = normalizeRuleOptions(result[ruleId]); + + /* + * If either rule config is missing, then the correct + * config is already present and we just need to normalize + * the severity. + */ + if (!(ruleId in first) || !(ruleId in second)) { + continue; + } + + const firstRuleOptions = normalizeRuleOptions(first[ruleId]); + const secondRuleOptions = normalizeRuleOptions(second[ruleId]); + + /* + * If the second rule config only has a severity (length of 1), + * then use that severity and keep the rest of the options from + * the first rule config. + */ + if (secondRuleOptions.length === 1) { + result[ruleId] = [secondRuleOptions[0], ...firstRuleOptions.slice(1)]; + continue; + } + + /* + * In any other situation, then the second rule config takes + * precedence. That means the value at `result[ruleId]` is + * already correct and no further work is necessary. + */ + } catch (ex) { + throw new Error(`Key "${ruleId}": ${ex.message}`, { cause: ex }); } - /* - * In any other situation, then the second rule config takes - * precedence. That means the value at `result[ruleId]` is - * already correct and no further work is necessary. - */ } return result; + + }, validate(value) { diff --git a/package.json b/package.json index ece3957419ee..8f4a112e3cdb 100644 --- a/package.json +++ b/package.json @@ -64,9 +64,10 @@ "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", "@eslint/js": "8.51.0", - "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index 728b3e93785c..b0dbfec93f7f 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -1987,4 +1987,71 @@ describe("FlatConfigArray", () => { }); }); + + // https://github.com/eslint/eslint/issues/12592 + describe("Shared references between rule configs", () => { + + it("shared rule config should not cause a rule validation error", () => { + + const ruleConfig = ["error", {}]; + + const configs = new FlatConfigArray([{ + rules: { + camelcase: ruleConfig, + "default-case": ruleConfig + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.rules, { + camelcase: [2, { + ignoreDestructuring: false, + ignoreGlobals: false, + ignoreImports: false + }], + "default-case": [2, {}] + }); + + }); + + + it("should throw rule validation error for camelcase", async () => { + + const ruleConfig = ["error", {}]; + + const configs = new FlatConfigArray([ + { + rules: { + camelcase: ruleConfig + } + }, + { + rules: { + "default-case": ruleConfig, + + + camelcase: [ + "error", + { + ignoreDestructuring: Date + } + + ] + } + } + ]); + + configs.normalizeSync(); + + // exact error may differ based on structuredClone implementation so just test prefix + assert.throws(() => { + configs.getConfig("foo.js"); + }, /Key "rules": Key "camelcase":/u); + + }); + + }); }); From 476d58a584d5d2db003c4c22ffee90e63566164d Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 20 Oct 2023 13:19:22 -0400 Subject: [PATCH 226/248] docs: Add note about invalid CLI flags when using flat config. (#17664) * fix: Add note about invalid CLI flags when using flat config. Fixes #17652 * Update docs/src/use/command-line-interface.md Co-authored-by: Milos Djermanovic * Upgrade config-array * Move error message --------- Co-authored-by: Milos Djermanovic --- docs/src/use/command-line-interface.md | 10 +++++----- lib/cli.js | 9 ++++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/src/use/command-line-interface.md b/docs/src/use/command-line-interface.md index 6087aae4fcc6..4749b7c19ec6 100644 --- a/docs/src/use/command-line-interface.md +++ b/docs/src/use/command-line-interface.md @@ -121,7 +121,7 @@ Miscellaneous: #### `--no-eslintrc` -Disables use of configuration from `.eslintrc.*` and `package.json` files. +**eslintrc Mode Only.** Disables use of configuration from `.eslintrc.*` and `package.json` files. For flat config mode, use `--no-config-lookup` instead. * **Argument Type**: No argument. @@ -150,7 +150,7 @@ If `.eslintrc.*` and/or `package.json` files are also used for configuration (i. #### `--env` -This option enables specific environments. +**eslintrc Mode Only.** This option enables specific environments. * **Argument Type**: String. One of the available environments. * **Multiple Arguments**: Yes @@ -166,7 +166,7 @@ npx eslint --env browser --env node file.js #### `--ext` -This option allows you to specify which file extensions ESLint uses when searching for target files in the directories you specify. +**eslintrc Mode Only.** 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 @@ -232,7 +232,7 @@ echo '3 ** 4' | npx eslint --stdin --parser-options ecmaVersion:7 # succeeds, ya #### `--resolve-plugins-relative-to` -Changes the directory where plugins are resolved from. +**eslintrc Mode Only.** Changes the directory where plugins are resolved from. * **Argument Type**: String. Path to directory. * **Multiple Arguments**: No @@ -374,7 +374,7 @@ npx eslint --fix --fix-type suggestion,layout . #### `--ignore-path` -This option allows you to specify the file to use as your `.eslintignore`. +**eslintrc Mode Only.** This option allows you to specify the file to use as your `.eslintignore`. * **Argument Type**: String. Path to file. * **Multiple Arguments**: No diff --git a/lib/cli.js b/lib/cli.js index 807d28a0d1bc..f472659c20f9 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -318,7 +318,14 @@ const cli = { options = CLIOptions.parse(args); } catch (error) { debug("Error parsing CLI options:", error.message); - log.error(error.message); + + let errorMessage = error.message; + + if (usingFlatConfig) { + errorMessage += "\nYou'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."; + } + + log.error(errorMessage); return 2; } From d63d4fe0942e6747ab60e758aa36076f43041a30 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 20 Oct 2023 16:25:39 -0400 Subject: [PATCH 227/248] 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 7e43db7f2e9a..f9d8a1c66925 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "8.51.0", + "version": "8.52.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 6d1f0c2da0309c06c21149b8d71a8f439a70d7e8 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 20 Oct 2023 22:44:39 +0200 Subject: [PATCH 228/248] chore: upgrade @eslint/js@8.52.0 (#17671) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f4a112e3cdb..14f86b846cfc 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.51.0", + "@eslint/js": "8.52.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", From 7dc28ed1615169c68c08808fa445172cc0cf37e0 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 20 Oct 2023 17:00:03 -0400 Subject: [PATCH 229/248] Build: changelog update for 8.52.0 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61aa641c59f3..7cbca458d6c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +v8.52.0 - October 20, 2023 + +* [`6d1f0c2`](https://github.com/eslint/eslint/commit/6d1f0c2da0309c06c21149b8d71a8f439a70d7e8) chore: upgrade @eslint/js@8.52.0 (#17671) (Milos Djermanovic) +* [`d63d4fe`](https://github.com/eslint/eslint/commit/d63d4fe0942e6747ab60e758aa36076f43041a30) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`476d58a`](https://github.com/eslint/eslint/commit/476d58a584d5d2db003c4c22ffee90e63566164d) docs: Add note about invalid CLI flags when using flat config. (#17664) (Nicholas C. Zakas) +* [`5de9637`](https://github.com/eslint/eslint/commit/5de9637fc925729a83d5a5e9e868a41792a184e3) fix: Ensure shared references in rule configs are separated (#17666) (Nicholas C. Zakas) +* [`f30cefe`](https://github.com/eslint/eslint/commit/f30cefee6bda2789ede18e1664b84c2638ea1bb5) test: fix FlatESLint tests for caching (#17658) (Milos Djermanovic) +* [`ef650cb`](https://github.com/eslint/eslint/commit/ef650cb612510bcfa1379c1f0af56dd563b3a705) test: update tests for no-promise-executor-return (#17661) (Milos Djermanovic) +* [`70648ee`](https://github.com/eslint/eslint/commit/70648ee49c07f7b533d09f6bf8a5291e5a5a8601) feat: report-unused-disable-directive to report unused eslint-enable (#17611) (Yosuke Ota) +* [`dcfe573`](https://github.com/eslint/eslint/commit/dcfe5739c374c9d7ed21f14027870ec0fd453661) fix: add preceding semicolon in suggestions of `no-object-constructor` (#17649) (Francesco Trotta) +* [`660ed3a`](https://github.com/eslint/eslint/commit/660ed3afd128ad529234a855345629982caf1bc7) docs: Plugin flat config migration guide (#17640) (Nicholas C. Zakas) +* [`a58aa20`](https://github.com/eslint/eslint/commit/a58aa200fccedae7e2e9b6129246f2cedab14f8d) docs: fix examples for several rules (#17645) (Milos Djermanovic) +* [`179929b`](https://github.com/eslint/eslint/commit/179929bd46892f18f2aef0c159d5cc361cb69987) docs: Remove trailing newline from the code of Playground links (#17641) (Francesco Trotta) +* [`f8e5c30`](https://github.com/eslint/eslint/commit/f8e5c30636450d4a8baf51f0e227685e6d77ac64) docs: Update README (GitHub Actions Bot) +* [`b7ef2f3`](https://github.com/eslint/eslint/commit/b7ef2f34fe12b68a366e1b4bf5f64d7332c6e72e) docs: Enable pretty code formatter output (#17635) (Nicholas C. Zakas) +* [`0bcb9a8`](https://github.com/eslint/eslint/commit/0bcb9a8db608a3d0bd2645f99e0707b9a9bbaaf0) docs: Fix syntax errors in rule examples (#17633) (Francesco Trotta) +* [`61b9083`](https://github.com/eslint/eslint/commit/61b90839633ef300ac7707a651f65f532e65f42d) docs: Make no-continue example code work (#17643) (Zhongyuan Zhou) +* [`9fafe45`](https://github.com/eslint/eslint/commit/9fafe450c31ed9b6bdd9dcd6c115255943b8c1c2) docs: upgrade to 11ty 2.0 (#17632) (Percy Ma) +* [`ff8e4bf`](https://github.com/eslint/eslint/commit/ff8e4bf327b5c92b0623b0fc5f8f101954f785db) docs: Update README (GitHub Actions Bot) +* [`fab249a`](https://github.com/eslint/eslint/commit/fab249ae6addac2ee18cd81cee80916010bb469e) docs: Update README (GitHub Actions Bot) +* [`392305b`](https://github.com/eslint/eslint/commit/392305bf4797e3ebc696dfca48bd874741fca845) docs: Update `no-irregular-whitespace` and fix examples (#17626) (Francesco Trotta) +* [`6b8acfb`](https://github.com/eslint/eslint/commit/6b8acfb770589f3941df41c3910d3b8ffc3e1e45) docs: Add real whitespace to `no-trailing-spaces` examples (#17630) (Francesco Trotta) +* [`1000187`](https://github.com/eslint/eslint/commit/1000187e00949332babcee4d37d46c96a6a554a8) docs: Fix examples in `unicode-bom` (#17631) (Francesco Trotta) +* [`000290c`](https://github.com/eslint/eslint/commit/000290c4c923cc1473e21b4bdbdc0c42765ef7dd) docs: Update README (GitHub Actions Bot) + v8.51.0 - October 6, 2023 * [`1ef39ea`](https://github.com/eslint/eslint/commit/1ef39ea5b884453be717ebc929155d7eb584dcbf) chore: upgrade @eslint/js@8.51.0 (#17624) (Milos Djermanovic) From 331cf62024b6c7ad4067c14c593f116576c3c861 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 20 Oct 2023 17:00:04 -0400 Subject: [PATCH 230/248] 8.52.0 --- docs/package.json | 2 +- .../formatters/html-formatter-example.html | 2 +- docs/src/use/formatters/index.md | 757 +++++++++++++++++- package.json | 2 +- 4 files changed, 751 insertions(+), 12 deletions(-) diff --git a/docs/package.json b/docs/package.json index d24e203338c3..fa9d7101dd4a 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.51.0", + "version": "8.52.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 2981958fab76..05cbe6e08b39 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

- 9 problems (5 errors, 4 warnings) - Generated on Fri Oct 06 2023 16:14:35 GMT-0400 (Eastern Daylight Time) + 9 problems (5 errors, 4 warnings) - Generated on Fri Oct 20 2023 17:00:05 GMT-0400 (Eastern Daylight Time)
diff --git a/docs/src/use/formatters/index.md b/docs/src/use/formatters/index.md index ec6ce537ed71..95a930aa3f36 100644 --- a/docs/src/use/formatters/index.md +++ b/docs/src/use/formatters/index.md @@ -71,7 +71,7 @@ Outputs results to the [Checkstyle](https://checkstyle.sourceforge.io/) format. Example output: -```text +```xml ``` @@ -109,7 +109,7 @@ Outputs results to format compatible with the [JSLint Jenkins plugin](https://pl Example output: -```text +```xml ``` @@ -119,10 +119,595 @@ Outputs JSON-serialized results. The `json-with-metadata` provides the same lint Alternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint. -Example output: +Example output (formatted for easier reading): -```text -{"results":[{"filePath":"/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'addOne' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":16},{"ruleId":"use-isnan","severity":2,"message":"Use the isNaN function to compare with NaN.","line":2,"column":9,"nodeType":"BinaryExpression","messageId":"comparisonWithNaN","endLine":2,"endColumn":17},{"ruleId":"space-unary-ops","severity":2,"message":"Unexpected space before unary operator '++'.","line":3,"column":16,"nodeType":"UpdateExpression","messageId":"unexpectedBefore","endLine":3,"endColumn":20,"fix":{"range":[57,58],"text":""}},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":3,"column":20,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":4,"endColumn":1,"fix":{"range":[60,60],"text":";"}},{"ruleId":"no-else-return","severity":1,"message":"Unnecessary 'else' after 'return'.","line":4,"column":12,"nodeType":"BlockStatement","messageId":"unexpected","endLine":6,"endColumn":6,"fix":{"range":[0,94],"text":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}"}},{"ruleId":"indent","severity":1,"message":"Expected indentation of 8 spaces but found 6.","line":5,"column":1,"nodeType":"Keyword","messageId":"wrongIndentation","endLine":5,"endColumn":7,"fix":{"range":[74,80],"text":" "}},{"ruleId":"consistent-return","severity":2,"message":"Function 'addOne' expected a return value.","line":5,"column":7,"nodeType":"ReturnStatement","messageId":"missingReturnValue","endLine":5,"endColumn":13},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":13,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":6,"endColumn":1,"fix":{"range":[86,86],"text":";"}},{"ruleId":"no-extra-semi","severity":2,"message":"Unnecessary semicolon.","line":7,"column":2,"nodeType":"EmptyStatement","messageId":"unexpected","endLine":7,"endColumn":3,"fix":{"range":[93,95],"text":"}"}}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":2,"fixableWarningCount":4,"source":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};"}],"metadata":{"rulesMeta":{"no-else-return":{"type":"suggestion","docs":{"description":"Disallow `else` blocks after `return` statements in `if` statements","recommended":false,"url":"https://eslint.org/docs/latest/rules/no-else-return"},"schema":[{"type":"object","properties":{"allowElseIf":{"type":"boolean","default":true}},"additionalProperties":false}],"fixable":"code","messages":{"unexpected":"Unnecessary 'else' after 'return'."}},"indent":{"type":"layout","docs":{"description":"Enforce consistent indentation","recommended":false,"url":"https://eslint.org/docs/latest/rules/indent"},"fixable":"whitespace","schema":[{"oneOf":[{"enum":["tab"]},{"type":"integer","minimum":0}]},{"type":"object","properties":{"SwitchCase":{"type":"integer","minimum":0,"default":0},"VariableDeclarator":{"oneOf":[{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},{"type":"object","properties":{"var":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"let":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"const":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false}]},"outerIIFEBody":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"MemberExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["off"]}]},"FunctionDeclaration":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"FunctionExpression":{"type":"object","properties":{"parameters":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"body":{"type":"integer","minimum":0}},"additionalProperties":false},"StaticBlock":{"type":"object","properties":{"body":{"type":"integer","minimum":0}},"additionalProperties":false},"CallExpression":{"type":"object","properties":{"arguments":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]}},"additionalProperties":false},"ArrayExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ObjectExpression":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"ImportDeclaration":{"oneOf":[{"type":"integer","minimum":0},{"enum":["first","off"]}]},"flatTernaryExpressions":{"type":"boolean","default":false},"offsetTernaryExpressions":{"type":"boolean","default":false},"ignoredNodes":{"type":"array","items":{"type":"string","not":{"pattern":":exit$"}}},"ignoreComments":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"wrongIndentation":"Expected indentation of {{expected}} but found {{actual}}."}},"space-unary-ops":{"type":"layout","docs":{"description":"Enforce consistent spacing before or after unary operators","recommended":false,"url":"https://eslint.org/docs/latest/rules/space-unary-ops"},"fixable":"whitespace","schema":[{"type":"object","properties":{"words":{"type":"boolean","default":true},"nonwords":{"type":"boolean","default":false},"overrides":{"type":"object","additionalProperties":{"type":"boolean"}}},"additionalProperties":false}],"messages":{"unexpectedBefore":"Unexpected space before unary operator '{{operator}}'.","unexpectedAfter":"Unexpected space after unary operator '{{operator}}'.","unexpectedAfterWord":"Unexpected space after unary word operator '{{word}}'.","wordOperator":"Unary word operator '{{word}}' must be followed by whitespace.","operator":"Unary operator '{{operator}}' must be followed by whitespace.","beforeUnaryExpressions":"Space is required before unary expressions '{{token}}'."}},"semi":{"type":"layout","docs":{"description":"Require or disallow semicolons instead of ASI","recommended":false,"url":"https://eslint.org/docs/latest/rules/semi"},"fixable":"code","schema":{"anyOf":[{"type":"array","items":[{"enum":["never"]},{"type":"object","properties":{"beforeStatementContinuationChars":{"enum":["always","any","never"]}},"additionalProperties":false}],"minItems":0,"maxItems":2},{"type":"array","items":[{"enum":["always"]},{"type":"object","properties":{"omitLastInOneLineBlock":{"type":"boolean"},"omitLastInOneLineClassBody":{"type":"boolean"}},"additionalProperties":false}],"minItems":0,"maxItems":2}]},"messages":{"missingSemi":"Missing semicolon.","extraSemi":"Extra semicolon."}},"consistent-return":{"type":"suggestion","docs":{"description":"Require `return` statements to either always or never specify values","recommended":false,"url":"https://eslint.org/docs/latest/rules/consistent-return"},"schema":[{"type":"object","properties":{"treatUndefinedAsUnspecified":{"type":"boolean","default":false}},"additionalProperties":false}],"messages":{"missingReturn":"Expected to return a value at the end of {{name}}.","missingReturnValue":"{{name}} expected a return value.","unexpectedReturnValue":"{{name}} expected no return value."}}}}} +```json +{ + "results": [ + { + "filePath": "/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js", + "messages": [ + { + "ruleId": "no-unused-vars", + "severity": 2, + "message": "'addOne' is defined but never used.", + "line": 1, + "column": 10, + "nodeType": "Identifier", + "messageId": "unusedVar", + "endLine": 1, + "endColumn": 16 + }, + { + "ruleId": "use-isnan", + "severity": 2, + "message": "Use the isNaN function to compare with NaN.", + "line": 2, + "column": 9, + "nodeType": "BinaryExpression", + "messageId": "comparisonWithNaN", + "endLine": 2, + "endColumn": 17 + }, + { + "ruleId": "space-unary-ops", + "severity": 2, + "message": "Unexpected space before unary operator '++'.", + "line": 3, + "column": 16, + "nodeType": "UpdateExpression", + "messageId": "unexpectedBefore", + "endLine": 3, + "endColumn": 20, + "fix": { + "range": [ + 57, + 58 + ], + "text": "" + } + }, + { + "ruleId": "semi", + "severity": 1, + "message": "Missing semicolon.", + "line": 3, + "column": 20, + "nodeType": "ReturnStatement", + "messageId": "missingSemi", + "endLine": 4, + "endColumn": 1, + "fix": { + "range": [ + 60, + 60 + ], + "text": ";" + } + }, + { + "ruleId": "no-else-return", + "severity": 1, + "message": "Unnecessary 'else' after 'return'.", + "line": 4, + "column": 12, + "nodeType": "BlockStatement", + "messageId": "unexpected", + "endLine": 6, + "endColumn": 6, + "fix": { + "range": [ + 0, + 94 + ], + "text": "function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}" + } + }, + { + "ruleId": "indent", + "severity": 1, + "message": "Expected indentation of 8 spaces but found 6.", + "line": 5, + "column": 1, + "nodeType": "Keyword", + "messageId": "wrongIndentation", + "endLine": 5, + "endColumn": 7, + "fix": { + "range": [ + 74, + 80 + ], + "text": " " + } + }, + { + "ruleId": "consistent-return", + "severity": 2, + "message": "Function 'addOne' expected a return value.", + "line": 5, + "column": 7, + "nodeType": "ReturnStatement", + "messageId": "missingReturnValue", + "endLine": 5, + "endColumn": 13 + }, + { + "ruleId": "semi", + "severity": 1, + "message": "Missing semicolon.", + "line": 5, + "column": 13, + "nodeType": "ReturnStatement", + "messageId": "missingSemi", + "endLine": 6, + "endColumn": 1, + "fix": { + "range": [ + 86, + 86 + ], + "text": ";" + } + }, + { + "ruleId": "no-extra-semi", + "severity": 2, + "message": "Unnecessary semicolon.", + "line": 7, + "column": 2, + "nodeType": "EmptyStatement", + "messageId": "unexpected", + "endLine": 7, + "endColumn": 3, + "fix": { + "range": [ + 93, + 95 + ], + "text": "}" + } + } + ], + "suppressedMessages": [], + "errorCount": 5, + "fatalErrorCount": 0, + "warningCount": 4, + "fixableErrorCount": 2, + "fixableWarningCount": 4, + "source": "function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};" + } + ], + "metadata": { + "rulesMeta": { + "no-else-return": { + "type": "suggestion", + "docs": { + "description": "Disallow `else` blocks after `return` statements in `if` statements", + "recommended": false, + "url": "https://eslint.org/docs/latest/rules/no-else-return" + }, + "schema": [ + { + "type": "object", + "properties": { + "allowElseIf": { + "type": "boolean", + "default": true + } + }, + "additionalProperties": false + } + ], + "fixable": "code", + "messages": { + "unexpected": "Unnecessary 'else' after 'return'." + } + }, + "indent": { + "type": "layout", + "docs": { + "description": "Enforce consistent indentation", + "recommended": false, + "url": "https://eslint.org/docs/latest/rules/indent" + }, + "fixable": "whitespace", + "schema": [ + { + "oneOf": [ + { + "enum": [ + "tab" + ] + }, + { + "type": "integer", + "minimum": 0 + } + ] + }, + { + "type": "object", + "properties": { + "SwitchCase": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "VariableDeclarator": { + "oneOf": [ + { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "enum": [ + "first", + "off" + ] + } + ] + }, + { + "type": "object", + "properties": { + "var": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "enum": [ + "first", + "off" + ] + } + ] + }, + "let": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "enum": [ + "first", + "off" + ] + } + ] + }, + "const": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "enum": [ + "first", + "off" + ] + } + ] + } + }, + "additionalProperties": false + } + ] + }, + "outerIIFEBody": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "enum": [ + "off" + ] + } + ] + }, + "MemberExpression": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "enum": [ + "off" + ] + } + ] + }, + "FunctionDeclaration": { + "type": "object", + "properties": { + "parameters": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "enum": [ + "first", + "off" + ] + } + ] + }, + "body": { + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "FunctionExpression": { + "type": "object", + "properties": { + "parameters": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "enum": [ + "first", + "off" + ] + } + ] + }, + "body": { + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "StaticBlock": { + "type": "object", + "properties": { + "body": { + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "CallExpression": { + "type": "object", + "properties": { + "arguments": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "enum": [ + "first", + "off" + ] + } + ] + } + }, + "additionalProperties": false + }, + "ArrayExpression": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "enum": [ + "first", + "off" + ] + } + ] + }, + "ObjectExpression": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "enum": [ + "first", + "off" + ] + } + ] + }, + "ImportDeclaration": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "enum": [ + "first", + "off" + ] + } + ] + }, + "flatTernaryExpressions": { + "type": "boolean", + "default": false + }, + "offsetTernaryExpressions": { + "type": "boolean", + "default": false + }, + "ignoredNodes": { + "type": "array", + "items": { + "type": "string", + "not": { + "pattern": ":exit$" + } + } + }, + "ignoreComments": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + } + ], + "messages": { + "wrongIndentation": "Expected indentation of {{expected}} but found {{actual}}." + } + }, + "space-unary-ops": { + "type": "layout", + "docs": { + "description": "Enforce consistent spacing before or after unary operators", + "recommended": false, + "url": "https://eslint.org/docs/latest/rules/space-unary-ops" + }, + "fixable": "whitespace", + "schema": [ + { + "type": "object", + "properties": { + "words": { + "type": "boolean", + "default": true + }, + "nonwords": { + "type": "boolean", + "default": false + }, + "overrides": { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + } + }, + "additionalProperties": false + } + ], + "messages": { + "unexpectedBefore": "Unexpected space before unary operator '{{operator}}'.", + "unexpectedAfter": "Unexpected space after unary operator '{{operator}}'.", + "unexpectedAfterWord": "Unexpected space after unary word operator '{{word}}'.", + "wordOperator": "Unary word operator '{{word}}' must be followed by whitespace.", + "operator": "Unary operator '{{operator}}' must be followed by whitespace.", + "beforeUnaryExpressions": "Space is required before unary expressions '{{token}}'." + } + }, + "semi": { + "type": "layout", + "docs": { + "description": "Require or disallow semicolons instead of ASI", + "recommended": false, + "url": "https://eslint.org/docs/latest/rules/semi" + }, + "fixable": "code", + "schema": { + "anyOf": [ + { + "type": "array", + "items": [ + { + "enum": [ + "never" + ] + }, + { + "type": "object", + "properties": { + "beforeStatementContinuationChars": { + "enum": [ + "always", + "any", + "never" + ] + } + }, + "additionalProperties": false + } + ], + "minItems": 0, + "maxItems": 2 + }, + { + "type": "array", + "items": [ + { + "enum": [ + "always" + ] + }, + { + "type": "object", + "properties": { + "omitLastInOneLineBlock": { + "type": "boolean" + }, + "omitLastInOneLineClassBody": { + "type": "boolean" + } + }, + "additionalProperties": false + } + ], + "minItems": 0, + "maxItems": 2 + } + ] + }, + "messages": { + "missingSemi": "Missing semicolon.", + "extraSemi": "Extra semicolon." + } + }, + "consistent-return": { + "type": "suggestion", + "docs": { + "description": "Require `return` statements to either always or never specify values", + "recommended": false, + "url": "https://eslint.org/docs/latest/rules/consistent-return" + }, + "schema": [ + { + "type": "object", + "properties": { + "treatUndefinedAsUnspecified": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + } + ], + "messages": { + "missingReturn": "Expected to return a value at the end of {{name}}.", + "missingReturnValue": "{{name}} expected a return value.", + "unexpectedReturnValue": "{{name}} expected no return value." + } + } + } + } +} ``` ### json @@ -131,10 +716,164 @@ Outputs JSON-serialized results. The `json` formatter is useful when you want to Alternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint. -Example output: +Example output (formatted for easier reading): -```text -[{"filePath":"/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'addOne' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":16},{"ruleId":"use-isnan","severity":2,"message":"Use the isNaN function to compare with NaN.","line":2,"column":9,"nodeType":"BinaryExpression","messageId":"comparisonWithNaN","endLine":2,"endColumn":17},{"ruleId":"space-unary-ops","severity":2,"message":"Unexpected space before unary operator '++'.","line":3,"column":16,"nodeType":"UpdateExpression","messageId":"unexpectedBefore","endLine":3,"endColumn":20,"fix":{"range":[57,58],"text":""}},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":3,"column":20,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":4,"endColumn":1,"fix":{"range":[60,60],"text":";"}},{"ruleId":"no-else-return","severity":1,"message":"Unnecessary 'else' after 'return'.","line":4,"column":12,"nodeType":"BlockStatement","messageId":"unexpected","endLine":6,"endColumn":6,"fix":{"range":[0,94],"text":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}"}},{"ruleId":"indent","severity":1,"message":"Expected indentation of 8 spaces but found 6.","line":5,"column":1,"nodeType":"Keyword","messageId":"wrongIndentation","endLine":5,"endColumn":7,"fix":{"range":[74,80],"text":" "}},{"ruleId":"consistent-return","severity":2,"message":"Function 'addOne' expected a return value.","line":5,"column":7,"nodeType":"ReturnStatement","messageId":"missingReturnValue","endLine":5,"endColumn":13},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":13,"nodeType":"ReturnStatement","messageId":"missingSemi","endLine":6,"endColumn":1,"fix":{"range":[86,86],"text":";"}},{"ruleId":"no-extra-semi","severity":2,"message":"Unnecessary semicolon.","line":7,"column":2,"nodeType":"EmptyStatement","messageId":"unexpected","endLine":7,"endColumn":3,"fix":{"range":[93,95],"text":"}"}}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":2,"fixableWarningCount":4,"source":"function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};"}] +```json +[ + { + "filePath": "/var/lib/jenkins/workspace/Releases/eslint Release/eslint/fullOfProblems.js", + "messages": [ + { + "ruleId": "no-unused-vars", + "severity": 2, + "message": "'addOne' is defined but never used.", + "line": 1, + "column": 10, + "nodeType": "Identifier", + "messageId": "unusedVar", + "endLine": 1, + "endColumn": 16 + }, + { + "ruleId": "use-isnan", + "severity": 2, + "message": "Use the isNaN function to compare with NaN.", + "line": 2, + "column": 9, + "nodeType": "BinaryExpression", + "messageId": "comparisonWithNaN", + "endLine": 2, + "endColumn": 17 + }, + { + "ruleId": "space-unary-ops", + "severity": 2, + "message": "Unexpected space before unary operator '++'.", + "line": 3, + "column": 16, + "nodeType": "UpdateExpression", + "messageId": "unexpectedBefore", + "endLine": 3, + "endColumn": 20, + "fix": { + "range": [ + 57, + 58 + ], + "text": "" + } + }, + { + "ruleId": "semi", + "severity": 1, + "message": "Missing semicolon.", + "line": 3, + "column": 20, + "nodeType": "ReturnStatement", + "messageId": "missingSemi", + "endLine": 4, + "endColumn": 1, + "fix": { + "range": [ + 60, + 60 + ], + "text": ";" + } + }, + { + "ruleId": "no-else-return", + "severity": 1, + "message": "Unnecessary 'else' after 'return'.", + "line": 4, + "column": 12, + "nodeType": "BlockStatement", + "messageId": "unexpected", + "endLine": 6, + "endColumn": 6, + "fix": { + "range": [ + 0, + 94 + ], + "text": "function addOne(i) {\n if (i != NaN) {\n return i ++\n } \n return\n \n}" + } + }, + { + "ruleId": "indent", + "severity": 1, + "message": "Expected indentation of 8 spaces but found 6.", + "line": 5, + "column": 1, + "nodeType": "Keyword", + "messageId": "wrongIndentation", + "endLine": 5, + "endColumn": 7, + "fix": { + "range": [ + 74, + 80 + ], + "text": " " + } + }, + { + "ruleId": "consistent-return", + "severity": 2, + "message": "Function 'addOne' expected a return value.", + "line": 5, + "column": 7, + "nodeType": "ReturnStatement", + "messageId": "missingReturnValue", + "endLine": 5, + "endColumn": 13 + }, + { + "ruleId": "semi", + "severity": 1, + "message": "Missing semicolon.", + "line": 5, + "column": 13, + "nodeType": "ReturnStatement", + "messageId": "missingSemi", + "endLine": 6, + "endColumn": 1, + "fix": { + "range": [ + 86, + 86 + ], + "text": ";" + } + }, + { + "ruleId": "no-extra-semi", + "severity": 2, + "message": "Unnecessary semicolon.", + "line": 7, + "column": 2, + "nodeType": "EmptyStatement", + "messageId": "unexpected", + "endLine": 7, + "endColumn": 3, + "fix": { + "range": [ + 93, + 95 + ], + "text": "}" + } + } + ], + "suppressedMessages": [], + "errorCount": 5, + "fatalErrorCount": 0, + "warningCount": 4, + "fixableErrorCount": 2, + "fixableWarningCount": 4, + "source": "function addOne(i) {\n if (i != NaN) {\n return i ++\n } else {\n return\n }\n};" + } +] ``` ### junit @@ -143,7 +882,7 @@ Outputs results to format compatible with the [JUnit Jenkins plugin](https://plu Example output: -```text +```xml diff --git a/package.json b/package.json index 14f86b846cfc..45a42d1bf2c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.51.0", + "version": "8.52.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 994596b07f5ff20a615a4be1ea03e5fd59cdb84b Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Sun, 22 Oct 2023 18:56:52 +0200 Subject: [PATCH 231/248] ci: run tests in Node.js 21 (#17673) Run CI tests in Node 21 --- .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 7602b8380378..7bf368fdac75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node: [20.x, 19.x, 18.x, 17.x, 16.x, 14.x, 12.x, "12.22.0"] + node: [21.x, 20.x, 19.x, 18.x, 17.x, 16.x, 14.x, 12.x, "12.22.0"] include: - os: windows-latest node: "lts/*" From b329ea748dff45f11c7e218208244dc24fcb5c8f Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Tue, 24 Oct 2023 08:58:43 +0200 Subject: [PATCH 232/248] fix: add `;` after JSX nodes in `no-object-constructor` autofix (#17672) Fix `no-object-constructor` autofix after JSX nodes --- lib/rules/no-object-constructor.js | 4 ++-- tests/lib/rules/no-object-constructor.js | 28 ++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/rules/no-object-constructor.js b/lib/rules/no-object-constructor.js index 0659d1633816..e3ac20957348 100644 --- a/lib/rules/no-object-constructor.js +++ b/lib/rules/no-object-constructor.js @@ -35,10 +35,10 @@ const NODE_TYPES_BY_KEYWORD = { }; /* - * Before an opening parenthesis, `>` (for JSX), and postfix `++` and `--` always trigger ASI; + * Before an opening parenthesis, postfix `++` and `--` always trigger ASI; * the tokens `:`, `;`, `{` and `=>` don't expect a semicolon, as that would count as an empty statement. */ -const PUNCTUATORS = new Set([":", ";", ">", "{", "=>", "++", "--"]); +const PUNCTUATORS = new Set([":", ";", "{", "=>", "++", "--"]); /* * Statements that can contain an `ExpressionStatement` after a closing parenthesis. diff --git a/tests/lib/rules/no-object-constructor.js b/tests/lib/rules/no-object-constructor.js index 4d51f2a21d69..464a0779bc70 100644 --- a/tests/lib/rules/no-object-constructor.js +++ b/tests/lib/rules/no-object-constructor.js @@ -168,6 +168,20 @@ ruleTester.run("no-object-constructor", rule, { var foo = { bar: baz } Object() ` + }, + { + code: ` + + Object() + `, + parserOptions: { ecmaFeatures: { jsx: true } } + }, + { + code: ` + + Object() + `, + parserOptions: { ecmaFeatures: { jsx: true } } } ].map(props => ({ ...props, @@ -296,20 +310,6 @@ ruleTester.run("no-object-constructor", rule, { Object() ` }, - { - code: ` - - Object() - `, - parserOptions: { ecmaFeatures: { jsx: true } } - }, - { - code: ` - - Object() - `, - parserOptions: { ecmaFeatures: { jsx: true } } - }, { code: ` const foo = bar From db06a7ff7992a74368f03d1f21beb00df0407021 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 09:26:08 +0200 Subject: [PATCH 233/248] ci: bump actions/setup-node from 3 to 4 (#17676) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- .github/workflows/update-readme.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bf368fdac75..c2ee74329c5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 'lts/*' - name: Install Packages @@ -54,7 +54,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - name: Install Packages @@ -69,7 +69,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: '16' - name: Install Packages diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml index 43b783d3fcad..5e3fb97c48d3 100644 --- a/.github/workflows/update-readme.yml +++ b/.github/workflows/update-readme.yml @@ -14,7 +14,7 @@ jobs: token: ${{ secrets.WORKFLOW_PUSH_BOT_TOKEN }} - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 - name: Install npm packages run: npm install From 3aec1c55ba2c6d2833e1c0afe0a58f0cc6bbc0a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Tue, 24 Oct 2023 11:31:34 -0400 Subject: [PATCH 234/248] docs: explained rule fixers and suggestions (#17657) * docs: explained report fixers and suggestions * Apply suggestions from code review Co-authored-by: Nicholas C. Zakas * Post-suggestion fix: rule-categories.macro.html link --------- Co-authored-by: Nicholas C. Zakas --- .../components/rule-categories.macro.html | 2 +- docs/src/use/core-concepts.md | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/src/_includes/components/rule-categories.macro.html b/docs/src/_includes/components/rule-categories.macro.html index 193f6def64eb..f38d371049eb 100644 --- a/docs/src/_includes/components/rule-categories.macro.html +++ b/docs/src/_includes/components/rule-categories.macro.html @@ -21,7 +21,7 @@
๐Ÿ’ก hasSuggestions

- Some problems reported by this rule are manually fixable by editor suggestions + Some problems reported by this rule are manually fixable by editor suggestions

{%- endif -%} diff --git a/docs/src/use/core-concepts.md b/docs/src/use/core-concepts.md index b9dfbbbfa1c0..b60ad3b0b0ed 100644 --- a/docs/src/use/core-concepts.md +++ b/docs/src/use/core-concepts.md @@ -23,6 +23,23 @@ ESLint contains hundreds of built-in rules that you can use. You can also create 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](https://eslint.org/docs/latest/use/command-line-interface#--fix) and via editor extensions. + +Rules that may provide fixes are marked with ๐Ÿ”ง in [Rules](../rules/). + +### Rule Suggestions + +Rules may optionally provide suggestions in addition to or instead of providing fixes. Suggestions differ from fixes in two ways: + +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/). + ## 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. From 8651895ca7ae15e13d74c8be67d9eebd63a7ce1f Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Thu, 26 Oct 2023 22:50:36 +0200 Subject: [PATCH 235/248] docs: Fix tabs in rule examples (#17653) * Different ways to handle tabs * Option to remove Playground links * use tabs with `markdownlint-disable` comments * add missing `eslint` comments * Apply review suggestions * Remove comments from `no-mixed-spaces-and-tabs` --- docs/src/rules/indent-legacy.md | 11 +++++--- docs/src/rules/indent.md | 11 +++++--- docs/src/rules/max-len.md | 19 +++++++++----- docs/src/rules/no-mixed-spaces-and-tabs.md | 30 +++++++++++----------- docs/src/rules/no-tabs.md | 20 +++++++++++---- 5 files changed, 57 insertions(+), 34 deletions(-) diff --git a/docs/src/rules/indent-legacy.md b/docs/src/rules/indent-legacy.md index c3543133be4c..a8097b229324 100644 --- a/docs/src/rules/indent-legacy.md +++ b/docs/src/rules/indent-legacy.md @@ -140,20 +140,23 @@ function foo(d) { Examples of **correct** code for this rule with the `"tab"` option: + + ::: correct ```js /*eslint indent-legacy: ["error", "tab"]*/ if (a) { -/*tab*/b=c; -/*tab*/function foo(d) { -/*tab*//*tab*/e=f; -/*tab*/} + b=c; + function foo(d) { + e=f; + } } ``` ::: + ### SwitchCase diff --git a/docs/src/rules/indent.md b/docs/src/rules/indent.md index 72efad265683..ea700b240bb1 100644 --- a/docs/src/rules/indent.md +++ b/docs/src/rules/indent.md @@ -141,20 +141,23 @@ function foo(d) { Examples of **correct** code for this rule with the `"tab"` option: + + ::: correct ```js /*eslint indent: ["error", "tab"]*/ if (a) { -/*tab*/b=c; -/*tab*/function foo(d) { -/*tab*//*tab*/e=f; -/*tab*/} + b=c; + function foo(d) { + e=f; + } } ``` ::: + ### ignoredNodes diff --git a/docs/src/rules/max-len.md b/docs/src/rules/max-len.md index eabd3efc4c6c..fb3310269e37 100644 --- a/docs/src/rules/max-len.md +++ b/docs/src/rules/max-len.md @@ -69,30 +69,36 @@ var foo = { Examples of **incorrect** code for this rule with the default `{ "tabWidth": 4 }` option: + + ::: incorrect ```js /*eslint max-len: ["error", { "code": 80, "tabWidth": 4 }]*/ -\t \t var foo = { "bar": "This is a bar.", "baz": { "qux": "This is a qux" } }; + var foo = { "bar": "This is a bar.", "baz": { "qux": "This is a qux" } }; ``` ::: + Examples of **correct** code for this rule with the default `{ "tabWidth": 4 }` option: + + ::: correct ```js /*eslint max-len: ["error", { "code": 80, "tabWidth": 4 }]*/ -\t \t var foo = { -\t \t \t \t "bar": "This is a bar.", -\t \t \t \t "baz": { "qux": "This is a qux" } -\t \t }; + var foo = { + "bar": "This is a bar.", + "baz": { "qux": "This is a qux" } + }; ``` ::: + ### comments @@ -203,7 +209,8 @@ Examples of **correct** code for this rule with the `ignorePattern` option: ::: correct ```js -/*eslint max-len: ["error", { "ignorePattern": "^\\s*var\\s.+=\\s*require\\s*\\(" }]*/ +/*eslint max-len: +["error", { "ignorePattern": "^\\s*var\\s.+=\\s*require\\s*\\(" }]*/ var dep = require('really/really/really/really/really/really/really/really/long/module'); ``` diff --git a/docs/src/rules/no-mixed-spaces-and-tabs.md b/docs/src/rules/no-mixed-spaces-and-tabs.md index 29f82e2191f7..39c230b2dcaf 100644 --- a/docs/src/rules/no-mixed-spaces-and-tabs.md +++ b/docs/src/rules/no-mixed-spaces-and-tabs.md @@ -15,42 +15,42 @@ This rule disallows mixed spaces and tabs for indentation. Examples of **incorrect** code for this rule: + + ::: incorrect ```js /*eslint no-mixed-spaces-and-tabs: "error"*/ function add(x, y) { -// --->..return x + y; - - return x + y; + return x + y; } function main() { -// --->var x = 5, -// --->....y = 7; - - var x = 5, - y = 7; + var x = 5, + y = 7; } ``` ::: + Examples of **correct** code for this rule: + + ::: correct ```js /*eslint no-mixed-spaces-and-tabs: "error"*/ function add(x, y) { -// --->return x + y; - return x + y; + return x + y; } ``` ::: + ## Options @@ -62,18 +62,18 @@ This rule has a string option. Examples of **correct** code for this rule with the `"smart-tabs"` option: + + ::: correct ```js /*eslint no-mixed-spaces-and-tabs: ["error", "smart-tabs"]*/ function main() { -// --->var x = 5, -// --->....y = 7; - - var x = 5, - y = 7; + var x = 5, + y = 7; } ``` ::: + diff --git a/docs/src/rules/no-tabs.md b/docs/src/rules/no-tabs.md index 316db6203f3d..c105b6a8193b 100644 --- a/docs/src/rules/no-tabs.md +++ b/docs/src/rules/no-tabs.md @@ -12,26 +12,33 @@ This rule looks for tabs anywhere inside a file: code, comments or anything else Examples of **incorrect** code for this rule: + + ::: incorrect ```js -var a \t= 2; +/* eslint no-tabs: "error" */ + +var a = 2; /** -* \t\t it's a test function +* it's a test function */ function test(){} -var x = 1; // \t test +var x = 1; // test ``` ::: + Examples of **correct** code for this rule: ::: correct ```js +/* eslint no-tabs: "error" */ + var a = 2; /** @@ -54,19 +61,22 @@ This rule has an optional object option with the following properties: Examples of **correct** code for this rule with the `allowIndentationTabs: true` option: + + ::: correct ```js /* eslint no-tabs: ["error", { allowIndentationTabs: true }] */ function test() { -\tdoSomething(); + doSomething(); } -\t// comment with leading indentation tab + // comment with leading indentation tab ``` ::: + ## When Not To Use It From 4164b2ceec89726b18ea0b0e34fab05735d55a09 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Fri, 27 Oct 2023 08:06:57 +0000 Subject: [PATCH 236/248] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c910134a6f41..5379c319491c 100644 --- a/README.md +++ b/README.md @@ -294,7 +294,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Chrome Frameworks Fund Automattic

Gold Sponsors

Salesforce Airbnb

Silver Sponsors

Liftoff American Express

Bronze Sponsors

-

ThemeIsle Anagram Solver Icons8 Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

+

ThemeIsle Anagram Solver Icons8 Discord Transloadit Ignition HeroCoders

## Technology Sponsors From 1ad6257744d63281235fcc33288394b1d69b34ce Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 27 Oct 2023 13:36:58 +0200 Subject: [PATCH 237/248] fix: ensure that exit code for fatal errors is not overwritten (#17683) * fix: ensure that exit code for fatal errors is not overwritten * Add comments * update test --- bin/eslint.js | 26 +++++++++++++++++-- tests/bin/eslint.js | 18 +++++++++++++ tests/fixtures/bin/empty.js | 0 .../bin/eslint.config-promise-tick-throws.js | 22 ++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/bin/empty.js create mode 100644 tests/fixtures/bin/eslint.config-promise-tick-throws.js diff --git a/bin/eslint.js b/bin/eslint.js index 5c7972cc086e..eeb4647e70b1 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -97,9 +97,14 @@ function getErrorMessage(error) { * same message once. * @type {Set} */ - const displayedErrors = new Set(); +/** + * Tracks whether an unexpected error was caught + * @type {boolean} + */ +let hadFatalError = false; + /** * Catch and report unexpected error. * @param {any} error The thrown error object. @@ -107,6 +112,7 @@ const displayedErrors = new Set(); */ function onFatalError(error) { process.exitCode = 2; + hadFatalError = true; const { version } = require("../package.json"); const message = ` @@ -143,9 +149,25 @@ ${getErrorMessage(error)}`; } // Otherwise, call the CLI. - process.exitCode = await require("../lib/cli").execute( + const exitCode = await require("../lib/cli").execute( process.argv, process.argv.includes("--stdin") ? await readStdin() : null, true ); + + /* + * If an uncaught exception or unhandled rejection was detected in the meantime, + * keep the fatal exit code 2 that is already assigned to `process.exitCode`. + * Without this condition, exit code 2 (unsuccessful execution) could be overwritten with + * 1 (successful execution, lint problems found) or even 0 (successful execution, no lint problems found). + * This ensures that unexpected errors that seemingly don't affect the success + * of the execution will still cause a non-zero exit code, as it's a common + * practice and the default behavior of Node.js to exit with non-zero + * in case of an uncaught exception or unhandled rejection. + * + * Otherwise, assign the exit code returned from CLI. + */ + if (!hadFatalError) { + process.exitCode = exitCode; + } }()).catch(onFatalError); diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js index dca8955d0386..430f7112e962 100644 --- a/tests/bin/eslint.js +++ b/tests/bin/eslint.js @@ -387,6 +387,24 @@ describe("bin/eslint.js", () => { return Promise.all([exitCodeAssertion, outputAssertion]); }); + it("does not exit with zero when there is an error in the next tick", () => { + const config = path.join(__dirname, "../fixtures/bin/eslint.config-promise-tick-throws.js"); + const file = path.join(__dirname, "../fixtures/bin/empty.js"); + const child = runESLint(["--config", config, file]); + const exitCodeAssertion = assertExitCode(child, 2); + const outputAssertion = getOutput(child).then(output => { + + // ensure the expected error was printed + assert.include(output.stderr, "test_error_stack"); + + // ensure that linting the file did not cause an error + assert.notInclude(output.stderr, "empty.js"); + assert.notInclude(output.stdout, "empty.js"); + }); + + return Promise.all([exitCodeAssertion, outputAssertion]); + }); + // https://github.com/eslint/eslint/issues/17560 describe("does not print duplicate errors in the event of a crash", () => { diff --git a/tests/fixtures/bin/empty.js b/tests/fixtures/bin/empty.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/fixtures/bin/eslint.config-promise-tick-throws.js b/tests/fixtures/bin/eslint.config-promise-tick-throws.js new file mode 100644 index 000000000000..6ea53d253863 --- /dev/null +++ b/tests/fixtures/bin/eslint.config-promise-tick-throws.js @@ -0,0 +1,22 @@ +function throwError() { + const error = new Error(); + error.stack = "test_error_stack"; + throw error; +} + +process.nextTick(throwError); + +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function getConfig() { + await delay(100); + return []; +} + +/* + * Exporting the config as an initially unsettled Promise should ensure that + * the error in next tick will be thrown before any linting is done + */ +module.exports = getConfig(); From 4fc44c0b8c5dca466bffdfe01dfd80794d7762b7 Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Fri, 27 Oct 2023 20:30:34 +0530 Subject: [PATCH 238/248] docs: update twitter icon to new X icon (#17687) --- docs/src/_includes/components/social-icons.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/src/_includes/components/social-icons.html b/docs/src/_includes/components/social-icons.html index bb025af5b134..6f2b887e9490 100644 --- a/docs/src/_includes/components/social-icons.html +++ b/docs/src/_includes/components/social-icons.html @@ -3,11 +3,8 @@
  • - - + +
  • From c0b11ddb9f8aacc64c3933b9f278939aa7bea481 Mon Sep 17 00:00:00 2001 From: Yonathan Randolph Date: Sun, 29 Oct 2023 14:05:41 -0700 Subject: [PATCH 239/248] feat: Add suggestions for no-prototype-builtins (#17677) * feat: Add suggestion for no-prototype-builtins Suggest a fix e.g. a.hasOwnProperty(b) -> Object.prototype.hasOwnProperty.call(a, b). However, if the method call follows an optional chain, then make no suggestions. * Don't provide suggestion if Object is shadowed or not in globals * Add parentheses back for SequenceExpression * Give no suggestion if no-unsafe-optional-chaining --- lib/rules/no-prototype-builtins.js | 92 ++++++++++++++- tests/lib/rules/no-prototype-builtins.js | 136 ++++++++++++++++++++++- 2 files changed, 222 insertions(+), 6 deletions(-) diff --git a/lib/rules/no-prototype-builtins.js b/lib/rules/no-prototype-builtins.js index a7a57bc119e3..b61e585291a9 100644 --- a/lib/rules/no-prototype-builtins.js +++ b/lib/rules/no-prototype-builtins.js @@ -10,6 +10,37 @@ const astUtils = require("./utils/ast-utils"); +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Returns true if the node or any of the objects + * to the left of it in the member/call chain is optional. + * + * e.g. `a?.b`, `a?.b.c`, `a?.()`, `a()?.()` + * @param {ASTNode} node The expression to check + * @returns {boolean} `true` if there is a short-circuiting optional `?.` + * in the same option chain to the left of this call or member expression, + * or the node itself is an optional call or member `?.`. + */ +function isAfterOptional(node) { + let leftNode; + + if (node.type === "MemberExpression") { + leftNode = node.object; + } else if (node.type === "CallExpression") { + leftNode = node.callee; + } else { + return false; + } + if (node.optional) { + return true; + } + return isAfterOptional(leftNode); +} + + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -25,10 +56,13 @@ module.exports = { url: "https://eslint.org/docs/latest/rules/no-prototype-builtins" }, + hasSuggestions: true, + schema: [], messages: { - prototypeBuildIn: "Do not access Object.prototype method '{{prop}}' from target object." + prototypeBuildIn: "Do not access Object.prototype method '{{prop}}' from target object.", + callObjectPrototype: "Call Object.prototype.{{prop}} explicitly." } }, @@ -59,7 +93,61 @@ module.exports = { messageId: "prototypeBuildIn", loc: callee.property.loc, data: { prop: propName }, - node + node, + suggest: [ + { + messageId: "callObjectPrototype", + data: { prop: propName }, + fix(fixer) { + const sourceCode = context.sourceCode; + + /* + * A call after an optional chain (e.g. a?.b.hasOwnProperty(c)) + * must be fixed manually because the call can be short-circuited + */ + if (isAfterOptional(node)) { + return null; + } + + /* + * A call on a ChainExpression (e.g. (a?.hasOwnProperty)(c)) will trigger + * no-unsafe-optional-chaining which should be fixed before this suggestion + */ + if (node.callee.type === "ChainExpression") { + return null; + } + + const objectVariable = astUtils.getVariableByName(sourceCode.getScope(node), "Object"); + + /* + * We can't use Object if the global Object was shadowed, + * or Object does not exist in the global scope for some reason + */ + if (!objectVariable || objectVariable.scope.type !== "global" || objectVariable.defs.length > 0) { + return null; + } + + let objectText = sourceCode.getText(callee.object); + + if (astUtils.getPrecedence(callee.object) <= astUtils.getPrecedence({ type: "SequenceExpression" })) { + objectText = `(${objectText})`; + } + + const openParenToken = sourceCode.getTokenAfter( + node.callee, + astUtils.isOpeningParenToken + ); + const isEmptyParameters = node.arguments.length === 0; + const delim = isEmptyParameters ? "" : ", "; + const fixes = [ + fixer.replaceText(callee, `Object.prototype.${propName}.call`), + fixer.insertTextAfter(openParenToken, objectText + delim) + ]; + + return fixes; + } + } + ] }); } } diff --git a/tests/lib/rules/no-prototype-builtins.js b/tests/lib/rules/no-prototype-builtins.js index 6152e8acf6e3..80755a6d878e 100644 --- a/tests/lib/rules/no-prototype-builtins.js +++ b/tests/lib/rules/no-prototype-builtins.js @@ -61,6 +61,12 @@ ruleTester.run("no-prototype-builtins", rule, { endColumn: 19, messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" }, + suggestions: [ + { + messageId: "callObjectPrototype", + output: "Object.prototype.hasOwnProperty.call(foo, 'bar')" + } + ], type: "CallExpression" }] }, @@ -73,6 +79,12 @@ ruleTester.run("no-prototype-builtins", rule, { endColumn: 18, messageId: "prototypeBuildIn", data: { prop: "isPrototypeOf" }, + suggestions: [ + { + messageId: "callObjectPrototype", + output: "Object.prototype.isPrototypeOf.call(foo, 'bar')" + } + ], type: "CallExpression" }] }, @@ -84,6 +96,12 @@ ruleTester.run("no-prototype-builtins", rule, { endLine: 1, endColumn: 25, messageId: "prototypeBuildIn", + suggestions: [ + { + messageId: "callObjectPrototype", + output: "Object.prototype.propertyIsEnumerable.call(foo, 'bar')" + } + ], data: { prop: "propertyIsEnumerable" } }] }, @@ -96,6 +114,12 @@ ruleTester.run("no-prototype-builtins", rule, { endColumn: 23, messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" }, + suggestions: [ + { + messageId: "callObjectPrototype", + output: "Object.prototype.hasOwnProperty.call(foo.bar, 'bar')" + } + ], type: "CallExpression" }] }, @@ -108,6 +132,12 @@ ruleTester.run("no-prototype-builtins", rule, { endColumn: 26, messageId: "prototypeBuildIn", data: { prop: "isPrototypeOf" }, + suggestions: [ + { + messageId: "callObjectPrototype", + output: "Object.prototype.isPrototypeOf.call(foo.bar.baz, 'bar')" + } + ], type: "CallExpression" }] }, @@ -120,6 +150,12 @@ ruleTester.run("no-prototype-builtins", rule, { endColumn: 21, messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" }, + suggestions: [ + { + messageId: "callObjectPrototype", + output: "Object.prototype.hasOwnProperty.call(foo, 'bar')" + } + ], type: "CallExpression" }] }, @@ -133,6 +169,12 @@ ruleTester.run("no-prototype-builtins", rule, { endColumn: 20, messageId: "prototypeBuildIn", data: { prop: "isPrototypeOf" }, + suggestions: [ + { + messageId: "callObjectPrototype", + output: "Object.prototype.isPrototypeOf.call(foo, 'bar').baz" + } + ], type: "CallExpression" }] }, @@ -145,30 +187,116 @@ ruleTester.run("no-prototype-builtins", rule, { endColumn: 31, messageId: "prototypeBuildIn", data: { prop: "propertyIsEnumerable" }, + suggestions: [ + { + messageId: "callObjectPrototype", + output: String.raw`Object.prototype.propertyIsEnumerable.call(foo.bar, 'baz')` + } + ], type: "CallExpression" }] }, + { + + // Can't suggest Object.prototype when Object is shadowed + code: "(function(Object) {return foo.hasOwnProperty('bar');})", + errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" }, suggestions: [] }] + }, + { + code: "foo.hasOwnProperty('bar')", + globals: { + Object: "off" + }, + errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" }, suggestions: [] }], + name: "Can't suggest Object.prototype when there is no Object global variable" + }, // Optional chaining { code: "foo?.hasOwnProperty('bar')", parserOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" } }] + errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" }, suggestions: [] }] + }, + { + code: "foo?.bar.hasOwnProperty('baz')", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" }, suggestions: [] }] + }, + { + code: "foo.hasOwnProperty?.('bar')", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" }, suggestions: [] }] + }, + { + + /* + * If hasOwnProperty is part of a ChainExpresion + * and the optional part is before it, then don't suggest the fix + */ + code: "foo?.hasOwnProperty('bar').baz", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" }, suggestions: [] }] }, { + + /* + * If hasOwnProperty is part of a ChainExpresion + * but the optional part is after it, then the fix is safe + */ + code: "foo.hasOwnProperty('bar')?.baz", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ + messageId: "prototypeBuildIn", + data: { prop: "hasOwnProperty" }, + suggestions: [ + { + messageId: "callObjectPrototype", + output: "Object.prototype.hasOwnProperty.call(foo, 'bar')?.baz" + } + ] + }] + }, + { + + code: "(a,b).hasOwnProperty('bar')", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ + messageId: "prototypeBuildIn", + data: { prop: "hasOwnProperty" }, + suggestions: [ + + // Make sure the SequenceExpression has parentheses before other arguments + { + messageId: "callObjectPrototype", + output: "Object.prototype.hasOwnProperty.call((a,b), 'bar')" + } + ] + }] + }, + { + + // No suggestion where no-unsafe-optional-chaining is reported on the call code: "(foo?.hasOwnProperty)('bar')", parserOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" } }] + errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" }, suggestions: [] }] + + }, + { + code: "(foo?.hasOwnProperty)?.('bar')", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" }, suggestions: [] }] }, { code: "foo?.['hasOwnProperty']('bar')", parserOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" } }] + errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" }, suggestions: [] }] }, { + + // No suggestion where no-unsafe-optional-chaining is reported on the call code: "(foo?.[`hasOwnProperty`])('bar')", parserOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" } }] + errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" }, suggestions: [] }] } ] }); From 485ec7d08ed2040c292f52bf9b9152f6c8ef4809 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 1 Nov 2023 16:44:11 +0100 Subject: [PATCH 240/248] test: fix ESLint tests for caching (#17699) --- tests/lib/eslint/eslint.js | 391 +++++++++++++++++++------------- tests/lib/eslint/flat-eslint.js | 7 +- 2 files changed, 239 insertions(+), 159 deletions(-) diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 74abaf42e5d9..60480b7cc141 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -2321,25 +2321,22 @@ describe("ESLint", () => { } } - /** - * helper method to delete the cache files created during testing - * @returns {void} - */ - function deleteCache() { - doDelete(path.resolve(".eslintcache")); - doDelete(path.resolve(".cache/custom-cache")); - } + let cacheFilePath; beforeEach(() => { - deleteCache(); + cacheFilePath = null; }); afterEach(() => { sinon.restore(); - deleteCache(); + if (cacheFilePath) { + doDelete(cacheFilePath); + } }); - describe("when the cacheFile is a directory or looks like a directory", () => { + describe("when cacheLocation is a directory or looks like a directory", () => { + + const cwd = getFixturePath(); /** * helper method to delete the cache files created during testing @@ -2347,7 +2344,21 @@ describe("ESLint", () => { */ function deleteCacheDir() { try { - fs.unlinkSync("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory"); + + /* + * `fs.rmdir(path, { recursive: true })` is deprecated and will be removed. + * Use `fs.rm(path, { recursive: true })` instead. + * When supporting Node.js 14.14.0+, the compatibility condition can be removed for `fs.rmdir`. + */ + // eslint-disable-next-line n/no-unsupported-features/node-builtins -- just checking if it exists + if (typeof fs.rm === "function") { + + // eslint-disable-next-line n/no-unsupported-features/node-builtins -- conditionally used + fs.rmSync(path.resolve(cwd, "tmp/.cacheFileDir/"), { recursive: true, force: true }); + } else { + fs.rmdirSync(path.resolve(cwd, "tmp/.cacheFileDir/"), { recursive: true, force: true }); + } + } catch { /* @@ -2364,11 +2375,12 @@ describe("ESLint", () => { deleteCacheDir(); }); - it("should create the cache file inside the provided directory", async () => { - assert(!shell.test("-d", path.resolve("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory")), "the cache for eslint does not exist"); + it("should create the directory and the cache file inside it when cacheLocation ends with a slash", async () => { + assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); eslint = new ESLint({ useEslintrc: false, + cwd, // specifying cache true the cache will be created cache: true, @@ -2386,42 +2398,71 @@ describe("ESLint", () => { await eslint.lintFiles([file]); - assert(shell.test("-f", path.resolve(`./tmp/.cacheFileDir/.cache_${hash(process.cwd())}`)), "the cache for eslint was created"); - - sinon.restore(); + assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); }); - }); - it("should create the cache file inside the provided directory using the cacheLocation option", async () => { - assert(!shell.test("-d", path.resolve("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory")), "the cache for eslint does not exist"); + it("should create the cache file inside existing cacheLocation directory when cacheLocation ends with a slash", async () => { + assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); - eslint = new ESLint({ - useEslintrc: false, + fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { recursive: true }); - // specifying cache true the cache will be created - cache: true, - cacheLocation: "./tmp/.cacheFileDir/", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"], - ignore: false + eslint = new ESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: "./tmp/.cacheFileDir/", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + ignore: false + }); + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); }); - const file = getFixturePath("cache/src", "test-file.js"); - await eslint.lintFiles([file]); + it("should create the cache file inside existing cacheLocation directory when cacheLocation doesn't end with a path separator", async () => { + assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); - assert(shell.test("-f", path.resolve(`./tmp/.cacheFileDir/.cache_${hash(process.cwd())}`)), "the cache for eslint was created"); + fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { recursive: true }); - sinon.restore(); + eslint = new ESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: "./tmp/.cacheFileDir", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + ignore: false + }); + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); + }); }); it("should create the cache file inside cwd when no cacheLocation provided", async () => { const cwd = path.resolve(getFixturePath("cli-engine")); + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + eslint = new ESLint({ useEslintrc: false, cache: true, @@ -2438,14 +2479,19 @@ describe("ESLint", () => { await eslint.lintFiles([file]); - assert(shell.test("-f", path.resolve(cwd, ".eslintcache")), "the cache for eslint was created at provided cwd"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created at provided cwd"); }); it("should invalidate the cache if the configuration changed between executions", async () => { - assert(!shell.test("-f", path.resolve(".eslintcache")), "the cache for eslint does not exist"); + const cwd = getFixturePath("cache/src"); + + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new ESLint({ useEslintrc: false, + cwd, // specifying cache true the cache will be created cache: true, @@ -2467,16 +2513,17 @@ describe("ESLint", () => { const results = await eslint.lintFiles([file]); for (const { errorCount, warningCount } of results) { - assert.strictEqual(errorCount + warningCount, 0, "the file passed without errors or warnings"); + assert.strictEqual(errorCount + warningCount, 0, "the file should have passed linting without errors or warnings"); } - assert.strictEqual(spy.getCall(0).args[0], file, "the module read the file because is considered changed"); - assert(shell.test("-f", path.resolve(".eslintcache")), "the cache for eslint was created"); + assert(spy.calledWith(file), "ESLint should have read the file because there was no cache file"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); // destroy the spy sinon.restore(); eslint = new ESLint({ useEslintrc: false, + cwd, // specifying cache true the cache will be created cache: true, @@ -2493,18 +2540,24 @@ describe("ESLint", () => { // create a new spy spy = sinon.spy(fs, "readFileSync"); - const [cachedResult] = await eslint.lintFiles([file]); + const [newResult] = await eslint.lintFiles([file]); - assert.strictEqual(spy.getCall(0).args[0], file, "the module read the file because is considered changed because the config changed"); - assert.strictEqual(cachedResult.errorCount, 1, "since configuration changed the cache was not used an one error was reported"); - assert(shell.test("-f", path.resolve(".eslintcache")), "the cache for eslint was created"); + assert(spy.calledWith(file), "ESLint should have read the file again because it's considered changed because the config changed"); + assert.strictEqual(newResult.errorCount, 1, "since configuration changed the cache should have not been used and one error should have been reported"); + assert.strictEqual(newResult.messages[0].ruleId, "no-console"); + assert(shell.test("-f", cacheFilePath), "The cache for ESLint should still exist"); }); it("should remember the files from a previous run and do not operate on them if not changed", async () => { - assert(!shell.test("-f", path.resolve(".eslintcache")), "the cache for eslint does not exist"); + const cwd = getFixturePath("cache/src"); + + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new ESLint({ useEslintrc: false, + cwd, // specifying cache true the cache will be created cache: true, @@ -2526,14 +2579,15 @@ describe("ESLint", () => { const result = await eslint.lintFiles([file]); - assert.strictEqual(spy.getCall(0).args[0], file, "the module read the file because is considered changed"); - assert(shell.test("-f", path.resolve(".eslintcache")), "the cache for eslint was created"); + assert(spy.calledWith(file), "ESLint should have read the file because there was no cache file"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); // destroy the spy sinon.restore(); eslint = new ESLint({ useEslintrc: false, + cwd, // specifying cache true the cache will be created cache: true, @@ -2552,20 +2606,23 @@ describe("ESLint", () => { const cachedResult = await eslint.lintFiles([file]); - assert.deepStrictEqual(result, cachedResult, "the result is the same regardless of using cache or not"); + assert.deepStrictEqual(result, cachedResult, "the result should have been the same"); // assert the file was not processed because the cache was used - assert(!spy.calledWith(file), "the file was not loaded because it used the cache"); + assert(!spy.calledWith(file), "the file should not have been reloaded"); }); - it("should remember the files from a previous run and do not operate on then if not changed", async () => { - const cacheLocation = getFixturePath(".eslintcache"); + it("when `cacheLocation` is specified, should create the cache file with `cache:true` and then delete it with `cache:false`", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + const eslintOptions = { useEslintrc: false, // specifying cache true the cache will be created cache: true, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2576,8 +2633,6 @@ describe("ESLint", () => { cwd: path.join(fixtureDir, "..") }; - assert(!shell.test("-f", cacheLocation), "the cache for eslint does not exist"); - eslint = new ESLint(eslintOptions); let file = getFixturePath("cache/src", "test-file.js"); @@ -2586,20 +2641,20 @@ describe("ESLint", () => { await eslint.lintFiles([file]); - assert(shell.test("-f", cacheLocation), "the cache for eslint was created"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); eslintOptions.cache = false; eslint = new ESLint(eslintOptions); await eslint.lintFiles([file]); - assert(!shell.test("-f", cacheLocation), "the cache for eslint was deleted since last run did not used the cache"); + assert(!shell.test("-f", cacheFilePath), "the cache for eslint should have been deleted since last run did not use the cache"); }); - it("should store in the cache a file that failed the test", async () => { - const cacheLocation = getFixturePath(".eslintcache"); - - assert(!shell.test("-f", cacheLocation), "the cache for eslint does not exist"); + it("should store in the cache a file that has lint messages and a file that doesn't have lint messages", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), @@ -2607,7 +2662,7 @@ describe("ESLint", () => { // specifying cache true the cache will be created cache: true, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2619,22 +2674,27 @@ describe("ESLint", () => { const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); const result = await eslint.lintFiles([badFile, goodFile]); + const [badFileResult, goodFileResult] = result; + + assert.notStrictEqual(badFileResult.errorCount + badFileResult.warningCount, 0, "the bad file should have some lint errors or warnings"); + assert.strictEqual(goodFileResult.errorCount + badFileResult.warningCount, 0, "the good file should have passed linting without errors or warnings"); - assert(shell.test("-f", cacheLocation), "the cache for eslint was created"); - const fileCache = fCache.createFromFile(cacheLocation); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); + + const fileCache = fCache.createFromFile(cacheFilePath); const { cache } = fileCache; - assert.strictEqual(typeof cache.getKey(goodFile), "object", "the entry for the good file is in the cache"); - assert.strictEqual(typeof cache.getKey(badFile), "object", "the entry for the bad file is in the cache"); + assert.strictEqual(typeof cache.getKey(goodFile), "object", "the entry for the good file should have been in the cache"); + assert.strictEqual(typeof cache.getKey(badFile), "object", "the entry for the bad file should have been in the cache"); const cachedResult = await eslint.lintFiles([badFile, goodFile]); - assert.deepStrictEqual(result, cachedResult, "result is the same with or without cache"); + assert.deepStrictEqual(result, cachedResult, "result should be the same with or without cache"); }); it("should not contain in the cache a file that was deleted", async () => { - const cacheLocation = getFixturePath(".eslintcache"); - - doDelete(cacheLocation); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), @@ -2642,7 +2702,7 @@ describe("ESLint", () => { // specifying cache true the cache will be created cache: true, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2656,10 +2716,10 @@ describe("ESLint", () => { const toBeDeletedFile = fs.realpathSync(getFixturePath("cache/src", "file-to-delete.js")); await eslint.lintFiles([badFile, goodFile, toBeDeletedFile]); - const fileCache = fCache.createFromFile(cacheLocation); + const fileCache = fCache.createFromFile(cacheFilePath); let { cache } = fileCache; - assert.strictEqual(typeof cache.getKey(toBeDeletedFile), "object", "the entry for the file to be deleted is in the cache"); + assert.strictEqual(typeof cache.getKey(toBeDeletedFile), "object", "the entry for the file to be deleted should have been in the cache"); // delete the file from the file system fs.unlinkSync(toBeDeletedFile); @@ -2670,15 +2730,19 @@ describe("ESLint", () => { */ await eslint.lintFiles([badFile, goodFile]); - cache = JSON.parse(fs.readFileSync(cacheLocation)); + cache = JSON.parse(fs.readFileSync(cacheFilePath)); - assert.strictEqual(typeof cache[toBeDeletedFile], "undefined", "the entry for the file to be deleted is not in the cache"); + assert.strictEqual(typeof cache[0][toBeDeletedFile], "undefined", "the entry for the file to be deleted should not have been in the cache"); + + // make sure that the previos assertion checks the right place + assert.notStrictEqual(typeof cache[0][badFile], "undefined", "the entry for the bad file should have been in the cache"); + assert.notStrictEqual(typeof cache[0][goodFile], "undefined", "the entry for the good file should have been in the cache"); }); it("should contain files that were not visited in the cache provided they still exist", async () => { - const cacheLocation = getFixturePath(".eslintcache"); - - doDelete(cacheLocation); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), @@ -2686,7 +2750,7 @@ describe("ESLint", () => { // specifying cache true the cache will be created cache: true, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2701,10 +2765,10 @@ describe("ESLint", () => { await eslint.lintFiles([badFile, goodFile, testFile2]); - let fileCache = fCache.createFromFile(cacheLocation); + let fileCache = fCache.createFromFile(cacheFilePath); let { cache } = fileCache; - assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 is in the cache"); + assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 should have been in the cache"); /* * we pass a different set of files minus test-file2 @@ -2713,19 +2777,23 @@ describe("ESLint", () => { */ await eslint.lintFiles([badFile, goodFile]); - fileCache = fCache.createFromFile(cacheLocation); + fileCache = fCache.createFromFile(cacheFilePath); cache = fileCache.cache; - assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 is in the cache"); + assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 should have been in the cache"); }); it("should not delete cache when executing on text", async () => { - const cacheLocation = getFixturePath(".eslintcache"); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2735,20 +2803,24 @@ describe("ESLint", () => { extensions: ["js"] }); - assert(shell.test("-f", cacheLocation), "the cache for eslint exists"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); await eslint.lintText("var foo = 'bar';"); - assert(shell.test("-f", cacheLocation), "the cache for eslint still exists"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); }); it("should not delete cache when executing on text with a provided filename", async () => { - const cacheLocation = getFixturePath(".eslintcache"); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2758,21 +2830,25 @@ describe("ESLint", () => { extensions: ["js"] }); - assert(shell.test("-f", cacheLocation), "the cache for eslint exists"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); await eslint.lintText("var bar = foo;", { filePath: "fixtures/passing.js" }); - assert(shell.test("-f", cacheLocation), "the cache for eslint still exists"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); }); it("should not delete cache when executing on files with --cache flag", async () => { - const cacheLocation = getFixturePath(".eslintcache"); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + fs.writeFileSync(cacheFilePath, ""); eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, cache: true, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2783,20 +2859,24 @@ describe("ESLint", () => { }); const file = getFixturePath("cli-engine", "console.js"); - assert(shell.test("-f", cacheLocation), "the cache for eslint exists"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); await eslint.lintFiles([file]); - assert(shell.test("-f", cacheLocation), "the cache for eslint still exists"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); }); it("should delete cache when executing on files without --cache flag", async () => { - const cacheLocation = getFixturePath(".eslintcache"); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, - cacheLocation, + cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, @@ -2807,58 +2887,57 @@ describe("ESLint", () => { }); const file = getFixturePath("cli-engine", "console.js"); - assert(shell.test("-f", cacheLocation), "the cache for eslint exists"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); await eslint.lintFiles([file]); - assert(!shell.test("-f", cacheLocation), "the cache for eslint has been deleted"); + assert(!shell.test("-f", cacheFilePath), "the cache for eslint should have been deleted"); }); - describe("cacheFile", () => { - it("should use the specified cache file", async () => { - const customCacheFile = path.resolve(".cache/custom-cache"); + it("should use the specified cache file", async () => { + cacheFilePath = path.resolve(".cache/custom-cache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - assert(!shell.test("-f", customCacheFile), "the cache for eslint does not exist"); + eslint = new ESLint({ + useEslintrc: false, - eslint = new ESLint({ - useEslintrc: false, + // specify a custom cache file + cacheLocation: cacheFilePath, - // specify a custom cache file - cacheLocation: customCacheFile, + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"], + cwd: path.join(fixtureDir, "..") + }); + const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); + const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); + const result = await eslint.lintFiles([badFile, goodFile]); - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"], - cwd: path.join(fixtureDir, "..") - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const result = await eslint.lintFiles([badFile, goodFile]); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - assert(shell.test("-f", customCacheFile), "the cache for eslint was created"); - const fileCache = fCache.createFromFile(customCacheFile); - const { cache } = fileCache; + const fileCache = fCache.createFromFile(cacheFilePath); + const { cache } = fileCache; - assert(typeof cache.getKey(goodFile) === "object", "the entry for the good file is in the cache"); + assert(typeof cache.getKey(goodFile) === "object", "the entry for the good file should have been in the cache"); + assert(typeof cache.getKey(badFile) === "object", "the entry for the bad file should have been in the cache"); - assert(typeof cache.getKey(badFile) === "object", "the entry for the bad file is in the cache"); - const cachedResult = await eslint.lintFiles([badFile, goodFile]); + const cachedResult = await eslint.lintFiles([badFile, goodFile]); - assert.deepStrictEqual(result, cachedResult, "result is the same with or without cache"); - }); + assert.deepStrictEqual(result, cachedResult, "result should be the same with or without cache"); }); describe("cacheStrategy", () => { it("should detect changes using a file's modification time when set to 'metadata'", async () => { - const cacheLocation = getFixturePath(".eslintcache"); - - doDelete(cacheLocation); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), @@ -2866,7 +2945,7 @@ describe("ESLint", () => { // specifying cache true the cache will be created cache: true, - cacheLocation, + cacheLocation: cacheFilePath, cacheStrategy: "metadata", overrideConfig: { rules: { @@ -2880,24 +2959,24 @@ describe("ESLint", () => { const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); await eslint.lintFiles([badFile, goodFile]); - let fileCache = fCache.createFromFile(cacheLocation); + let fileCache = fCache.createFromFile(cacheFilePath); const entries = fileCache.normalizeEntries([badFile, goodFile]); entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} is initially unchanged`); + assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); }); // this should result in a changed entry shell.touch(goodFile); - fileCache = fCache.createFromFile(cacheLocation); - assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} is unchanged`); - assert(fileCache.getFileDescriptor(goodFile).changed === true, `the entry for ${goodFile} is changed`); + fileCache = fCache.createFromFile(cacheFilePath); + assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} should have been unchanged`); + assert(fileCache.getFileDescriptor(goodFile).changed === true, `the entry for ${goodFile} should have been changed`); }); it("should not detect changes using a file's modification time when set to 'content'", async () => { - const cacheLocation = getFixturePath(".eslintcache"); - - doDelete(cacheLocation); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), @@ -2905,7 +2984,7 @@ describe("ESLint", () => { // specifying cache true the cache will be created cache: true, - cacheLocation, + cacheLocation: cacheFilePath, cacheStrategy: "content", overrideConfig: { rules: { @@ -2919,26 +2998,26 @@ describe("ESLint", () => { const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); await eslint.lintFiles([badFile, goodFile]); - let fileCache = fCache.createFromFile(cacheLocation, true); + let fileCache = fCache.createFromFile(cacheFilePath, true); let entries = fileCache.normalizeEntries([badFile, goodFile]); entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} is initially unchanged`); + assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); }); // this should NOT result in a changed entry shell.touch(goodFile); - fileCache = fCache.createFromFile(cacheLocation, true); + fileCache = fCache.createFromFile(cacheFilePath, true); entries = fileCache.normalizeEntries([badFile, goodFile]); entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} remains unchanged`); + assert(entry.changed === false, `the entry for ${entry.key} should have remained unchanged`); }); }); it("should detect changes using a file's contents when set to 'content'", async () => { - const cacheLocation = getFixturePath(".eslintcache"); - - doDelete(cacheLocation); + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), @@ -2946,7 +3025,7 @@ describe("ESLint", () => { // specifying cache true the cache will be created cache: true, - cacheLocation, + cacheLocation: cacheFilePath, cacheStrategy: "content", overrideConfig: { rules: { @@ -2963,18 +3042,18 @@ describe("ESLint", () => { shell.cp(goodFile, goodFileCopy); await eslint.lintFiles([badFile, goodFileCopy]); - let fileCache = fCache.createFromFile(cacheLocation, true); + let fileCache = fCache.createFromFile(cacheFilePath, true); const entries = fileCache.normalizeEntries([badFile, goodFileCopy]); entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} is initially unchanged`); + assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); }); // this should result in a changed entry shell.sed("-i", "abc", "xzy", goodFileCopy); - fileCache = fCache.createFromFile(cacheLocation, true); - assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} is unchanged`); - assert(fileCache.getFileDescriptor(goodFileCopy).changed === true, `the entry for ${goodFileCopy} is changed`); + fileCache = fCache.createFromFile(cacheFilePath, true); + assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} should have been unchanged`); + assert(fileCache.getFileDescriptor(goodFileCopy).changed === true, `the entry for ${goodFileCopy} should have been changed`); }); }); }); diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index 8fdb96ca7bcc..5cfeca351e70 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -2244,7 +2244,8 @@ describe("FlatESLint", () => { * Use `fs.rm(path, { recursive: true })` instead. * When supporting Node.js 14.14.0+, the compatibility condition can be removed for `fs.rmdir`. */ - if (typeof fsp.rm === "function") { + // eslint-disable-next-line n/no-unsupported-features/node-builtins -- just checking if it exists + if (typeof fs.rm === "function") { // eslint-disable-next-line n/no-unsupported-features/node-builtins -- conditionally used fs.rmSync(path.resolve(cwd, "tmp/.cacheFileDir/"), { recursive: true, force: true }); @@ -2296,7 +2297,7 @@ describe("FlatESLint", () => { it("should create the cache file inside existing cacheLocation directory when cacheLocation ends with a slash", async () => { assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); - fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/")); + fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { recursive: true }); eslint = new FlatESLint({ overrideConfigFile: true, @@ -2323,7 +2324,7 @@ describe("FlatESLint", () => { it("should create the cache file inside existing cacheLocation directory when cacheLocation doesn't end with a path separator", async () => { assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); - fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/")); + fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { recursive: true }); eslint = new FlatESLint({ overrideConfigFile: true, From 528e1c00dc2aa8636e5b706c4270dc655cfa17e3 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 3 Nov 2023 11:12:29 -0400 Subject: [PATCH 241/248] feat: Deprecate formatting rules (#17696) * feat: Deprecate formatting rules Fixes #17522 * Add deprecation notice to rule files * Fix tests * Update eslint:all test * Add deprecation notice to docs * Clarify error message for rule check * Update eslint:all test --- Makefile.js | 2 +- docs/src/rules/array-bracket-newline.md | 2 +- docs/src/rules/array-bracket-spacing.md | 2 +- docs/src/rules/array-element-newline.md | 2 +- docs/src/rules/arrow-parens.md | 2 +- docs/src/rules/arrow-spacing.md | 2 +- docs/src/rules/block-spacing.md | 2 +- docs/src/rules/brace-style.md | 2 +- docs/src/rules/comma-dangle.md | 2 +- docs/src/rules/comma-spacing.md | 2 +- docs/src/rules/comma-style.md | 2 +- docs/src/rules/computed-property-spacing.md | 2 +- docs/src/rules/dot-location.md | 2 +- docs/src/rules/eol-last.md | 2 +- docs/src/rules/func-call-spacing.md | 2 +- .../rules/function-call-argument-newline.md | 2 +- docs/src/rules/function-paren-newline.md | 2 +- docs/src/rules/generator-star-spacing.md | 2 +- docs/src/rules/implicit-arrow-linebreak.md | 2 +- docs/src/rules/indent.md | 2 +- docs/src/rules/jsx-quotes.md | 2 +- docs/src/rules/key-spacing.md | 2 +- docs/src/rules/keyword-spacing.md | 2 +- docs/src/rules/linebreak-style.md | 2 +- docs/src/rules/lines-around-comment.md | 2 +- docs/src/rules/lines-between-class-members.md | 2 +- docs/src/rules/max-len.md | 1 + docs/src/rules/max-statements-per-line.md | 1 + docs/src/rules/multiline-ternary.md | 2 +- docs/src/rules/new-parens.md | 2 +- docs/src/rules/newline-per-chained-call.md | 2 +- docs/src/rules/no-confusing-arrow.md | 2 +- docs/src/rules/no-extra-parens.md | 2 +- docs/src/rules/no-extra-semi.md | 4 +- docs/src/rules/no-floating-decimal.md | 2 +- docs/src/rules/no-mixed-operators.md | 1 + docs/src/rules/no-mixed-spaces-and-tabs.md | 2 +- docs/src/rules/no-multi-spaces.md | 2 +- docs/src/rules/no-multiple-empty-lines.md | 2 +- docs/src/rules/no-tabs.md | 1 + docs/src/rules/no-trailing-spaces.md | 2 +- .../rules/no-whitespace-before-property.md | 2 +- .../rules/nonblock-statement-body-position.md | 2 +- docs/src/rules/object-curly-newline.md | 2 +- docs/src/rules/object-curly-spacing.md | 2 +- docs/src/rules/object-property-newline.md | 2 +- .../src/rules/one-var-declaration-per-line.md | 2 +- docs/src/rules/operator-linebreak.md | 2 +- docs/src/rules/padded-blocks.md | 2 +- .../rules/padding-line-between-statements.md | 2 +- docs/src/rules/quote-props.md | 2 +- docs/src/rules/quotes.md | 2 +- docs/src/rules/rest-spread-spacing.md | 2 +- docs/src/rules/semi-spacing.md | 2 +- docs/src/rules/semi-style.md | 2 +- docs/src/rules/semi.md | 2 +- docs/src/rules/space-before-blocks.md | 2 +- docs/src/rules/space-before-function-paren.md | 2 +- docs/src/rules/space-in-parens.md | 2 +- docs/src/rules/space-infix-ops.md | 2 +- docs/src/rules/space-unary-ops.md | 2 +- docs/src/rules/spaced-comment.md | 2 +- docs/src/rules/switch-colon-spacing.md | 2 +- docs/src/rules/template-curly-spacing.md | 2 +- docs/src/rules/template-tag-spacing.md | 2 +- docs/src/rules/wrap-iife.md | 2 +- docs/src/rules/wrap-regex.md | 2 +- docs/src/rules/yield-star-spacing.md | 2 +- lib/rules/array-bracket-newline.js | 3 + lib/rules/array-bracket-spacing.js | 3 + lib/rules/array-element-newline.js | 3 + lib/rules/arrow-parens.js | 3 + lib/rules/arrow-spacing.js | 3 + lib/rules/block-spacing.js | 3 + lib/rules/brace-style.js | 3 + lib/rules/comma-dangle.js | 3 + lib/rules/comma-spacing.js | 3 + lib/rules/comma-style.js | 3 + lib/rules/computed-property-spacing.js | 3 + lib/rules/dot-location.js | 3 + lib/rules/eol-last.js | 3 + lib/rules/func-call-spacing.js | 3 + lib/rules/function-call-argument-newline.js | 3 + lib/rules/function-paren-newline.js | 3 + lib/rules/generator-star-spacing.js | 3 + lib/rules/implicit-arrow-linebreak.js | 3 + lib/rules/indent.js | 3 + lib/rules/jsx-quotes.js | 3 + lib/rules/key-spacing.js | 3 + lib/rules/keyword-spacing.js | 3 + lib/rules/linebreak-style.js | 3 + lib/rules/lines-around-comment.js | 3 + lib/rules/lines-between-class-members.js | 3 + lib/rules/max-len.js | 3 + lib/rules/max-statements-per-line.js | 3 + lib/rules/multiline-ternary.js | 3 + lib/rules/new-parens.js | 3 + lib/rules/newline-per-chained-call.js | 3 + lib/rules/no-confusing-arrow.js | 3 + lib/rules/no-extra-parens.js | 3 + lib/rules/no-extra-semi.js | 3 + lib/rules/no-floating-decimal.js | 3 + lib/rules/no-mixed-operators.js | 3 + lib/rules/no-mixed-spaces-and-tabs.js | 3 + lib/rules/no-multi-spaces.js | 3 + lib/rules/no-multiple-empty-lines.js | 3 + lib/rules/no-tabs.js | 3 + lib/rules/no-trailing-spaces.js | 3 + lib/rules/no-whitespace-before-property.js | 3 + lib/rules/nonblock-statement-body-position.js | 3 + lib/rules/object-curly-newline.js | 3 + lib/rules/object-curly-spacing.js | 3 + lib/rules/object-property-newline.js | 3 + lib/rules/one-var-declaration-per-line.js | 3 + lib/rules/operator-linebreak.js | 3 + lib/rules/padded-blocks.js | 3 + lib/rules/padding-line-between-statements.js | 3 + lib/rules/quote-props.js | 3 + lib/rules/quotes.js | 3 + lib/rules/rest-spread-spacing.js | 3 + lib/rules/semi-spacing.js | 3 + lib/rules/semi-style.js | 3 + lib/rules/semi.js | 3 + lib/rules/space-before-blocks.js | 3 + lib/rules/space-before-function-paren.js | 3 + lib/rules/space-in-parens.js | 3 + lib/rules/space-infix-ops.js | 3 + lib/rules/space-unary-ops.js | 3 + lib/rules/spaced-comment.js | 3 + lib/rules/switch-colon-spacing.js | 3 + lib/rules/template-curly-spacing.js | 3 + lib/rules/template-tag-spacing.js | 3 + lib/rules/wrap-iife.js | 3 + lib/rules/wrap-regex.js | 3 + lib/rules/yield-star-spacing.js | 3 + packages/js/src/configs/eslint-all.js | 67 --------------- tests/bin/eslint.js | 7 +- tests/conf/eslint-all.js | 2 +- tests/lib/cli-engine/cli-engine.js | 11 ++- tests/lib/eslint/eslint.js | 84 ++++++++++++++++--- tests/lib/eslint/flat-eslint.js | 82 +++++++++++++++--- 141 files changed, 428 insertions(+), 160 deletions(-) diff --git a/Makefile.js b/Makefile.js index 8a963388ebea..24accfb61030 100644 --- a/Makefile.js +++ b/Makefile.js @@ -826,7 +826,7 @@ target.checkRuleFiles = function() { // check deprecated if (ruleDef.meta.deprecated && !hasDeprecatedInfo()) { - console.error(`Missing deprecated information in ${basename} rule code or README.md. Please write @deprecated tag in code or ใ€ŒThis rule was deprecated in ESLint ...ใ€ in README.md.`); + console.error(`Missing deprecated information in ${basename} rule code or README.md. Please write @deprecated tag in code andใ€ŒThis rule was deprecated in ESLint ...ใ€ in README.md.`); errors++; } diff --git a/docs/src/rules/array-bracket-newline.md b/docs/src/rules/array-bracket-newline.md index 4ad7e27b5603..2ccb13dddf99 100644 --- a/docs/src/rules/array-bracket-newline.md +++ b/docs/src/rules/array-bracket-newline.md @@ -5,7 +5,7 @@ related_rules: - array-bracket-spacing --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). A number of style guides require or disallow line breaks inside of array brackets. diff --git a/docs/src/rules/array-bracket-spacing.md b/docs/src/rules/array-bracket-spacing.md index 5b6dd8a7cac9..0fdc83107471 100644 --- a/docs/src/rules/array-bracket-spacing.md +++ b/docs/src/rules/array-bracket-spacing.md @@ -7,7 +7,7 @@ related_rules: - computed-property-spacing --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). A number of style guides require or disallow spaces between array brackets and other tokens. This rule applies to both array literals and destructuring assignments (ECMAScript 6). diff --git a/docs/src/rules/array-element-newline.md b/docs/src/rules/array-element-newline.md index 2b34bd530145..0a8bc39b9bcc 100644 --- a/docs/src/rules/array-element-newline.md +++ b/docs/src/rules/array-element-newline.md @@ -12,7 +12,7 @@ related_rules: - brace-style --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). A number of style guides require or disallow line breaks between array elements. diff --git a/docs/src/rules/arrow-parens.md b/docs/src/rules/arrow-parens.md index 8b956c356648..4bfa819f87b4 100644 --- a/docs/src/rules/arrow-parens.md +++ b/docs/src/rules/arrow-parens.md @@ -5,7 +5,7 @@ further_reading: - https://github.com/airbnb/javascript#arrows--one-arg-parens --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Arrow functions can omit parentheses when they have exactly one parameter. In all other cases the parameter(s) must be wrapped in parentheses. This rule enforces the consistent use of parentheses in arrow functions. diff --git a/docs/src/rules/arrow-spacing.md b/docs/src/rules/arrow-spacing.md index fa5a42410a4e..3975e9f82cd6 100644 --- a/docs/src/rules/arrow-spacing.md +++ b/docs/src/rules/arrow-spacing.md @@ -3,7 +3,7 @@ title: arrow-spacing rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). This rule normalize style of spacing before/after an arrow function's arrow(`=>`). diff --git a/docs/src/rules/block-spacing.md b/docs/src/rules/block-spacing.md index 415fc7db0bdd..831916b55e75 100644 --- a/docs/src/rules/block-spacing.md +++ b/docs/src/rules/block-spacing.md @@ -6,7 +6,7 @@ related_rules: - brace-style --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). ## Rule Details diff --git a/docs/src/rules/brace-style.md b/docs/src/rules/brace-style.md index 456ccf2c2e1d..8fb982328a6f 100644 --- a/docs/src/rules/brace-style.md +++ b/docs/src/rules/brace-style.md @@ -8,7 +8,7 @@ further_reading: - https://en.wikipedia.org/wiki/Indent_style --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Brace style is closely related to [indent style](https://en.wikipedia.org/wiki/Indent_style) in programming and describes the placement of braces relative to their control statement and body. There are probably a dozen, if not more, brace styles in the world. diff --git a/docs/src/rules/comma-dangle.md b/docs/src/rules/comma-dangle.md index 1370874ed505..17685a08f3d1 100644 --- a/docs/src/rules/comma-dangle.md +++ b/docs/src/rules/comma-dangle.md @@ -3,7 +3,7 @@ title: comma-dangle rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Trailing commas in object literals are valid according to the ECMAScript 5 (and ECMAScript 3!) spec. However, IE8 (when not in IE8 document mode) and below will throw an error when it encounters trailing commas in JavaScript. diff --git a/docs/src/rules/comma-spacing.md b/docs/src/rules/comma-spacing.md index 9bc7f82dd246..e4d430997be3 100644 --- a/docs/src/rules/comma-spacing.md +++ b/docs/src/rules/comma-spacing.md @@ -16,7 +16,7 @@ further_reading: - https://dojotoolkit.org/reference-guide/1.9/developer/styleguide.html --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Spacing around commas improves readability of a list of items. Although most of the style guidelines for languages prescribe adding a space after a comma and not before it, it is subjective to the preferences of a project. diff --git a/docs/src/rules/comma-style.md b/docs/src/rules/comma-style.md index 6936cdbcb244..5859710ee89d 100644 --- a/docs/src/rules/comma-style.md +++ b/docs/src/rules/comma-style.md @@ -7,7 +7,7 @@ further_reading: - https://gist.github.com/isaacs/357981 --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). The Comma Style rule enforces styles for comma-separated lists. There are two comma styles primarily used in JavaScript: diff --git a/docs/src/rules/computed-property-spacing.md b/docs/src/rules/computed-property-spacing.md index 065270d6cafd..424e97cce633 100644 --- a/docs/src/rules/computed-property-spacing.md +++ b/docs/src/rules/computed-property-spacing.md @@ -7,7 +7,7 @@ related_rules: - space-in-parens --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). While formatting preferences are very personal, a number of style guides require or disallow spaces between computed properties in the following situations: diff --git a/docs/src/rules/dot-location.md b/docs/src/rules/dot-location.md index 47ee575be975..8abb47708f48 100644 --- a/docs/src/rules/dot-location.md +++ b/docs/src/rules/dot-location.md @@ -6,7 +6,7 @@ related_rules: - dot-notation --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). JavaScript allows you to place newlines before or after a dot in a member expression. diff --git a/docs/src/rules/eol-last.md b/docs/src/rules/eol-last.md index 2a8de22258b5..c4c0dc991f0f 100644 --- a/docs/src/rules/eol-last.md +++ b/docs/src/rules/eol-last.md @@ -3,7 +3,7 @@ title: eol-last rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Trailing newlines in non-empty files are a common UNIX idiom. Benefits of trailing newlines include the ability to concatenate or append to files as well diff --git a/docs/src/rules/func-call-spacing.md b/docs/src/rules/func-call-spacing.md index 082c86d81406..2821630fdbf1 100644 --- a/docs/src/rules/func-call-spacing.md +++ b/docs/src/rules/func-call-spacing.md @@ -5,7 +5,7 @@ related_rules: - no-spaced-func --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). When calling a function, developers may insert optional whitespace between the function's name and the parentheses that invoke it. The following pairs of function calls are equivalent: diff --git a/docs/src/rules/function-call-argument-newline.md b/docs/src/rules/function-call-argument-newline.md index be44b2ed7e95..73adf30c59fd 100644 --- a/docs/src/rules/function-call-argument-newline.md +++ b/docs/src/rules/function-call-argument-newline.md @@ -8,7 +8,7 @@ related_rules: - array-element-newline --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). A number of style guides require or disallow line breaks between arguments of a function call. diff --git a/docs/src/rules/function-paren-newline.md b/docs/src/rules/function-paren-newline.md index ad44aaec3650..116737118e90 100644 --- a/docs/src/rules/function-paren-newline.md +++ b/docs/src/rules/function-paren-newline.md @@ -3,7 +3,7 @@ title: function-paren-newline rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Many style guides require or disallow newlines inside of function parentheses. diff --git a/docs/src/rules/generator-star-spacing.md b/docs/src/rules/generator-star-spacing.md index 3ef58d86da6b..a8b3c6c9b17a 100644 --- a/docs/src/rules/generator-star-spacing.md +++ b/docs/src/rules/generator-star-spacing.md @@ -5,7 +5,7 @@ further_reading: - https://leanpub.com/understandinges6/read/#leanpub-auto-generators --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Generators are a new type of function in ECMAScript 6 that can return multiple values over time. These special functions are indicated by placing an `*` after the `function` keyword. diff --git a/docs/src/rules/implicit-arrow-linebreak.md b/docs/src/rules/implicit-arrow-linebreak.md index 09aaba18e039..25f5bc97d76e 100644 --- a/docs/src/rules/implicit-arrow-linebreak.md +++ b/docs/src/rules/implicit-arrow-linebreak.md @@ -5,7 +5,7 @@ related_rules: - brace-style --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). An arrow function body can contain an implicit return as an expression instead of a block body. It can be useful to enforce a consistent location for the implicitly returned expression. diff --git a/docs/src/rules/indent.md b/docs/src/rules/indent.md index ea700b240bb1..6cd25b73aecd 100644 --- a/docs/src/rules/indent.md +++ b/docs/src/rules/indent.md @@ -3,7 +3,7 @@ title: indent rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). There are several common guidelines which require specific indentation of nested blocks and statements, like: diff --git a/docs/src/rules/jsx-quotes.md b/docs/src/rules/jsx-quotes.md index 4e0799687550..b26450a9bc4d 100644 --- a/docs/src/rules/jsx-quotes.md +++ b/docs/src/rules/jsx-quotes.md @@ -5,7 +5,7 @@ related_rules: - quotes --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). JSX attribute values can contain string literals, which are delimited with single or double quotes. diff --git a/docs/src/rules/key-spacing.md b/docs/src/rules/key-spacing.md index d6e500053cf1..234b0de1b219 100644 --- a/docs/src/rules/key-spacing.md +++ b/docs/src/rules/key-spacing.md @@ -3,7 +3,7 @@ title: key-spacing rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). This rule enforces spacing around the colon in object literal properties. It can verify each property individually, or it can ensure horizontal alignment of adjacent properties in an object literal. diff --git a/docs/src/rules/keyword-spacing.md b/docs/src/rules/keyword-spacing.md index 0ffe1e13df63..7510378dea09 100644 --- a/docs/src/rules/keyword-spacing.md +++ b/docs/src/rules/keyword-spacing.md @@ -3,7 +3,7 @@ title: keyword-spacing rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Keywords are syntax elements of JavaScript, such as `try` and `if`. These keywords have special meaning to the language and so often appear in a different color in code editors. diff --git a/docs/src/rules/linebreak-style.md b/docs/src/rules/linebreak-style.md index 71158115f343..c4a764c9debd 100644 --- a/docs/src/rules/linebreak-style.md +++ b/docs/src/rules/linebreak-style.md @@ -3,7 +3,7 @@ title: linebreak-style rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). When developing with a lot of people all having different editors, VCS applications and operating systems it may occur that different line endings are written by either of the mentioned (might especially happen when using the windows and mac versions of SourceTree together). diff --git a/docs/src/rules/lines-around-comment.md b/docs/src/rules/lines-around-comment.md index 0cafa6df7036..56ea775ef44c 100644 --- a/docs/src/rules/lines-around-comment.md +++ b/docs/src/rules/lines-around-comment.md @@ -6,7 +6,7 @@ related_rules: - spaced-comment --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Many style guides require empty lines before or after comments. The primary goal of these rules is to make the comments easier to read and improve readability of the code. diff --git a/docs/src/rules/lines-between-class-members.md b/docs/src/rules/lines-between-class-members.md index 42c20b92ddda..9c9d7e0cbad6 100644 --- a/docs/src/rules/lines-between-class-members.md +++ b/docs/src/rules/lines-between-class-members.md @@ -6,7 +6,7 @@ related_rules: - padding-line-between-statements --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). This rule improves readability by enforcing lines between class members. It will not check empty lines before the first member and after the last member, since that is already taken care of by padded-blocks. diff --git a/docs/src/rules/max-len.md b/docs/src/rules/max-len.md index fb3310269e37..7f7a0d006c02 100644 --- a/docs/src/rules/max-len.md +++ b/docs/src/rules/max-len.md @@ -9,6 +9,7 @@ related_rules: - max-statements --- +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Very long lines of code in any language can be difficult to read. In order to aid in readability and maintainability many coders have developed a convention to limit lines of code to X number of characters (traditionally 80 characters). diff --git a/docs/src/rules/max-statements-per-line.md b/docs/src/rules/max-statements-per-line.md index d42d2ef55c1a..1a579add6fb8 100644 --- a/docs/src/rules/max-statements-per-line.md +++ b/docs/src/rules/max-statements-per-line.md @@ -11,6 +11,7 @@ related_rules: - max-statements --- +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). A line of code containing too many statements can be difficult to read. Code is generally read from the top down, especially when scanning, so limiting the number of statements allowed on a single line can be very beneficial for readability and maintainability. diff --git a/docs/src/rules/multiline-ternary.md b/docs/src/rules/multiline-ternary.md index f828bc1cd40a..12ed8e176b5e 100644 --- a/docs/src/rules/multiline-ternary.md +++ b/docs/src/rules/multiline-ternary.md @@ -5,7 +5,7 @@ related_rules: - operator-linebreak --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). JavaScript allows operands of ternary expressions to be separated by newlines, which can improve the readability of your program. diff --git a/docs/src/rules/new-parens.md b/docs/src/rules/new-parens.md index 08292e8a9788..0f652c2eb34a 100644 --- a/docs/src/rules/new-parens.md +++ b/docs/src/rules/new-parens.md @@ -3,7 +3,7 @@ title: new-parens rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). JavaScript allows the omission of parentheses when invoking a function via the `new` keyword and the constructor has no arguments. However, some coders believe that omitting the parentheses is inconsistent with the rest of the language and thus makes code less clear. diff --git a/docs/src/rules/newline-per-chained-call.md b/docs/src/rules/newline-per-chained-call.md index e2482cdbc20e..34eddb31338c 100644 --- a/docs/src/rules/newline-per-chained-call.md +++ b/docs/src/rules/newline-per-chained-call.md @@ -3,7 +3,7 @@ title: newline-per-chained-call rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Chained method calls on a single line without line breaks are harder to read, so some developers place a newline character after each method call in the chain to make it more readable and easy to maintain. diff --git a/docs/src/rules/no-confusing-arrow.md b/docs/src/rules/no-confusing-arrow.md index 546e30321a86..b51140aa75f3 100644 --- a/docs/src/rules/no-confusing-arrow.md +++ b/docs/src/rules/no-confusing-arrow.md @@ -6,7 +6,7 @@ related_rules: - arrow-parens --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Arrow functions (`=>`) are similar in syntax to some comparison operators (`>`, `<`, `<=`, and `>=`). This rule warns against using the arrow function syntax in places where it could be confused with a comparison operator. diff --git a/docs/src/rules/no-extra-parens.md b/docs/src/rules/no-extra-parens.md index 5fcbcc639269..ab5655b2b0a3 100644 --- a/docs/src/rules/no-extra-parens.md +++ b/docs/src/rules/no-extra-parens.md @@ -9,7 +9,7 @@ further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). This rule restricts the use of parentheses to only where they are necessary. diff --git a/docs/src/rules/no-extra-semi.md b/docs/src/rules/no-extra-semi.md index 683c76cc5f0b..1e9ee0a611f7 100644 --- a/docs/src/rules/no-extra-semi.md +++ b/docs/src/rules/no-extra-semi.md @@ -6,9 +6,7 @@ related_rules: - semi-spacing --- - - - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Typing mistakes and misunderstandings about where semicolons are required can lead to semicolons that are unnecessary. While not technically an error, extra semicolons can cause confusion when reading code. diff --git a/docs/src/rules/no-floating-decimal.md b/docs/src/rules/no-floating-decimal.md index 30a43317d552..8ba7a799150d 100644 --- a/docs/src/rules/no-floating-decimal.md +++ b/docs/src/rules/no-floating-decimal.md @@ -3,7 +3,7 @@ title: no-floating-decimal rule_type: suggestion --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Float values in JavaScript contain a decimal point, and there is no requirement that the decimal point be preceded or followed by a number. For example, the following are all valid JavaScript numbers: diff --git a/docs/src/rules/no-mixed-operators.md b/docs/src/rules/no-mixed-operators.md index 07ab40f7fe53..d8bb908540ba 100644 --- a/docs/src/rules/no-mixed-operators.md +++ b/docs/src/rules/no-mixed-operators.md @@ -5,6 +5,7 @@ related_rules: - no-extra-parens --- +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Enclosing complex expressions by parentheses clarifies the developer's intention, which makes the code more readable. This rule warns when different operators are used consecutively without parentheses in an expression. diff --git a/docs/src/rules/no-mixed-spaces-and-tabs.md b/docs/src/rules/no-mixed-spaces-and-tabs.md index 39c230b2dcaf..b13223cf585f 100644 --- a/docs/src/rules/no-mixed-spaces-and-tabs.md +++ b/docs/src/rules/no-mixed-spaces-and-tabs.md @@ -5,7 +5,7 @@ further_reading: - https://www.emacswiki.org/emacs/SmartTabs --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Most code conventions require either tabs or spaces be used for indentation. As such, it's usually an error if a single line of code is indented with both tabs and spaces. diff --git a/docs/src/rules/no-multi-spaces.md b/docs/src/rules/no-multi-spaces.md index b23a97964fe7..ca7e873ab0b5 100644 --- a/docs/src/rules/no-multi-spaces.md +++ b/docs/src/rules/no-multi-spaces.md @@ -11,7 +11,7 @@ related_rules: - space-return-throw-case --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Multiple spaces in a row that are not used for indentation are typically mistakes. For example: diff --git a/docs/src/rules/no-multiple-empty-lines.md b/docs/src/rules/no-multiple-empty-lines.md index e3d7327c2e43..f6ccb3050e5a 100644 --- a/docs/src/rules/no-multiple-empty-lines.md +++ b/docs/src/rules/no-multiple-empty-lines.md @@ -3,7 +3,7 @@ title: no-multiple-empty-lines rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Some developers prefer to have multiple blank lines removed, while others feel that it helps improve readability. Whitespace is useful for separating logical sections of code, but excess whitespace takes up more of the screen. diff --git a/docs/src/rules/no-tabs.md b/docs/src/rules/no-tabs.md index c105b6a8193b..fe999fa3c78b 100644 --- a/docs/src/rules/no-tabs.md +++ b/docs/src/rules/no-tabs.md @@ -3,6 +3,7 @@ title: no-tabs rule_type: layout --- +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Some style guides don't allow the use of tab characters at all, including within comments. diff --git a/docs/src/rules/no-trailing-spaces.md b/docs/src/rules/no-trailing-spaces.md index 61aec1fcf4aa..71c50e41f447 100644 --- a/docs/src/rules/no-trailing-spaces.md +++ b/docs/src/rules/no-trailing-spaces.md @@ -3,7 +3,7 @@ title: no-trailing-spaces rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Sometimes in the course of editing files, you can end up with extra whitespace at the end of lines. These whitespace differences can be picked up by source control systems and flagged as diffs, causing frustration for developers. While this extra whitespace causes no functional issues, many code conventions require that trailing spaces be removed before check-in. diff --git a/docs/src/rules/no-whitespace-before-property.md b/docs/src/rules/no-whitespace-before-property.md index db79ca2a897d..a00864d491c1 100644 --- a/docs/src/rules/no-whitespace-before-property.md +++ b/docs/src/rules/no-whitespace-before-property.md @@ -3,7 +3,7 @@ title: no-whitespace-before-property rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). JavaScript allows whitespace between objects and their properties. However, inconsistent spacing can make code harder to read and can lead to errors. diff --git a/docs/src/rules/nonblock-statement-body-position.md b/docs/src/rules/nonblock-statement-body-position.md index 78d332026e71..1a2aae9122fe 100644 --- a/docs/src/rules/nonblock-statement-body-position.md +++ b/docs/src/rules/nonblock-statement-body-position.md @@ -5,7 +5,7 @@ further_reading: - https://jscs-dev.github.io/rule/requireNewlineBeforeSingleStatementsInIf --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). When writing `if`, `else`, `while`, `do-while`, and `for` statements, the body can be a single statement instead of a block. It can be useful to enforce a consistent location for these single statements. diff --git a/docs/src/rules/object-curly-newline.md b/docs/src/rules/object-curly-newline.md index 4a4f2e14f61e..d33dd6433e81 100644 --- a/docs/src/rules/object-curly-newline.md +++ b/docs/src/rules/object-curly-newline.md @@ -8,7 +8,7 @@ related_rules: - object-property-newline --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). A number of style guides require or disallow line breaks inside of object braces and other tokens. diff --git a/docs/src/rules/object-curly-spacing.md b/docs/src/rules/object-curly-spacing.md index 4027c9ab755a..0484c436a4bf 100644 --- a/docs/src/rules/object-curly-spacing.md +++ b/docs/src/rules/object-curly-spacing.md @@ -8,7 +8,7 @@ related_rules: - space-in-parens --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). While formatting preferences are very personal, a number of style guides require or disallow spaces between curly braces in the following situations: diff --git a/docs/src/rules/object-property-newline.md b/docs/src/rules/object-property-newline.md index 50214bdb7c06..e2bb161ea589 100644 --- a/docs/src/rules/object-property-newline.md +++ b/docs/src/rules/object-property-newline.md @@ -8,7 +8,7 @@ related_rules: - object-curly-spacing --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). This rule permits you to restrict the locations of property specifications in object literals. You may prohibit any part of any property specification from appearing on the same line as any part of any other property specification. You may make this prohibition absolute, or, by invoking an object option, you may allow an exception, permitting an object literal to have all parts of all of its property specifications on a single line. diff --git a/docs/src/rules/one-var-declaration-per-line.md b/docs/src/rules/one-var-declaration-per-line.md index 659ef73343dc..742f52352fb1 100644 --- a/docs/src/rules/one-var-declaration-per-line.md +++ b/docs/src/rules/one-var-declaration-per-line.md @@ -5,7 +5,7 @@ related_rules: - one-var --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Some developers declare multiple var statements on the same line: diff --git a/docs/src/rules/operator-linebreak.md b/docs/src/rules/operator-linebreak.md index f3cab6e4f03e..0724797f3eef 100644 --- a/docs/src/rules/operator-linebreak.md +++ b/docs/src/rules/operator-linebreak.md @@ -5,7 +5,7 @@ related_rules: - comma-style --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). When a statement is too long to fit on a single line, line breaks are generally inserted next to the operators separating expressions. The first style coming to mind would be to place the operator at the end of the line, following the English punctuation rules. diff --git a/docs/src/rules/padded-blocks.md b/docs/src/rules/padded-blocks.md index fd57f2a2905b..7d1a32042afc 100644 --- a/docs/src/rules/padded-blocks.md +++ b/docs/src/rules/padded-blocks.md @@ -6,7 +6,7 @@ related_rules: - padding-line-between-statements --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Some style guides require block statements to start and end with blank lines. The goal is to improve readability by visually separating the block content and the surrounding code. diff --git a/docs/src/rules/padding-line-between-statements.md b/docs/src/rules/padding-line-between-statements.md index 710a6116d636..af57ed489907 100644 --- a/docs/src/rules/padding-line-between-statements.md +++ b/docs/src/rules/padding-line-between-statements.md @@ -3,7 +3,7 @@ title: padding-line-between-statements rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). This rule requires or disallows blank lines between the given 2 kinds of statements. Properly blank lines help developers to understand the code. diff --git a/docs/src/rules/quote-props.md b/docs/src/rules/quote-props.md index b994b82507ec..2e204dd0cb6a 100644 --- a/docs/src/rules/quote-props.md +++ b/docs/src/rules/quote-props.md @@ -6,7 +6,7 @@ further_reading: - https://mathiasbynens.be/notes/javascript-properties --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Object literal property names can be defined in two ways: using literals or using strings. For example, these two objects are equivalent: diff --git a/docs/src/rules/quotes.md b/docs/src/rules/quotes.md index 1b876ebdd3ef..ea935d610a13 100644 --- a/docs/src/rules/quotes.md +++ b/docs/src/rules/quotes.md @@ -3,7 +3,7 @@ title: quotes rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). JavaScript allows you to define strings in one of three ways: double quotes, single quotes, and backticks (as of ECMAScript 6). For example: diff --git a/docs/src/rules/rest-spread-spacing.md b/docs/src/rules/rest-spread-spacing.md index f51ef54be35a..8a52856cad9a 100644 --- a/docs/src/rules/rest-spread-spacing.md +++ b/docs/src/rules/rest-spread-spacing.md @@ -5,7 +5,7 @@ further_reading: - https://github.com/tc39/proposal-object-rest-spread --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). ES2015 introduced the rest and spread operators, which expand an iterable structure into its individual parts. Some examples of their usage are as follows: diff --git a/docs/src/rules/semi-spacing.md b/docs/src/rules/semi-spacing.md index 4ed43ac570ee..5a27040f9b5e 100644 --- a/docs/src/rules/semi-spacing.md +++ b/docs/src/rules/semi-spacing.md @@ -9,7 +9,7 @@ related_rules: - space-in-parens --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). JavaScript allows you to place unnecessary spaces before or after a semicolon. diff --git a/docs/src/rules/semi-style.md b/docs/src/rules/semi-style.md index 66db3af0f75c..2979c1885644 100644 --- a/docs/src/rules/semi-style.md +++ b/docs/src/rules/semi-style.md @@ -7,7 +7,7 @@ related_rules: - semi-spacing --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Generally, semicolons are at the end of lines. However, in semicolon-less style, semicolons are at the beginning of lines. This rule enforces that semicolons are at the configured location. diff --git a/docs/src/rules/semi.md b/docs/src/rules/semi.md index 6fd05501cd1f..e118b4518f85 100644 --- a/docs/src/rules/semi.md +++ b/docs/src/rules/semi.md @@ -10,7 +10,7 @@ further_reading: - https://web.archive.org/web/20200420230322/http://inimino.org/~inimino/blog/javascript_semicolons --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). JavaScript doesn't require semicolons at the end of each statement. In many cases, the JavaScript engine can determine that a semicolon should be in a certain spot and will automatically add it. This feature is known as **automatic semicolon insertion (ASI)** and is considered one of the more controversial features of JavaScript. For example, the following lines are both valid: diff --git a/docs/src/rules/space-before-blocks.md b/docs/src/rules/space-before-blocks.md index a7bcad2c70a5..42f02f99ebf3 100644 --- a/docs/src/rules/space-before-blocks.md +++ b/docs/src/rules/space-before-blocks.md @@ -9,7 +9,7 @@ related_rules: - brace-style --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Consistency is an important part of any style guide. While it is a personal preference where to put the opening brace of blocks, diff --git a/docs/src/rules/space-before-function-paren.md b/docs/src/rules/space-before-function-paren.md index a132105ac415..8b439dac5f19 100644 --- a/docs/src/rules/space-before-function-paren.md +++ b/docs/src/rules/space-before-function-paren.md @@ -5,7 +5,7 @@ related_rules: - keyword-spacing --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). When formatting a function, whitespace is allowed between the function name or `function` keyword and the opening paren. Named functions also require a space between the `function` keyword and the function name, but anonymous functions require no whitespace. For example: diff --git a/docs/src/rules/space-in-parens.md b/docs/src/rules/space-in-parens.md index 1d6ca52bdf1e..9f92cdc524ac 100644 --- a/docs/src/rules/space-in-parens.md +++ b/docs/src/rules/space-in-parens.md @@ -7,7 +7,7 @@ related_rules: - computed-property-spacing --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Some style guides require or disallow spaces inside of parentheses: diff --git a/docs/src/rules/space-infix-ops.md b/docs/src/rules/space-infix-ops.md index b278a7c1d781..8a8c0c3dff76 100644 --- a/docs/src/rules/space-infix-ops.md +++ b/docs/src/rules/space-infix-ops.md @@ -3,7 +3,7 @@ title: space-infix-ops rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). While formatting preferences are very personal, a number of style guides require spaces around operators, such as: diff --git a/docs/src/rules/space-unary-ops.md b/docs/src/rules/space-unary-ops.md index a33735b5565e..4e7f79bfce99 100644 --- a/docs/src/rules/space-unary-ops.md +++ b/docs/src/rules/space-unary-ops.md @@ -3,7 +3,7 @@ title: space-unary-ops rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Some style guides require or disallow spaces before or after unary operators. This is mainly a stylistic issue, however, some JavaScript expressions can be written without spacing which makes it harder to read and maintain. diff --git a/docs/src/rules/spaced-comment.md b/docs/src/rules/spaced-comment.md index f15e16c72e8c..3094c3cebedf 100644 --- a/docs/src/rules/spaced-comment.md +++ b/docs/src/rules/spaced-comment.md @@ -5,7 +5,7 @@ related_rules: - spaced-line-comment --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Some style guides require or disallow a whitespace immediately after the initial `//` or `/*` of a comment. Whitespace after the `//` or `/*` makes it easier to read text in comments. diff --git a/docs/src/rules/switch-colon-spacing.md b/docs/src/rules/switch-colon-spacing.md index 63df48f23e19..7672c6275b74 100644 --- a/docs/src/rules/switch-colon-spacing.md +++ b/docs/src/rules/switch-colon-spacing.md @@ -3,7 +3,7 @@ title: switch-colon-spacing rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). Spacing around colons improves readability of `case`/`default` clauses. diff --git a/docs/src/rules/template-curly-spacing.md b/docs/src/rules/template-curly-spacing.md index b96a30c870dc..7a095cc38d86 100644 --- a/docs/src/rules/template-curly-spacing.md +++ b/docs/src/rules/template-curly-spacing.md @@ -3,7 +3,7 @@ title: template-curly-spacing rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). We can embed expressions in template strings with using a pair of `${` and `}`. diff --git a/docs/src/rules/template-tag-spacing.md b/docs/src/rules/template-tag-spacing.md index da0cfa8884b6..2adf4c762051 100644 --- a/docs/src/rules/template-tag-spacing.md +++ b/docs/src/rules/template-tag-spacing.md @@ -6,7 +6,7 @@ further_reading: - https://exploringjs.com/es6/ch_template-literals.html#_examples-of-using-tagged-template-literals --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). With ES6, it's possible to create functions called [tagged template literals](#further-reading) where the function parameters consist of a template literal's strings and expressions. diff --git a/docs/src/rules/wrap-iife.md b/docs/src/rules/wrap-iife.md index af8ec85c785f..dd0048a8bb78 100644 --- a/docs/src/rules/wrap-iife.md +++ b/docs/src/rules/wrap-iife.md @@ -3,7 +3,7 @@ title: wrap-iife rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). You can immediately invoke function expressions, but not function declarations. A common technique to create an immediately-invoked function expression (IIFE) is to wrap a function declaration in parentheses. The opening parentheses causes the contained function to be parsed as an expression, rather than a declaration. diff --git a/docs/src/rules/wrap-regex.md b/docs/src/rules/wrap-regex.md index d4cff7df9888..0a5daf0e1a04 100644 --- a/docs/src/rules/wrap-regex.md +++ b/docs/src/rules/wrap-regex.md @@ -3,7 +3,7 @@ title: wrap-regex rule_type: layout --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). When a regular expression is used in certain situations, it can end up looking like a division operator. For example: diff --git a/docs/src/rules/yield-star-spacing.md b/docs/src/rules/yield-star-spacing.md index c5e47380926d..aeeb0c73b2d2 100644 --- a/docs/src/rules/yield-star-spacing.md +++ b/docs/src/rules/yield-star-spacing.md @@ -5,7 +5,7 @@ further_reading: - https://leanpub.com/understandinges6/read/#leanpub-auto-generators --- - +This rule was **deprecated** in ESLint v8.53.0. Please use the corresponding rule in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). ## Rule Details diff --git a/lib/rules/array-bracket-newline.js b/lib/rules/array-bracket-newline.js index c3676bf4dfa5..12ef5b612d6a 100644 --- a/lib/rules/array-bracket-newline.js +++ b/lib/rules/array-bracket-newline.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to enforce linebreaks after open and before close array brackets * @author Jan Peer Stรถcklmair + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -14,6 +15,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/array-bracket-spacing.js b/lib/rules/array-bracket-spacing.js index e3a46d82214e..9dd3ffd902cc 100644 --- a/lib/rules/array-bracket-spacing.js +++ b/lib/rules/array-bracket-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Disallows or enforces spaces inside of array brackets. * @author Jamund Ferguson + * @deprecated in ESLint v8.53.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/array-element-newline.js b/lib/rules/array-element-newline.js index 0c806ef3a82c..504fe04a0b85 100644 --- a/lib/rules/array-element-newline.js +++ b/lib/rules/array-element-newline.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to enforce line breaks after each array element * @author Jan Peer Stรถcklmair + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -14,6 +15,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/arrow-parens.js b/lib/rules/arrow-parens.js index 0463323176ed..2206d8ce2bfd 100644 --- a/lib/rules/arrow-parens.js +++ b/lib/rules/arrow-parens.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to require parens in arrow function arguments. * @author Jxck + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -30,6 +31,8 @@ function hasBlockBody(node) { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/arrow-spacing.js b/lib/rules/arrow-spacing.js index fb74d2cb272b..2b7d464ffcf9 100644 --- a/lib/rules/arrow-spacing.js +++ b/lib/rules/arrow-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to define spacing before/after arrow function's arrow. * @author Jxck + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -17,6 +18,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/block-spacing.js b/lib/rules/block-spacing.js index dd4851c68435..9ca461158d9e 100644 --- a/lib/rules/block-spacing.js +++ b/lib/rules/block-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview A rule to disallow or enforce spaces inside of single line blocks. * @author Toru Nagashima + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -14,6 +15,8 @@ const util = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/brace-style.js b/lib/rules/brace-style.js index 59758c909250..0fb4c65e68d5 100644 --- a/lib/rules/brace-style.js +++ b/lib/rules/brace-style.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to flag block statements that do not use the one true brace style * @author Ian Christian Myers + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -14,6 +15,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/comma-dangle.js b/lib/rules/comma-dangle.js index e49983b722e7..5f4180f12c53 100644 --- a/lib/rules/comma-dangle.js +++ b/lib/rules/comma-dangle.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to forbid or enforce dangling commas. * @author Ian Christian Myers + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -73,6 +74,8 @@ function normalizeOptions(optionValue, ecmaVersion) { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/comma-spacing.js b/lib/rules/comma-spacing.js index 96015ef67795..e266de4a9c34 100644 --- a/lib/rules/comma-spacing.js +++ b/lib/rules/comma-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Comma spacing - validates spacing before and after comma * @author Vignesh Anand aka vegetableman. + * @deprecated in ESLint v8.53.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/comma-style.js b/lib/rules/comma-style.js index bc69de4698d4..0b51219531de 100644 --- a/lib/rules/comma-style.js +++ b/lib/rules/comma-style.js @@ -1,6 +1,7 @@ /** * @fileoverview Comma style - enforces comma styles of two types: last and first * @author Vignesh Anand aka vegetableman + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -14,6 +15,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/computed-property-spacing.js b/lib/rules/computed-property-spacing.js index 1e4e17c6c71f..2852877fddf4 100644 --- a/lib/rules/computed-property-spacing.js +++ b/lib/rules/computed-property-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Disallows or enforces spaces inside computed properties. * @author Jamund Ferguson + * @deprecated in ESLint v8.53.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/dot-location.js b/lib/rules/dot-location.js index dac98b06b9e2..0d017c16232b 100644 --- a/lib/rules/dot-location.js +++ b/lib/rules/dot-location.js @@ -1,6 +1,7 @@ /** * @fileoverview Validates newlines before and after dots * @author Greg Cochard + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -14,6 +15,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/eol-last.js b/lib/rules/eol-last.js index 1036db1a1085..03487b039f35 100644 --- a/lib/rules/eol-last.js +++ b/lib/rules/eol-last.js @@ -1,6 +1,7 @@ /** * @fileoverview Require or disallow newline at the end of files * @author Nodeca Team + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -11,6 +12,8 @@ /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/func-call-spacing.js b/lib/rules/func-call-spacing.js index 3d5e538493e3..33f73727b43c 100644 --- a/lib/rules/func-call-spacing.js +++ b/lib/rules/func-call-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to control spacing within function calls * @author Matt DuVall + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -18,6 +19,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/function-call-argument-newline.js b/lib/rules/function-call-argument-newline.js index 4462afd0b7c0..458399d62cd9 100644 --- a/lib/rules/function-call-argument-newline.js +++ b/lib/rules/function-call-argument-newline.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to enforce line breaks between arguments of a function call * @author Alexey Gonchar + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -12,6 +13,8 @@ /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/function-paren-newline.js b/lib/rules/function-paren-newline.js index 8a8714ac95d1..de315a0204b3 100644 --- a/lib/rules/function-paren-newline.js +++ b/lib/rules/function-paren-newline.js @@ -1,6 +1,7 @@ /** * @fileoverview enforce consistent line breaks inside function parentheses * @author Teddy Katz + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -17,6 +18,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/generator-star-spacing.js b/lib/rules/generator-star-spacing.js index 81c0b61059a5..c633f979f84a 100644 --- a/lib/rules/generator-star-spacing.js +++ b/lib/rules/generator-star-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to check the spacing around the * in generator functions. * @author Jamund Ferguson + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -28,6 +29,8 @@ const OVERRIDE_SCHEMA = { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/implicit-arrow-linebreak.js b/lib/rules/implicit-arrow-linebreak.js index 30ab1a5f3d07..32f422ce8282 100644 --- a/lib/rules/implicit-arrow-linebreak.js +++ b/lib/rules/implicit-arrow-linebreak.js @@ -1,6 +1,7 @@ /** * @fileoverview enforce the location of arrow function bodies * @author Sharmila Jesupaul + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -12,6 +13,8 @@ const { isCommentToken, isNotOpeningParenToken } = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/indent.js b/lib/rules/indent.js index 7ea4b3f86c33..9bcbd640c4de 100644 --- a/lib/rules/indent.js +++ b/lib/rules/indent.js @@ -4,6 +4,7 @@ * @author Teddy Katz * @author Vitaly Puzrin * @author Gyandeep Singh + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -493,6 +494,8 @@ const ELEMENT_LIST_SCHEMA = { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/jsx-quotes.js b/lib/rules/jsx-quotes.js index a41c85170fd5..3dcd5fa9d22b 100644 --- a/lib/rules/jsx-quotes.js +++ b/lib/rules/jsx-quotes.js @@ -1,6 +1,7 @@ /** * @fileoverview A rule to ensure consistent quotes used in jsx syntax. * @author Mathias Schreck + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -39,6 +40,8 @@ const QUOTE_SETTINGS = { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/key-spacing.js b/lib/rules/key-spacing.js index 0b51eb3fe137..19fc0167ae0d 100644 --- a/lib/rules/key-spacing.js +++ b/lib/rules/key-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to specify spacing of object literal keys and values * @author Brandon Mills + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -133,6 +134,8 @@ function initOptions(toOptions, fromOptions) { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/keyword-spacing.js b/lib/rules/keyword-spacing.js index 8ed82199810e..9d18441e0e56 100644 --- a/lib/rules/keyword-spacing.js +++ b/lib/rules/keyword-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to enforce spacing before and after keywords. * @author Toru Nagashima + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -64,6 +65,8 @@ function isCloseParenOfTemplate(token) { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/linebreak-style.js b/lib/rules/linebreak-style.js index d8f36094b2e5..e59acca1b5c1 100644 --- a/lib/rules/linebreak-style.js +++ b/lib/rules/linebreak-style.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to enforce a single linebreak style. * @author Erik Mueller + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -18,6 +19,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/lines-around-comment.js b/lib/rules/lines-around-comment.js index 10aeba3cbc14..2a6e472f9a09 100644 --- a/lib/rules/lines-around-comment.js +++ b/lib/rules/lines-around-comment.js @@ -1,6 +1,7 @@ /** * @fileoverview Enforces empty lines around comments. * @author Jamund Ferguson + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -52,6 +53,8 @@ function getCommentLineNums(comments) { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/lines-between-class-members.js b/lib/rules/lines-between-class-members.js index 3d0a5e6738e9..5f36d468dc09 100644 --- a/lib/rules/lines-between-class-members.js +++ b/lib/rules/lines-between-class-members.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to check empty newline between class members * @author ่–›ๅฎš่ฐ”็š„็Œซ + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -32,6 +33,8 @@ const ClassMemberTypes = { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js index 53ad5310799b..138a0f239fd3 100644 --- a/lib/rules/max-len.js +++ b/lib/rules/max-len.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to check for max length on a line. * @author Matt DuVall + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -66,6 +67,8 @@ const OPTIONS_OR_INTEGER_SCHEMA = { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/max-statements-per-line.js b/lib/rules/max-statements-per-line.js index b96650487644..4ad73a67f98e 100644 --- a/lib/rules/max-statements-per-line.js +++ b/lib/rules/max-statements-per-line.js @@ -1,6 +1,7 @@ /** * @fileoverview Specify the maximum number of statements allowed per line. * @author Kenneth Williams + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -17,6 +18,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-ternary.js b/lib/rules/multiline-ternary.js index f156fe32bb17..8155dd7a5a6f 100644 --- a/lib/rules/multiline-ternary.js +++ b/lib/rules/multiline-ternary.js @@ -1,6 +1,7 @@ /** * @fileoverview Enforce newlines between operands of ternary expressions * @author Kai Cataldo + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -14,6 +15,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/new-parens.js b/lib/rules/new-parens.js index e8667310f294..1c5d21d4a0c5 100644 --- a/lib/rules/new-parens.js +++ b/lib/rules/new-parens.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to flag when using constructor without parentheses * @author Ilya Volodin + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -22,6 +23,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/newline-per-chained-call.js b/lib/rules/newline-per-chained-call.js index b2e6cd9e49d8..3124ac2d19f7 100644 --- a/lib/rules/newline-per-chained-call.js +++ b/lib/rules/newline-per-chained-call.js @@ -2,6 +2,7 @@ * @fileoverview Rule to ensure newline per method call when chaining calls * @author Rajendra Patil * @author Burak Yigit Kaya + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -15,6 +16,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/no-confusing-arrow.js b/lib/rules/no-confusing-arrow.js index de6e2f30c2ed..6fef1870eb2a 100644 --- a/lib/rules/no-confusing-arrow.js +++ b/lib/rules/no-confusing-arrow.js @@ -2,6 +2,7 @@ * @fileoverview A rule to warn against using arrow functions when they could be * confused with comparisons * @author Jxck + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -28,6 +29,8 @@ function isConditional(node) { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "suggestion", docs: { diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js index bb80987858c4..75c082baf2e8 100644 --- a/lib/rules/no-extra-parens.js +++ b/lib/rules/no-extra-parens.js @@ -1,6 +1,7 @@ /** * @fileoverview Disallow parenthesising higher precedence subexpressions. * @author Michael Ficarra + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -14,6 +15,8 @@ const astUtils = require("./utils/ast-utils.js"); /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/no-extra-semi.js b/lib/rules/no-extra-semi.js index 3473df10dbfb..af7eb8888456 100644 --- a/lib/rules/no-extra-semi.js +++ b/lib/rules/no-extra-semi.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to flag use of unnecessary semicolons * @author Nicholas C. Zakas + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -19,6 +20,8 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "suggestion", docs: { diff --git a/lib/rules/no-floating-decimal.js b/lib/rules/no-floating-decimal.js index c26876440a58..80e4994cd72a 100644 --- a/lib/rules/no-floating-decimal.js +++ b/lib/rules/no-floating-decimal.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to flag use of a leading/trailing decimal point in a numeric literal * @author James Allardice + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -18,6 +19,8 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "suggestion", docs: { diff --git a/lib/rules/no-mixed-operators.js b/lib/rules/no-mixed-operators.js index 724abe094664..6b6f7364a012 100644 --- a/lib/rules/no-mixed-operators.js +++ b/lib/rules/no-mixed-operators.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to disallow mixed binary operators. * @author Toru Nagashima + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -85,6 +86,8 @@ function getChildNode(node) { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "suggestion", docs: { diff --git a/lib/rules/no-mixed-spaces-and-tabs.js b/lib/rules/no-mixed-spaces-and-tabs.js index a18e4f30d0aa..7698b5da7fa9 100644 --- a/lib/rules/no-mixed-spaces-and-tabs.js +++ b/lib/rules/no-mixed-spaces-and-tabs.js @@ -1,6 +1,7 @@ /** * @fileoverview Disallow mixed spaces and tabs for indentation * @author Jary Niebur + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -11,6 +12,8 @@ /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/no-multi-spaces.js b/lib/rules/no-multi-spaces.js index 62074e657ae3..bc90ee5b5b01 100644 --- a/lib/rules/no-multi-spaces.js +++ b/lib/rules/no-multi-spaces.js @@ -1,6 +1,7 @@ /** * @fileoverview Disallow use of multiple spaces. * @author Nicholas C. Zakas + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -14,6 +15,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/no-multiple-empty-lines.js b/lib/rules/no-multiple-empty-lines.js index 2c0fbf2c6ab9..5d038ff05b2e 100644 --- a/lib/rules/no-multiple-empty-lines.js +++ b/lib/rules/no-multiple-empty-lines.js @@ -2,6 +2,7 @@ * @fileoverview Disallows multiple blank lines. * implementation adapted from the no-trailing-spaces rule. * @author Greg Cochard + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -12,6 +13,8 @@ /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/no-tabs.js b/lib/rules/no-tabs.js index b33690c24412..8581e19af373 100644 --- a/lib/rules/no-tabs.js +++ b/lib/rules/no-tabs.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to check for tabs inside a file * @author Gyandeep Singh + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -19,6 +20,8 @@ const anyNonWhitespaceRegex = /\S/u; /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/no-trailing-spaces.js b/lib/rules/no-trailing-spaces.js index 1674de54207f..eede46c86346 100644 --- a/lib/rules/no-trailing-spaces.js +++ b/lib/rules/no-trailing-spaces.js @@ -1,6 +1,7 @@ /** * @fileoverview Disallow trailing spaces at the end of lines. * @author Nodeca Team + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -17,6 +18,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/no-whitespace-before-property.js b/lib/rules/no-whitespace-before-property.js index 1153314afe65..94a166e6adea 100644 --- a/lib/rules/no-whitespace-before-property.js +++ b/lib/rules/no-whitespace-before-property.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to disallow whitespace before properties * @author Kai Cataldo + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -17,6 +18,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/nonblock-statement-body-position.js b/lib/rules/nonblock-statement-body-position.js index 1ea2770ceb25..811b32b08212 100644 --- a/lib/rules/nonblock-statement-body-position.js +++ b/lib/rules/nonblock-statement-body-position.js @@ -1,6 +1,7 @@ /** * @fileoverview enforce the location of single-line statements * @author Teddy Katz + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -13,6 +14,8 @@ const POSITION_SCHEMA = { enum: ["beside", "below", "any"] }; /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/object-curly-newline.js b/lib/rules/object-curly-newline.js index caf1982312a6..176694b6a077 100644 --- a/lib/rules/object-curly-newline.js +++ b/lib/rules/object-curly-newline.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to require or disallow line breaks inside braces. * @author Toru Nagashima + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -147,6 +148,8 @@ function areLineBreaksRequired(node, options, first, last) { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/object-curly-spacing.js b/lib/rules/object-curly-spacing.js index 41ca428fe24d..4463bcd5af17 100644 --- a/lib/rules/object-curly-spacing.js +++ b/lib/rules/object-curly-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Disallows or enforces spaces inside of object literals. * @author Jamund Ferguson + * @deprecated in ESLint v8.53.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/object-property-newline.js b/lib/rules/object-property-newline.js index deca9b555b16..6ffa06421f02 100644 --- a/lib/rules/object-property-newline.js +++ b/lib/rules/object-property-newline.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to enforce placing object properties on separate lines. * @author Vitor Balocco + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -12,6 +13,8 @@ /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/one-var-declaration-per-line.js b/lib/rules/one-var-declaration-per-line.js index b1e045b0dc58..340eac169320 100644 --- a/lib/rules/one-var-declaration-per-line.js +++ b/lib/rules/one-var-declaration-per-line.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to check multiple var declarations per line * @author Alberto Rodrรญguez + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -11,6 +12,8 @@ /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "suggestion", docs: { diff --git a/lib/rules/operator-linebreak.js b/lib/rules/operator-linebreak.js index 2b609f635765..3065e66be128 100644 --- a/lib/rules/operator-linebreak.js +++ b/lib/rules/operator-linebreak.js @@ -1,6 +1,7 @@ /** * @fileoverview Operator linebreak - enforces operator linebreak style of two types: after and before * @author Benoรฎt Zugmeyer + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -18,6 +19,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/padded-blocks.js b/lib/rules/padded-blocks.js index c6d1372ac75d..ec4756ba739a 100644 --- a/lib/rules/padded-blocks.js +++ b/lib/rules/padded-blocks.js @@ -1,6 +1,7 @@ /** * @fileoverview A rule to ensure blank lines within blocks. * @author Mathias Schreck + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -18,6 +19,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/padding-line-between-statements.js b/lib/rules/padding-line-between-statements.js index 95e08736a9c5..084651b6dff9 100644 --- a/lib/rules/padding-line-between-statements.js +++ b/lib/rules/padding-line-between-statements.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to require or disallow newlines between statements * @author Toru Nagashima + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -383,6 +384,8 @@ const StatementTypes = { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/quote-props.js b/lib/rules/quote-props.js index 8abab150c4e3..fe26eed77de5 100644 --- a/lib/rules/quote-props.js +++ b/lib/rules/quote-props.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to flag non-quoted property names in object literals. * @author Mathias Bynens + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -19,6 +20,8 @@ const keywords = require("./utils/keywords"); /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "suggestion", docs: { diff --git a/lib/rules/quotes.js b/lib/rules/quotes.js index 6eda7c0b40ae..17d97dd697d1 100644 --- a/lib/rules/quotes.js +++ b/lib/rules/quotes.js @@ -1,6 +1,7 @@ /** * @fileoverview A rule to choose between single and double quote marks * @author Matt DuVall , Brandon Payton + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -77,6 +78,8 @@ const AVOID_ESCAPE = "avoid-escape"; /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/rest-spread-spacing.js b/lib/rules/rest-spread-spacing.js index 7791238081e2..287e56f014a6 100644 --- a/lib/rules/rest-spread-spacing.js +++ b/lib/rules/rest-spread-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Enforce spacing between rest and spread operators and their expressions. * @author Kai Cataldo + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -12,6 +13,8 @@ /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/semi-spacing.js b/lib/rules/semi-spacing.js index 770f62d41f12..35a49d2c22b5 100644 --- a/lib/rules/semi-spacing.js +++ b/lib/rules/semi-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Validates spacing before and after semicolon * @author Mathias Schreck + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -14,6 +15,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/semi-style.js b/lib/rules/semi-style.js index 67ed1e478e76..caf2224df3d4 100644 --- a/lib/rules/semi-style.js +++ b/lib/rules/semi-style.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to enforce location of semicolons. * @author Toru Nagashima + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -70,6 +71,8 @@ function isLastChild(node) { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/semi.js b/lib/rules/semi.js index 6a473535d49b..01586b8492d1 100644 --- a/lib/rules/semi.js +++ b/lib/rules/semi.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to flag missing semicolons. * @author Nicholas C. Zakas + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -18,6 +19,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/space-before-blocks.js b/lib/rules/space-before-blocks.js index a580a4f2249a..a4a5449e17fd 100644 --- a/lib/rules/space-before-blocks.js +++ b/lib/rules/space-before-blocks.js @@ -1,6 +1,7 @@ /** * @fileoverview A rule to ensure whitespace before blocks. * @author Mathias Schreck + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -37,6 +38,8 @@ function isFunctionBody(node) { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/space-before-function-paren.js b/lib/rules/space-before-function-paren.js index c5faa8cf4dde..575a1597a74e 100644 --- a/lib/rules/space-before-function-paren.js +++ b/lib/rules/space-before-function-paren.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to validate spacing before function paren. * @author Mathias Schreck + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -17,6 +18,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/space-in-parens.js b/lib/rules/space-in-parens.js index c6c06d29a3f5..d15a64317f4d 100644 --- a/lib/rules/space-in-parens.js +++ b/lib/rules/space-in-parens.js @@ -1,6 +1,7 @@ /** * @fileoverview Disallows or enforces spaces inside of parentheses. * @author Jonathan Rajavuori + * @deprecated in ESLint v8.53.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/space-infix-ops.js b/lib/rules/space-infix-ops.js index 81a95f83bf21..40071019480a 100644 --- a/lib/rules/space-infix-ops.js +++ b/lib/rules/space-infix-ops.js @@ -1,6 +1,7 @@ /** * @fileoverview Require spaces around infix operators * @author Michael Ficarra + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -13,6 +14,8 @@ const { isEqToken } = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/space-unary-ops.js b/lib/rules/space-unary-ops.js index 381381d6e5d2..aed43e7249eb 100644 --- a/lib/rules/space-unary-ops.js +++ b/lib/rules/space-unary-ops.js @@ -1,6 +1,7 @@ /** * @fileoverview This rule should require or disallow spaces before or after unary operations. * @author Marcin Kumorek + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -17,6 +18,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/spaced-comment.js b/lib/rules/spaced-comment.js index 2eb7f0e3008d..90ac7032d03f 100644 --- a/lib/rules/spaced-comment.js +++ b/lib/rules/spaced-comment.js @@ -1,6 +1,7 @@ /** * @fileoverview Source code for spaced-comments rule * @author Gyandeep Singh + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -149,6 +150,8 @@ function createNeverStylePattern(markers) { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "suggestion", docs: { diff --git a/lib/rules/switch-colon-spacing.js b/lib/rules/switch-colon-spacing.js index 45e401822a87..3ea63ca0eed4 100644 --- a/lib/rules/switch-colon-spacing.js +++ b/lib/rules/switch-colon-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to enforce spacing around colons of switch statements. * @author Toru Nagashima + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -18,6 +19,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/template-curly-spacing.js b/lib/rules/template-curly-spacing.js index cc9bbe306d51..1f8cc34c1d7c 100644 --- a/lib/rules/template-curly-spacing.js +++ b/lib/rules/template-curly-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to enforce spacing around embedded expressions of template strings * @author Toru Nagashima + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -18,6 +19,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/template-tag-spacing.js b/lib/rules/template-tag-spacing.js index 9bfdfc2288d1..52e0bcf20730 100644 --- a/lib/rules/template-tag-spacing.js +++ b/lib/rules/template-tag-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to check spacing between template tags and their literals * @author Jonathan Wilsson + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -12,6 +13,8 @@ /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/wrap-iife.js b/lib/rules/wrap-iife.js index 4c448fa79930..518071067db1 100644 --- a/lib/rules/wrap-iife.js +++ b/lib/rules/wrap-iife.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to flag when IIFE is not wrapped in parens * @author Ilya Volodin + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -40,6 +41,8 @@ function isCalleeOfNewExpression(node) { /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/wrap-regex.js b/lib/rules/wrap-regex.js index 8166c252f3c2..9e2808d60c70 100644 --- a/lib/rules/wrap-regex.js +++ b/lib/rules/wrap-regex.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to flag when regex literals are not wrapped in parens * @author Matt DuVall + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -12,6 +13,8 @@ /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/lib/rules/yield-star-spacing.js b/lib/rules/yield-star-spacing.js index 9f9d918ae6c5..9a67b78d25f0 100644 --- a/lib/rules/yield-star-spacing.js +++ b/lib/rules/yield-star-spacing.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to check the spacing around the * in yield* expressions. * @author Bryan Smith + * @deprecated in ESLint v8.53.0 */ "use strict"; @@ -12,6 +13,8 @@ /** @type {import('../shared/types').Rule} */ module.exports = { meta: { + deprecated: true, + replacedBy: [], type: "layout", docs: { diff --git a/packages/js/src/configs/eslint-all.js b/packages/js/src/configs/eslint-all.js index 52f580035a85..f2f7a664af6c 100644 --- a/packages/js/src/configs/eslint-all.js +++ b/packages/js/src/configs/eslint-all.js @@ -9,24 +9,13 @@ module.exports = Object.freeze({ "rules": { "accessor-pairs": "error", - "array-bracket-newline": "error", - "array-bracket-spacing": "error", "array-callback-return": "error", - "array-element-newline": "error", "arrow-body-style": "error", - "arrow-parens": "error", - "arrow-spacing": "error", "block-scoped-var": "error", - "block-spacing": "error", - "brace-style": "error", "camelcase": "error", "capitalized-comments": "error", "class-methods-use-this": "error", - "comma-dangle": "error", - "comma-spacing": "error", - "comma-style": "error", "complexity": "error", - "computed-property-spacing": "error", "consistent-return": "error", "consistent-this": "error", "constructor-super": "error", @@ -34,49 +23,30 @@ module.exports = Object.freeze({ "default-case": "error", "default-case-last": "error", "default-param-last": "error", - "dot-location": "error", "dot-notation": "error", - "eol-last": "error", "eqeqeq": "error", "for-direction": "error", - "func-call-spacing": "error", "func-name-matching": "error", "func-names": "error", "func-style": "error", - "function-call-argument-newline": "error", - "function-paren-newline": "error", - "generator-star-spacing": "error", "getter-return": "error", "grouped-accessor-pairs": "error", "guard-for-in": "error", "id-denylist": "error", "id-length": "error", "id-match": "error", - "implicit-arrow-linebreak": "error", - "indent": "error", "init-declarations": "error", - "jsx-quotes": "error", - "key-spacing": "error", - "keyword-spacing": "error", "line-comment-position": "error", - "linebreak-style": "error", - "lines-around-comment": "error", - "lines-between-class-members": "error", "logical-assignment-operators": "error", "max-classes-per-file": "error", "max-depth": "error", - "max-len": "error", "max-lines": "error", "max-lines-per-function": "error", "max-nested-callbacks": "error", "max-params": "error", "max-statements": "error", - "max-statements-per-line": "error", "multiline-comment-style": "error", - "multiline-ternary": "error", "new-cap": "error", - "new-parens": "error", - "newline-per-chained-call": "error", "no-alert": "error", "no-array-constructor": "error", "no-async-promise-executor": "error", @@ -87,7 +57,6 @@ module.exports = Object.freeze({ "no-class-assign": "error", "no-compare-neg-zero": "error", "no-cond-assign": "error", - "no-confusing-arrow": "error", "no-console": "error", "no-const-assign": "error", "no-constant-binary-expression": "error", @@ -117,10 +86,7 @@ module.exports = Object.freeze({ "no-extra-bind": "error", "no-extra-boolean-cast": "error", "no-extra-label": "error", - "no-extra-parens": "error", - "no-extra-semi": "error", "no-fallthrough": "error", - "no-floating-decimal": "error", "no-func-assign": "error", "no-global-assign": "error", "no-implicit-coercion": "error", @@ -141,12 +107,8 @@ module.exports = Object.freeze({ "no-loss-of-precision": "error", "no-magic-numbers": "error", "no-misleading-character-class": "error", - "no-mixed-operators": "error", - "no-mixed-spaces-and-tabs": "error", "no-multi-assign": "error", - "no-multi-spaces": "error", "no-multi-str": "error", - "no-multiple-empty-lines": "error", "no-negated-condition": "error", "no-nested-ternary": "error", "no-new": "error", @@ -180,12 +142,10 @@ module.exports = Object.freeze({ "no-shadow": "error", "no-shadow-restricted-names": "error", "no-sparse-arrays": "error", - "no-tabs": "error", "no-template-curly-in-string": "error", "no-ternary": "error", "no-this-before-super": "error", "no-throw-literal": "error", - "no-trailing-spaces": "error", "no-undef": "error", "no-undef-init": "error", "no-undefined": "error", @@ -215,19 +175,10 @@ module.exports = Object.freeze({ "no-var": "error", "no-void": "error", "no-warning-comments": "error", - "no-whitespace-before-property": "error", "no-with": "error", - "nonblock-statement-body-position": "error", - "object-curly-newline": "error", - "object-curly-spacing": "error", - "object-property-newline": "error", "object-shorthand": "error", "one-var": "error", - "one-var-declaration-per-line": "error", "operator-assignment": "error", - "operator-linebreak": "error", - "padded-blocks": "error", - "padding-line-between-statements": "error", "prefer-arrow-callback": "error", "prefer-const": "error", "prefer-destructuring": "error", @@ -241,38 +192,20 @@ module.exports = Object.freeze({ "prefer-rest-params": "error", "prefer-spread": "error", "prefer-template": "error", - "quote-props": "error", - "quotes": "error", "radix": "error", "require-atomic-updates": "error", "require-await": "error", "require-unicode-regexp": "error", "require-yield": "error", - "rest-spread-spacing": "error", - "semi": "error", - "semi-spacing": "error", - "semi-style": "error", "sort-imports": "error", "sort-keys": "error", "sort-vars": "error", - "space-before-blocks": "error", - "space-before-function-paren": "error", - "space-in-parens": "error", - "space-infix-ops": "error", - "space-unary-ops": "error", - "spaced-comment": "error", "strict": "error", - "switch-colon-spacing": "error", "symbol-description": "error", - "template-curly-spacing": "error", - "template-tag-spacing": "error", "unicode-bom": "error", "use-isnan": "error", "valid-typeof": "error", "vars-on-top": "error", - "wrap-iife": "error", - "wrap-regex": "error", - "yield-star-spacing": "error", "yoda": "error" } }); diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js index 430f7112e962..0622d48e9733 100644 --- a/tests/bin/eslint.js +++ b/tests/bin/eslint.js @@ -111,7 +111,12 @@ describe("bin/eslint.js", () => { fixableErrorCount: 0, fixableWarningCount: 0, output: "var foo = bar;\n", - usedDeprecatedRules: [] + usedDeprecatedRules: [ + { + ruleId: "no-extra-semi", + replacedBy: [] + } + ] } ]); diff --git a/tests/conf/eslint-all.js b/tests/conf/eslint-all.js index abee0c69b163..6475965a02ab 100644 --- a/tests/conf/eslint-all.js +++ b/tests/conf/eslint-all.js @@ -31,7 +31,7 @@ describe("eslint-all", () => { const someRule = "yoda"; assert.include(ruleNames, someRule); - assert.isAbove(count, 200); + assert.isBelow(count, 200); }); it("should configure all rules as errors", () => { diff --git a/tests/lib/cli-engine/cli-engine.js b/tests/lib/cli-engine/cli-engine.js index 5012867c3954..231501b364e3 100644 --- a/tests/lib/cli-engine/cli-engine.js +++ b/tests/lib/cli-engine/cli-engine.js @@ -360,7 +360,12 @@ describe("CLIEngine", () => { fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, - usedDeprecatedRules: [] + usedDeprecatedRules: [ + { + ruleId: "semi", + replacedBy: [] + } + ] }); }); @@ -725,7 +730,7 @@ describe("CLIEngine", () => { it("should return a `source` property when a parsing error has occurred", () => { engine = new CLIEngine({ useEslintrc: false, - rules: { semi: 2 } + rules: { eqeqeq: 2 } }); const report = engine.executeOnText("var bar = foothis is a syntax error.\n return bar;"); @@ -1774,7 +1779,7 @@ describe("CLIEngine", () => { engine = new CLIEngine({ cwd: originalDir, useEslintrc: false, - rules: { indent: 1, "valid-jsdoc": 0, "require-jsdoc": 0 } + rules: { eqeqeq: 1, "valid-jsdoc": 0, "require-jsdoc": 0 } }); const report = engine.executeOnFiles(["lib/cli*.js"]); diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 60480b7cc141..b9db3543f7c4 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -297,7 +297,9 @@ describe("ESLint", () => { assert.strictEqual(results[0].messages[4].ruleId, "eol-last"); assert.strictEqual(results[0].fixableErrorCount, 3); assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 2); + assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); + assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); }); it("should report the total and per file warnings", async () => { @@ -324,7 +326,9 @@ describe("ESLint", () => { assert.strictEqual(results[0].messages[4].ruleId, "eol-last"); assert.strictEqual(results[0].fixableErrorCount, 0); assert.strictEqual(results[0].fixableWarningCount, 3); - assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 2); + assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); + assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); }); }); @@ -343,7 +347,8 @@ describe("ESLint", () => { assert.strictEqual(results[0].errorCount, 1); assert.strictEqual(results[0].fixableErrorCount, 1); assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 1); + assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); }); it("should report the filename when passed in", async () => { @@ -455,7 +460,10 @@ describe("ESLint", () => { fixableErrorCount: 0, fixableWarningCount: 0, output: "var bar = foo;", - usedDeprecatedRules: [] + usedDeprecatedRules: [{ + ruleId: "semi", + replacedBy: [] + }] } ]); }); @@ -488,14 +496,14 @@ describe("ESLint", () => { cwd: getFixturePath() }); const options = { filePath: "file.js" }; - const results = await eslint.lintText("foo ()", options); + const results = await eslint.lintText("if (true) { foo() }", options); assert.strictEqual(results.length, 1); const { messages } = results[0]; // Some rules that should report errors in the given code. Not all, as we don't want to update this test when we add new rules. - const expectedRules = ["no-undef", "semi", "func-call-spacing"]; + const expectedRules = ["no-undef", "no-constant-condition"]; expectedRules.forEach(ruleId => { const messageFromRule = messages.find(message => message.ruleId === ruleId); @@ -853,7 +861,7 @@ describe("ESLint", () => { eslint = new ESLint({ useEslintrc: false, overrideConfig: { - rules: { semi: 2 } + rules: { eqeqeq: 2 } } }); const results = await eslint.lintText("var bar = foothis is a syntax error.\n return bar;"); @@ -1762,7 +1770,7 @@ describe("ESLint", () => { cwd: originalDir, useEslintrc: false, overrideConfig: { - rules: { indent: 1, "valid-jsdoc": 0, "require-jsdoc": 0 } + rules: { eqeqeq: 1, "valid-jsdoc": 0, "require-jsdoc": 0 } } }); const results = await eslint.lintFiles(["lib/cli*.js"]); @@ -1827,7 +1835,20 @@ describe("ESLint", () => { fixableErrorCount: 0, fixableWarningCount: 0, output: "true ? \"yes\" : \"no\";\n", - usedDeprecatedRules: [] + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] }, { filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/ok.js")), @@ -1838,7 +1859,20 @@ describe("ESLint", () => { fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, - usedDeprecatedRules: [] + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] }, { filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes-semi-eqeqeq.js")), @@ -1862,7 +1896,20 @@ describe("ESLint", () => { fixableErrorCount: 0, fixableWarningCount: 0, output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n", - usedDeprecatedRules: [] + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] }, { filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes.js")), @@ -1886,7 +1933,20 @@ describe("ESLint", () => { fixableErrorCount: 0, fixableWarningCount: 0, output: "var msg = \"hi\" + foo;\n", - usedDeprecatedRules: [] + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] } ]); }); diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index 5cfeca351e70..f9f36d909fd8 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -299,7 +299,9 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].messages[3].ruleId, "eol-last"); assert.strictEqual(results[0].fixableErrorCount, 3); assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 2); + assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); + assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); assert.strictEqual(results[0].suppressedMessages.length, 0); }); @@ -325,7 +327,9 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].messages[3].ruleId, "eol-last"); assert.strictEqual(results[0].fixableErrorCount, 0); assert.strictEqual(results[0].fixableWarningCount, 3); - assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 2); + assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); + assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); assert.strictEqual(results[0].suppressedMessages.length, 0); }); @@ -344,7 +348,8 @@ describe("FlatESLint", () => { assert.strictEqual(results[0].fixableErrorCount, 1); assert.strictEqual(results[0].warningCount, 0); assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 1); + assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); assert.strictEqual(results[0].suppressedMessages.length, 0); }); @@ -496,7 +501,12 @@ describe("FlatESLint", () => { fixableErrorCount: 0, fixableWarningCount: 0, output: "var bar = foo;", - usedDeprecatedRules: [] + usedDeprecatedRules: [ + { + ruleId: "semi", + replacedBy: [] + } + ] } ]); }); @@ -686,7 +696,7 @@ describe("FlatESLint", () => { eslint = new FlatESLint({ overrideConfigFile: true, overrideConfig: { - rules: { semi: 2 } + rules: { eqeqeq: 2 } } }); const results = await eslint.lintText("var bar = foothis is a syntax error.\n return bar;"); @@ -1956,7 +1966,7 @@ describe("FlatESLint", () => { cwd: originalDir, overrideConfigFile: true, overrideConfig: { - rules: { indent: 1, "valid-jsdoc": 0, "require-jsdoc": 0 } + rules: { eqeqeq: 1, "valid-jsdoc": 0, "require-jsdoc": 0 } } }); const results = await eslint.lintFiles(["lib/cli*.js"]); @@ -2051,7 +2061,20 @@ describe("FlatESLint", () => { fixableErrorCount: 0, fixableWarningCount: 0, output: "true ? \"yes\" : \"no\";\n", - usedDeprecatedRules: [] + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] }, { filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/ok.js")), @@ -2062,7 +2085,20 @@ describe("FlatESLint", () => { fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, - usedDeprecatedRules: [] + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] }, { filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes-semi-eqeqeq.js")), @@ -2086,7 +2122,20 @@ describe("FlatESLint", () => { fixableErrorCount: 0, fixableWarningCount: 0, output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n", - usedDeprecatedRules: [] + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] }, { filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes.js")), @@ -2110,7 +2159,20 @@ describe("FlatESLint", () => { fixableErrorCount: 0, fixableWarningCount: 0, output: "var msg = \"hi\" + foo;\n", - usedDeprecatedRules: [] + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] } ]); }); From ab8c60d4f859cec787b5a12f7271b40e666235f5 Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Sat, 4 Nov 2023 01:17:22 +0530 Subject: [PATCH 242/248] docs: change position of return to top button (#17688) --- docs/src/assets/scss/docs.scss | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/docs/src/assets/scss/docs.scss b/docs/src/assets/scss/docs.scss index 3709a9cebf13..a59742475da1 100644 --- a/docs/src/assets/scss/docs.scss +++ b/docs/src/assets/scss/docs.scss @@ -166,7 +166,7 @@ pre[class*="language-"] { height: 50px; display: none; position: fixed; - right: 50px; + right: 19.8vw; bottom: 35px; z-index: 1; font-size: 1.5rem; @@ -177,7 +177,35 @@ pre[class*="language-"] { align-items: center; background-color: var(--link-color); - @media (max-width: 800px) { + @media (max-width: 1299px) { + right: 18.99vw; + } + + @media (max-width: 1100px) { + right: 19.4vw; + } + + @media (max-width: 1060px) { + right: 19.9vw; + } + + @media (max-width: 1024px) { + right: 22vw; + } + + @media (max-width: 860px) { + right: 22.2vw; + } + + @media (max-width: 850px) { + right: 22.6vw; + } + + @media (max-width: 820px) { + right: 23.4vw; + } + + @media (max-width: 799px) { right: 35px; } From 93256a32e312f3f4e5c532762df71bdc06bded20 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 3 Nov 2023 16:54:02 -0400 Subject: [PATCH 243/248] 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 f9d8a1c66925..529c630dd996 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "8.52.0", + "version": "8.53.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 0d0733882944b4849d71a40723c251213698cef9 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 3 Nov 2023 22:14:36 +0100 Subject: [PATCH 244/248] chore: Update dependencies (#17706) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 45a42d1bf2c5..7c4775779029 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,8 @@ "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", From ba4d4d567a82554250dd8c7933322824e6a73944 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sat, 4 Nov 2023 00:07:49 +0100 Subject: [PATCH 245/248] chore: remove metascraper (#17707) --- eslint.config.js | 3 ++- package.json | 10 ---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 40ebe8c08f7a..8bb053b2028a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -90,7 +90,8 @@ module.exports = [ "tests/performance/**", "tmp/**", "tools/internal-rules/node_modules/**", - "**/test.js" + "**/test.js", + "tools/fetch-docs-links.js" ] }, { diff --git a/package.json b/package.json index 7c4775779029..6d618c10beab 100644 --- a/package.json +++ b/package.json @@ -41,10 +41,6 @@ "node tools/update-eslint-all.js", "git add packages/js/src/configs/eslint-all.js" ], - "docs/src/rules/*.md": [ - "node tools/fetch-docs-links.js", - "git add docs/src/_data/further_reading_links.json" - ], "docs/**/*.svg": "npx svgo -r --multipass" }, "files": [ @@ -136,12 +132,6 @@ "markdownlint-cli": "^0.31.1", "marked": "^4.0.8", "memfs": "^3.0.1", - "metascraper": "^5.25.7", - "metascraper-description": "^5.25.7", - "metascraper-image": "^5.29.3", - "metascraper-logo": "^5.25.7", - "metascraper-logo-favicon": "^5.25.7", - "metascraper-title": "^5.25.7", "mocha": "^8.3.2", "mocha-junit-reporter": "^2.0.0", "node-polyfill-webpack-plugin": "^1.0.3", From ec361d14b0c623ea3ae569c784608431454f33e2 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 3 Nov 2023 19:23:37 -0400 Subject: [PATCH 246/248] Build: changelog update for 8.53.0 --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cbca458d6c1..cb64e16b2c47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +v8.53.0 - November 3, 2023 + +* [`ba4d4d5`](https://github.com/eslint/eslint/commit/ba4d4d567a82554250dd8c7933322824e6a73944) chore: remove metascraper (#17707) (Milos Djermanovic) +* [`0d07338`](https://github.com/eslint/eslint/commit/0d0733882944b4849d71a40723c251213698cef9) chore: Update dependencies (#17706) (Milos Djermanovic) +* [`93256a3`](https://github.com/eslint/eslint/commit/93256a32e312f3f4e5c532762df71bdc06bded20) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`ab8c60d`](https://github.com/eslint/eslint/commit/ab8c60d4f859cec787b5a12f7271b40e666235f5) docs: change position of return to top button (#17688) (Tanuj Kanti) +* [`528e1c0`](https://github.com/eslint/eslint/commit/528e1c00dc2aa8636e5b706c4270dc655cfa17e3) feat: Deprecate formatting rules (#17696) (Nicholas C. Zakas) +* [`485ec7d`](https://github.com/eslint/eslint/commit/485ec7d08ed2040c292f52bf9b9152f6c8ef4809) test: fix ESLint tests for caching (#17699) (Milos Djermanovic) +* [`c0b11dd`](https://github.com/eslint/eslint/commit/c0b11ddb9f8aacc64c3933b9f278939aa7bea481) feat: Add suggestions for no-prototype-builtins (#17677) (Yonathan Randolph) +* [`4fc44c0`](https://github.com/eslint/eslint/commit/4fc44c0b8c5dca466bffdfe01dfd80794d7762b7) docs: update twitter icon to new X icon (#17687) (Tanuj Kanti) +* [`1ad6257`](https://github.com/eslint/eslint/commit/1ad6257744d63281235fcc33288394b1d69b34ce) fix: ensure that exit code for fatal errors is not overwritten (#17683) (Milos Djermanovic) +* [`4164b2c`](https://github.com/eslint/eslint/commit/4164b2ceec89726b18ea0b0e34fab05735d55a09) docs: Update README (GitHub Actions Bot) +* [`8651895`](https://github.com/eslint/eslint/commit/8651895ca7ae15e13d74c8be67d9eebd63a7ce1f) docs: Fix tabs in rule examples (#17653) (Francesco Trotta) +* [`3aec1c5`](https://github.com/eslint/eslint/commit/3aec1c55ba2c6d2833e1c0afe0a58f0cc6bbc0a4) docs: explained rule fixers and suggestions (#17657) (Josh Goldberg โœจ) +* [`db06a7f`](https://github.com/eslint/eslint/commit/db06a7ff7992a74368f03d1f21beb00df0407021) ci: bump actions/setup-node from 3 to 4 (#17676) (dependabot[bot]) +* [`b329ea7`](https://github.com/eslint/eslint/commit/b329ea748dff45f11c7e218208244dc24fcb5c8f) fix: add `;` after JSX nodes in `no-object-constructor` autofix (#17672) (Francesco Trotta) +* [`994596b`](https://github.com/eslint/eslint/commit/994596b07f5ff20a615a4be1ea03e5fd59cdb84b) ci: run tests in Node.js 21 (#17673) (Francesco Trotta) + v8.52.0 - October 20, 2023 * [`6d1f0c2`](https://github.com/eslint/eslint/commit/6d1f0c2da0309c06c21149b8d71a8f439a70d7e8) chore: upgrade @eslint/js@8.52.0 (#17671) (Milos Djermanovic) From cb9393dd9f160d3dbff27a39b9ce8e24303e3b6f Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 3 Nov 2023 19:23:38 -0400 Subject: [PATCH 247/248] 8.53.0 --- docs/package.json | 2 +- docs/src/_data/rules.json | 743 +++++++----------- docs/src/_data/rules_meta.json | 137 +++- .../formatters/html-formatter-example.html | 2 +- docs/src/use/formatters/index.md | 6 + package.json | 2 +- 6 files changed, 416 insertions(+), 476 deletions(-) diff --git a/docs/package.json b/docs/package.json index fa9d7101dd4a..0619d4d5b8f9 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.52.0", + "version": "8.53.0", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 3565b9ca65e7..33915152ad63 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -258,7 +258,7 @@ "description": "Disallow calling some `Object.prototype` methods directly on objects", "recommended": true, "fixable": false, - "hasSuggestions": false + "hasSuggestions": true }, { "name": "no-self-assign", @@ -682,13 +682,6 @@ "fixable": false, "hasSuggestions": false }, - { - "name": "no-confusing-arrow", - "description": "Disallow arrow functions where they could be confused with comparisons", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, { "name": "no-console", "description": "Disallow the use of `console`", @@ -787,20 +780,6 @@ "fixable": true, "hasSuggestions": false }, - { - "name": "no-extra-semi", - "description": "Disallow unnecessary semicolons", - "recommended": true, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-floating-decimal", - "description": "Disallow leading or trailing decimal points in numeric literals", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, { "name": "no-global-assign", "description": "Disallow assignments to native objects or read-only global variables", @@ -892,13 +871,6 @@ "fixable": false, "hasSuggestions": false }, - { - "name": "no-mixed-operators", - "description": "Disallow mixed binary operators", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, { "name": "no-multi-assign", "description": "Disallow use of chained assignment expressions", @@ -1235,13 +1207,6 @@ "fixable": true, "hasSuggestions": false }, - { - "name": "one-var-declaration-per-line", - "description": "Require or disallow newlines around variable declarations", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, { "name": "operator-assignment", "description": "Require or disallow assignment operator shorthand where possible", @@ -1340,13 +1305,6 @@ "fixable": true, "hasSuggestions": false }, - { - "name": "quote-props", - "description": "Require quotes around object literal property names", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, { "name": "radix", "description": "Enforce the consistent use of the radix argument when using `parseInt()`", @@ -1396,13 +1354,6 @@ "fixable": true, "hasSuggestions": false }, - { - "name": "spaced-comment", - "description": "Enforce consistent spacing after the `//` or `/*` in a comment", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, { "name": "strict", "description": "Require or disallow strict mode directives", @@ -1433,160 +1384,6 @@ } ], "layout": [ - { - "name": "array-bracket-newline", - "description": "Enforce linebreaks after opening and before closing array brackets", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "array-bracket-spacing", - "description": "Enforce consistent spacing inside array brackets", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "array-element-newline", - "description": "Enforce line breaks after each array element", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "arrow-parens", - "description": "Require parentheses around arrow function arguments", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "arrow-spacing", - "description": "Enforce consistent spacing before and after the arrow in arrow functions", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "block-spacing", - "description": "Disallow or enforce spaces inside of blocks after opening block and before closing block", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "brace-style", - "description": "Enforce consistent brace style for blocks", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "comma-dangle", - "description": "Require or disallow trailing commas", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "comma-spacing", - "description": "Enforce consistent spacing before and after commas", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "comma-style", - "description": "Enforce consistent comma style", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "computed-property-spacing", - "description": "Enforce consistent spacing inside computed property brackets", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "dot-location", - "description": "Enforce consistent newlines before and after dots", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "eol-last", - "description": "Require or disallow newline at the end of files", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "func-call-spacing", - "description": "Require or disallow spacing between function identifiers and their invocations", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "function-call-argument-newline", - "description": "Enforce line breaks between arguments of a function call", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "function-paren-newline", - "description": "Enforce consistent line breaks inside function parentheses", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "generator-star-spacing", - "description": "Enforce consistent spacing around `*` operators in generator functions", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "implicit-arrow-linebreak", - "description": "Enforce the location of arrow function bodies", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "indent", - "description": "Enforce consistent indentation", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "jsx-quotes", - "description": "Enforce the consistent use of either double or single quotes in JSX attributes", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "key-spacing", - "description": "Enforce consistent spacing between keys and values in object literal properties", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "keyword-spacing", - "description": "Enforce consistent spacing before and after keywords", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, { "name": "line-comment-position", "description": "Enforce position of line comments", @@ -1595,274 +1392,8 @@ "hasSuggestions": false }, { - "name": "linebreak-style", - "description": "Enforce consistent linebreak style", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "lines-around-comment", - "description": "Require empty lines around comments", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "lines-between-class-members", - "description": "Require or disallow an empty line between class members", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "max-len", - "description": "Enforce a maximum line length", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "max-statements-per-line", - "description": "Enforce a maximum number of statements allowed per line", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "multiline-ternary", - "description": "Enforce newlines between operands of ternary expressions", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "new-parens", - "description": "Enforce or disallow parentheses when invoking a constructor with no arguments", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "newline-per-chained-call", - "description": "Require a newline after each call in a method chain", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-extra-parens", - "description": "Disallow unnecessary parentheses", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-mixed-spaces-and-tabs", - "description": "Disallow mixed spaces and tabs for indentation", - "recommended": true, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-multi-spaces", - "description": "Disallow multiple spaces", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-multiple-empty-lines", - "description": "Disallow multiple empty lines", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-tabs", - "description": "Disallow all tabs", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-trailing-spaces", - "description": "Disallow trailing whitespace at the end of lines", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "no-whitespace-before-property", - "description": "Disallow whitespace before properties", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "nonblock-statement-body-position", - "description": "Enforce the location of single-line statements", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "object-curly-newline", - "description": "Enforce consistent line breaks after opening and before closing braces", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "object-curly-spacing", - "description": "Enforce consistent spacing inside braces", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "object-property-newline", - "description": "Enforce placing object properties on separate lines", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "operator-linebreak", - "description": "Enforce consistent linebreak style for operators", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "padded-blocks", - "description": "Require or disallow padding within blocks", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "padding-line-between-statements", - "description": "Require or disallow padding lines between statements", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "quotes", - "description": "Enforce the consistent use of either backticks, double, or single quotes", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "rest-spread-spacing", - "description": "Enforce spacing between rest and spread operators and their expressions", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "semi", - "description": "Require or disallow semicolons instead of ASI", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "semi-spacing", - "description": "Enforce consistent spacing before and after semicolons", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "semi-style", - "description": "Enforce location of semicolons", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "space-before-blocks", - "description": "Enforce consistent spacing before blocks", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "space-before-function-paren", - "description": "Enforce consistent spacing before `function` definition opening parenthesis", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "space-in-parens", - "description": "Enforce consistent spacing inside parentheses", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "space-infix-ops", - "description": "Require spacing around infix operators", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "space-unary-ops", - "description": "Enforce consistent spacing before or after unary operators", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "switch-colon-spacing", - "description": "Enforce spacing around colons of switch statements", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "template-curly-spacing", - "description": "Require or disallow spacing around embedded expressions of template strings", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "template-tag-spacing", - "description": "Require or disallow spacing between template tags and their literals", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "unicode-bom", - "description": "Require or disallow Unicode byte order mark (BOM)", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "wrap-iife", - "description": "Require parentheses around immediate `function` invocations", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "wrap-regex", - "description": "Require parenthesis around regex literals", - "recommended": false, - "fixable": true, - "hasSuggestions": false - }, - { - "name": "yield-star-spacing", - "description": "Require or disallow spacing around the `*` in `yield*` expressions", + "name": "unicode-bom", + "description": "Require or disallow Unicode byte order mark (BOM)", "recommended": false, "fixable": true, "hasSuggestions": false @@ -1870,10 +1401,78 @@ ] }, "deprecated": [ + { + "name": "array-bracket-newline", + "replacedBy": [] + }, + { + "name": "array-bracket-spacing", + "replacedBy": [] + }, + { + "name": "array-element-newline", + "replacedBy": [] + }, + { + "name": "arrow-parens", + "replacedBy": [] + }, + { + "name": "arrow-spacing", + "replacedBy": [] + }, + { + "name": "block-spacing", + "replacedBy": [] + }, + { + "name": "brace-style", + "replacedBy": [] + }, { "name": "callback-return", "replacedBy": [] }, + { + "name": "comma-dangle", + "replacedBy": [] + }, + { + "name": "comma-spacing", + "replacedBy": [] + }, + { + "name": "comma-style", + "replacedBy": [] + }, + { + "name": "computed-property-spacing", + "replacedBy": [] + }, + { + "name": "dot-location", + "replacedBy": [] + }, + { + "name": "eol-last", + "replacedBy": [] + }, + { + "name": "func-call-spacing", + "replacedBy": [] + }, + { + "name": "function-call-argument-newline", + "replacedBy": [] + }, + { + "name": "function-paren-newline", + "replacedBy": [] + }, + { + "name": "generator-star-spacing", + "replacedBy": [] + }, { "name": "global-require", "replacedBy": [] @@ -1888,18 +1487,66 @@ "id-denylist" ] }, + { + "name": "implicit-arrow-linebreak", + "replacedBy": [] + }, + { + "name": "indent", + "replacedBy": [] + }, { "name": "indent-legacy", "replacedBy": [ "indent" ] }, + { + "name": "jsx-quotes", + "replacedBy": [] + }, + { + "name": "key-spacing", + "replacedBy": [] + }, + { + "name": "keyword-spacing", + "replacedBy": [] + }, + { + "name": "linebreak-style", + "replacedBy": [] + }, + { + "name": "lines-around-comment", + "replacedBy": [] + }, { "name": "lines-around-directive", "replacedBy": [ "padding-line-between-statements" ] }, + { + "name": "lines-between-class-members", + "replacedBy": [] + }, + { + "name": "max-len", + "replacedBy": [] + }, + { + "name": "max-statements-per-line", + "replacedBy": [] + }, + { + "name": "multiline-ternary", + "replacedBy": [] + }, + { + "name": "new-parens", + "replacedBy": [] + }, { "name": "newline-after-var", "replacedBy": [ @@ -1912,6 +1559,10 @@ "padding-line-between-statements" ] }, + { + "name": "newline-per-chained-call", + "replacedBy": [] + }, { "name": "no-buffer-constructor", "replacedBy": [] @@ -1922,10 +1573,42 @@ "no-shadow" ] }, + { + "name": "no-confusing-arrow", + "replacedBy": [] + }, + { + "name": "no-extra-parens", + "replacedBy": [] + }, + { + "name": "no-extra-semi", + "replacedBy": [] + }, + { + "name": "no-floating-decimal", + "replacedBy": [] + }, + { + "name": "no-mixed-operators", + "replacedBy": [] + }, { "name": "no-mixed-requires", "replacedBy": [] }, + { + "name": "no-mixed-spaces-and-tabs", + "replacedBy": [] + }, + { + "name": "no-multi-spaces", + "replacedBy": [] + }, + { + "name": "no-multiple-empty-lines", + "replacedBy": [] + }, { "name": "no-native-reassign", "replacedBy": [ @@ -1978,17 +1661,133 @@ "name": "no-sync", "replacedBy": [] }, + { + "name": "no-tabs", + "replacedBy": [] + }, + { + "name": "no-trailing-spaces", + "replacedBy": [] + }, + { + "name": "no-whitespace-before-property", + "replacedBy": [] + }, + { + "name": "nonblock-statement-body-position", + "replacedBy": [] + }, + { + "name": "object-curly-newline", + "replacedBy": [] + }, + { + "name": "object-curly-spacing", + "replacedBy": [] + }, + { + "name": "object-property-newline", + "replacedBy": [] + }, + { + "name": "one-var-declaration-per-line", + "replacedBy": [] + }, + { + "name": "operator-linebreak", + "replacedBy": [] + }, + { + "name": "padded-blocks", + "replacedBy": [] + }, + { + "name": "padding-line-between-statements", + "replacedBy": [] + }, { "name": "prefer-reflect", "replacedBy": [] }, + { + "name": "quote-props", + "replacedBy": [] + }, + { + "name": "quotes", + "replacedBy": [] + }, { "name": "require-jsdoc", "replacedBy": [] }, + { + "name": "rest-spread-spacing", + "replacedBy": [] + }, + { + "name": "semi", + "replacedBy": [] + }, + { + "name": "semi-spacing", + "replacedBy": [] + }, + { + "name": "semi-style", + "replacedBy": [] + }, + { + "name": "space-before-blocks", + "replacedBy": [] + }, + { + "name": "space-before-function-paren", + "replacedBy": [] + }, + { + "name": "space-in-parens", + "replacedBy": [] + }, + { + "name": "space-infix-ops", + "replacedBy": [] + }, + { + "name": "space-unary-ops", + "replacedBy": [] + }, + { + "name": "spaced-comment", + "replacedBy": [] + }, + { + "name": "switch-colon-spacing", + "replacedBy": [] + }, + { + "name": "template-curly-spacing", + "replacedBy": [] + }, + { + "name": "template-tag-spacing", + "replacedBy": [] + }, { "name": "valid-jsdoc", "replacedBy": [] + }, + { + "name": "wrap-iife", + "replacedBy": [] + }, + { + "name": "wrap-regex", + "replacedBy": [] + }, + { + "name": "yield-star-spacing", + "replacedBy": [] } ], "removed": [ diff --git a/docs/src/_data/rules_meta.json b/docs/src/_data/rules_meta.json index f40044241240..a00b86c70667 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -8,6 +8,8 @@ } }, "array-bracket-newline": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce linebreaks after opening and before closing array brackets", @@ -17,6 +19,8 @@ "fixable": "whitespace" }, "array-bracket-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent spacing inside array brackets", @@ -35,6 +39,8 @@ "hasSuggestions": true }, "array-element-newline": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce line breaks after each array element", @@ -53,6 +59,8 @@ "fixable": "code" }, "arrow-parens": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require parentheses around arrow function arguments", @@ -62,6 +70,8 @@ "fixable": "code" }, "arrow-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent spacing before and after the arrow in arrow functions", @@ -79,6 +89,8 @@ } }, "block-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Disallow or enforce spaces inside of blocks after opening block and before closing block", @@ -88,6 +100,8 @@ "fixable": "whitespace" }, "brace-style": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent brace style for blocks", @@ -132,6 +146,8 @@ } }, "comma-dangle": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require or disallow trailing commas", @@ -141,6 +157,8 @@ "fixable": "code" }, "comma-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent spacing before and after commas", @@ -150,6 +168,8 @@ "fixable": "whitespace" }, "comma-style": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent comma style", @@ -167,6 +187,8 @@ } }, "computed-property-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent spacing inside computed property brackets", @@ -233,6 +255,8 @@ } }, "dot-location": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent newlines before and after dots", @@ -251,6 +275,8 @@ "fixable": "code" }, "eol-last": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require or disallow newline at the end of files", @@ -278,6 +304,8 @@ "fixable": null }, "func-call-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require or disallow spacing between function identifiers and their invocations", @@ -311,6 +339,8 @@ } }, "function-call-argument-newline": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce line breaks between arguments of a function call", @@ -320,6 +350,8 @@ "fixable": "whitespace" }, "function-paren-newline": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent line breaks inside function parentheses", @@ -329,6 +361,8 @@ "fixable": "whitespace" }, "generator-star-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent spacing around `*` operators in generator functions", @@ -419,6 +453,8 @@ } }, "implicit-arrow-linebreak": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce the location of arrow function bodies", @@ -428,6 +464,8 @@ "fixable": "whitespace" }, "indent": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent indentation", @@ -458,6 +496,8 @@ } }, "jsx-quotes": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce the consistent use of either double or single quotes in JSX attributes", @@ -467,6 +507,8 @@ "fixable": "whitespace" }, "key-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent spacing between keys and values in object literal properties", @@ -476,6 +518,8 @@ "fixable": "whitespace" }, "keyword-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent spacing before and after keywords", @@ -493,6 +537,8 @@ } }, "linebreak-style": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent linebreak style", @@ -502,6 +548,8 @@ "fixable": "whitespace" }, "lines-around-comment": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require empty lines around comments", @@ -524,6 +572,8 @@ ] }, "lines-between-class-members": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require or disallow an empty line between class members", @@ -559,6 +609,8 @@ } }, "max-len": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce a maximum line length", @@ -607,6 +659,8 @@ } }, "max-statements-per-line": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce a maximum number of statements allowed per line", @@ -624,6 +678,8 @@ "fixable": "whitespace" }, "multiline-ternary": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce newlines between operands of ternary expressions", @@ -641,6 +697,8 @@ } }, "new-parens": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce or disallow parentheses when invoking a constructor with no arguments", @@ -676,6 +734,8 @@ ] }, "newline-per-chained-call": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require a newline after each call in a method chain", @@ -789,6 +849,8 @@ } }, "no-confusing-arrow": { + "deprecated": true, + "replacedBy": [], "type": "suggestion", "docs": { "description": "Disallow arrow functions where they could be confused with comparisons", @@ -1038,6 +1100,8 @@ "fixable": "code" }, "no-extra-parens": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Disallow unnecessary parentheses", @@ -1047,6 +1111,8 @@ "fixable": "code" }, "no-extra-semi": { + "deprecated": true, + "replacedBy": [], "type": "suggestion", "docs": { "description": "Disallow unnecessary semicolons", @@ -1064,6 +1130,8 @@ } }, "no-floating-decimal": { + "deprecated": true, + "replacedBy": [], "type": "suggestion", "docs": { "description": "Disallow leading or trailing decimal points in numeric literals", @@ -1236,6 +1304,8 @@ "hasSuggestions": true }, "no-mixed-operators": { + "deprecated": true, + "replacedBy": [], "type": "suggestion", "docs": { "description": "Disallow mixed binary operators", @@ -1254,6 +1324,8 @@ } }, "no-mixed-spaces-and-tabs": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Disallow mixed spaces and tabs for indentation", @@ -1270,6 +1342,8 @@ } }, "no-multi-spaces": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Disallow multiple spaces", @@ -1287,6 +1361,8 @@ } }, "no-multiple-empty-lines": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Disallow multiple empty lines", @@ -1508,7 +1584,8 @@ "description": "Disallow calling some `Object.prototype` methods directly on objects", "recommended": true, "url": "https://eslint.org/docs/latest/rules/no-prototype-builtins" - } + }, + "hasSuggestions": true }, "no-redeclare": { "type": "suggestion", @@ -1685,6 +1762,8 @@ } }, "no-tabs": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Disallow all tabs", @@ -1725,6 +1804,8 @@ } }, "no-trailing-spaces": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Disallow trailing whitespace at the end of lines", @@ -1977,6 +2058,8 @@ } }, "no-whitespace-before-property": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Disallow whitespace before properties", @@ -1994,6 +2077,8 @@ } }, "nonblock-statement-body-position": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce the location of single-line statements", @@ -2003,6 +2088,8 @@ "fixable": "whitespace" }, "object-curly-newline": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent line breaks after opening and before closing braces", @@ -2012,6 +2099,8 @@ "fixable": "whitespace" }, "object-curly-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent spacing inside braces", @@ -2021,6 +2110,8 @@ "fixable": "whitespace" }, "object-property-newline": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce placing object properties on separate lines", @@ -2048,6 +2139,8 @@ "fixable": "code" }, "one-var-declaration-per-line": { + "deprecated": true, + "replacedBy": [], "type": "suggestion", "docs": { "description": "Require or disallow newlines around variable declarations", @@ -2066,6 +2159,8 @@ "fixable": "code" }, "operator-linebreak": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent linebreak style for operators", @@ -2075,6 +2170,8 @@ "fixable": "code" }, "padded-blocks": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require or disallow padding within blocks", @@ -2084,6 +2181,8 @@ "fixable": "whitespace" }, "padding-line-between-statements": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require or disallow padding lines between statements", @@ -2219,6 +2318,8 @@ "fixable": "code" }, "quote-props": { + "deprecated": true, + "replacedBy": [], "type": "suggestion", "docs": { "description": "Require quotes around object literal property names", @@ -2228,6 +2329,8 @@ "fixable": "code" }, "quotes": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce the consistent use of either backticks, double, or single quotes", @@ -2290,6 +2393,8 @@ } }, "rest-spread-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce spacing between rest and spread operators and their expressions", @@ -2299,6 +2404,8 @@ "fixable": "whitespace" }, "semi": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require or disallow semicolons instead of ASI", @@ -2308,6 +2415,8 @@ "fixable": "code" }, "semi-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent spacing before and after semicolons", @@ -2317,6 +2426,8 @@ "fixable": "whitespace" }, "semi-style": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce location of semicolons", @@ -2352,6 +2463,8 @@ "fixable": "code" }, "space-before-blocks": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent spacing before blocks", @@ -2361,6 +2474,8 @@ "fixable": "whitespace" }, "space-before-function-paren": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent spacing before `function` definition opening parenthesis", @@ -2370,6 +2485,8 @@ "fixable": "whitespace" }, "space-in-parens": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent spacing inside parentheses", @@ -2379,6 +2496,8 @@ "fixable": "whitespace" }, "space-infix-ops": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require spacing around infix operators", @@ -2388,6 +2507,8 @@ "fixable": "whitespace" }, "space-unary-ops": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent spacing before or after unary operators", @@ -2397,6 +2518,8 @@ "fixable": "whitespace" }, "spaced-comment": { + "deprecated": true, + "replacedBy": [], "type": "suggestion", "docs": { "description": "Enforce consistent spacing after the `//` or `/*` in a comment", @@ -2415,6 +2538,8 @@ "fixable": "code" }, "switch-colon-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce spacing around colons of switch statements", @@ -2433,6 +2558,8 @@ "fixable": null }, "template-curly-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require or disallow spacing around embedded expressions of template strings", @@ -2442,6 +2569,8 @@ "fixable": "whitespace" }, "template-tag-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require or disallow spacing between template tags and their literals", @@ -2496,6 +2625,8 @@ } }, "wrap-iife": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require parentheses around immediate `function` invocations", @@ -2505,6 +2636,8 @@ "fixable": "code" }, "wrap-regex": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require parenthesis around regex literals", @@ -2514,6 +2647,8 @@ "fixable": "code" }, "yield-star-spacing": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require or disallow spacing around the `*` in `yield*` expressions", diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index 05cbe6e08b39..03dc7dcefefe 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

    - 9 problems (5 errors, 4 warnings) - Generated on Fri Oct 20 2023 17:00:05 GMT-0400 (Eastern Daylight Time) + 9 problems (5 errors, 4 warnings) - Generated on Fri Nov 03 2023 19:23:39 GMT-0400 (Eastern Daylight Time)
diff --git a/docs/src/use/formatters/index.md b/docs/src/use/formatters/index.md index 95a930aa3f36..6d853e16785e 100644 --- a/docs/src/use/formatters/index.md +++ b/docs/src/use/formatters/index.md @@ -305,6 +305,8 @@ Example output (formatted for easier reading): } }, "indent": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent indentation", @@ -576,6 +578,8 @@ Example output (formatted for easier reading): } }, "space-unary-ops": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Enforce consistent spacing before or after unary operators", @@ -615,6 +619,8 @@ Example output (formatted for easier reading): } }, "semi": { + "deprecated": true, + "replacedBy": [], "type": "layout", "docs": { "description": "Require or disallow semicolons instead of ASI", diff --git a/package.json b/package.json index 6d618c10beab..5fdd58eedcb1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.52.0", + "version": "8.53.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 5454c22b24f39be2dac7f28cfcfdb6c753faaf4e Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Sat, 4 Nov 2023 01:33:48 +0100 Subject: [PATCH 248/248] Revert "chore: remove metascraper (#17707)" (#17708) This reverts commit ba4d4d567a82554250dd8c7933322824e6a73944. --- eslint.config.js | 3 +-- package.json | 10 ++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 8bb053b2028a..40ebe8c08f7a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -90,8 +90,7 @@ module.exports = [ "tests/performance/**", "tmp/**", "tools/internal-rules/node_modules/**", - "**/test.js", - "tools/fetch-docs-links.js" + "**/test.js" ] }, { diff --git a/package.json b/package.json index 5fdd58eedcb1..70c5241d15c4 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,10 @@ "node tools/update-eslint-all.js", "git add packages/js/src/configs/eslint-all.js" ], + "docs/src/rules/*.md": [ + "node tools/fetch-docs-links.js", + "git add docs/src/_data/further_reading_links.json" + ], "docs/**/*.svg": "npx svgo -r --multipass" }, "files": [ @@ -132,6 +136,12 @@ "markdownlint-cli": "^0.31.1", "marked": "^4.0.8", "memfs": "^3.0.1", + "metascraper": "^5.25.7", + "metascraper-description": "^5.25.7", + "metascraper-image": "^5.29.3", + "metascraper-logo": "^5.25.7", + "metascraper-logo-favicon": "^5.25.7", + "metascraper-title": "^5.25.7", "mocha": "^8.3.2", "mocha-junit-reporter": "^2.0.0", "node-polyfill-webpack-plugin": "^1.0.3",