From 896c7312dfe8374312df9404cb11db8378cd091c Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Tue, 24 Sep 2024 13:06:44 -0400 Subject: [PATCH 01/18] docs: [no-duplicate-type-constituents] clarify relation to exactOptionalPropertyTypes (#10045) --- .../eslint-plugin/docs/rules/no-duplicate-type-constituents.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/no-duplicate-type-constituents.mdx b/packages/eslint-plugin/docs/rules/no-duplicate-type-constituents.mdx index e254a6ca4c3e..37a1e36e6217 100644 --- a/packages/eslint-plugin/docs/rules/no-duplicate-type-constituents.mdx +++ b/packages/eslint-plugin/docs/rules/no-duplicate-type-constituents.mdx @@ -19,6 +19,8 @@ For example, given `type A = string` and `type T = string | A`, this rule would This rule also disallows explicitly listing `undefined` in a type union when a function parameter is marked as optional. Doing so is unnecessary. +Please note that this check only applies to parameters, not properties. +Therefore, it does not conflict with the [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes) TypeScript compiler setting. From b6f4b2f9b15e925d09be30140e21079a028cdbd6 Mon Sep 17 00:00:00 2001 From: ChinoUkaegbu <77782533+ChinoUkaegbu@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:15:22 +0100 Subject: [PATCH 02/18] docs: fix link typo in Typed_Linting.mdx (#10050) --- docs/getting-started/Typed_Linting.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/Typed_Linting.mdx b/docs/getting-started/Typed_Linting.mdx index 2c76b9dfff73..e669ac9cf11a 100644 --- a/docs/getting-started/Typed_Linting.mdx +++ b/docs/getting-started/Typed_Linting.mdx @@ -84,7 +84,7 @@ In more detail: :::caution Your ESLint config file may start receiving a parsing error about type information. -See [our TSConfig inclusion FAQ](../troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file). +See [our TSConfig inclusion FAQ](../../troubleshooting/typed-linting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file). ::: With that done, run the same lint command you ran before. From 6dbf2341751bbafcd37b5af51cd87184eb9853bb Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Wed, 25 Sep 2024 20:06:46 +0300 Subject: [PATCH 03/18] fix(eslint-plugin): [no-unnecessary-template-expression] should underline template syntax with squiggly lines (#10044) * fix no-unnecessary-template-expression to include template syntax with a squiggly * Update packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts Co-authored-by: Kirk Waiblinger * Update packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts Co-authored-by: Kirk Waiblinger --------- Co-authored-by: Kirk Waiblinger --- .../rules/explicit-member-accessibility.ts | 11 +- .../no-unnecessary-template-expression.ts | 21 ++-- packages/eslint-plugin/src/util/rangeToLoc.ts | 11 ++ .../no-unnecessary-template-expression.shot | 18 +-- ...no-unnecessary-template-expression.test.ts | 115 +++++++++--------- 5 files changed, 91 insertions(+), 85 deletions(-) create mode 100644 packages/eslint-plugin/src/util/rangeToLoc.ts diff --git a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts index 2661949b57d7..3bf70eb8bb74 100644 --- a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts +++ b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts @@ -11,6 +11,7 @@ import { getMemberHeadLoc, getParameterPropertyHeadLoc, } from '../util/getMemberHeadLoc'; +import { rangeToLoc } from '../util/rangeToLoc'; type AccessibilityLevel = | 'explicit' // require an accessor (including public) @@ -387,13 +388,3 @@ export default createRule({ }; }, }); - -function rangeToLoc( - sourceCode: TSESLint.SourceCode, - range: TSESLint.AST.Range, -): TSESTree.SourceLocation { - return { - start: sourceCode.getLocFromIndex(range[0]), - end: sourceCode.getLocFromIndex(range[1]), - }; -} diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts index dc15a9bbcd06..212e44dfdadb 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts @@ -10,6 +10,7 @@ import { isTypeFlagSet, isUndefinedIdentifier, } from '../util'; +import { rangeToLoc } from '../util/rangeToLoc'; type MessageId = 'noUnnecessaryTemplateExpression'; @@ -105,7 +106,10 @@ export default createRule<[], MessageId>({ if (hasSingleStringVariable) { context.report({ - node: node.expressions[0], + loc: rangeToLoc(context.sourceCode, [ + node.expressions[0].range[0] - 2, + node.expressions[0].range[1] + 1, + ]), messageId: 'noUnnecessaryTemplateExpression', fix(fixer): TSESLint.RuleFix | null { const wrappingCode = getMovedNodeCode({ @@ -245,20 +249,17 @@ export default createRule<[], MessageId>({ ]); } + const warnLocStart = prevQuasi.range[1] - 2; + const warnLocEnd = nextQuasi.range[0] + 1; + context.report({ - node: expression, + loc: rangeToLoc(context.sourceCode, [warnLocStart, warnLocEnd]), messageId: 'noUnnecessaryTemplateExpression', fix(fixer): TSESLint.RuleFix[] { return [ // Remove the quasis' parts that are related to the current expression. - fixer.removeRange([ - prevQuasi.range[1] - 2, - expression.range[0], - ]), - fixer.removeRange([ - expression.range[1], - nextQuasi.range[0] + 1, - ]), + fixer.removeRange([warnLocStart, expression.range[0]]), + fixer.removeRange([expression.range[1], warnLocEnd]), ...fixers.flatMap(cb => cb(fixer)), ]; diff --git a/packages/eslint-plugin/src/util/rangeToLoc.ts b/packages/eslint-plugin/src/util/rangeToLoc.ts new file mode 100644 index 000000000000..893362d3a722 --- /dev/null +++ b/packages/eslint-plugin/src/util/rangeToLoc.ts @@ -0,0 +1,11 @@ +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; + +export function rangeToLoc( + sourceCode: TSESLint.SourceCode, + range: TSESLint.AST.Range, +): TSESTree.SourceLocation { + return { + start: sourceCode.getLocFromIndex(range[0]), + end: sourceCode.getLocFromIndex(range[1]), + }; +} diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-template-expression.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-template-expression.shot index d6062501d837..47aa863d7203 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-template-expression.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-template-expression.shot @@ -6,29 +6,29 @@ exports[`Validating rule docs no-unnecessary-template-expression.mdx code exampl // Static values can be incorporated into the surrounding template. const ab1 = \`\${'a'}\${'b'}\`; - ~~~ Template literal expression is unnecessary and can be simplified. - ~~~ Template literal expression is unnecessary and can be simplified. + ~~~~~~ Template literal expression is unnecessary and can be simplified. + ~~~~~~ Template literal expression is unnecessary and can be simplified. const ab2 = \`a\${'b'}\`; - ~~~ Template literal expression is unnecessary and can be simplified. + ~~~~~~ Template literal expression is unnecessary and can be simplified. const stringWithNumber = \`\${'1 + 1 = '}\${2}\`; - ~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. - ~ Template literal expression is unnecessary and can be simplified. + ~~~~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. + ~~~~ Template literal expression is unnecessary and can be simplified. const stringWithBoolean = \`\${'true is '}\${true}\`; - ~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. - ~~~~ Template literal expression is unnecessary and can be simplified. + ~~~~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. + ~~~~~~~ Template literal expression is unnecessary and can be simplified. // Some simple expressions that are already strings // can be rewritten without a template at all. const text = 'a'; const wrappedText = \`\${text}\`; - ~~~~ Template literal expression is unnecessary and can be simplified. + ~~~~~~~ Template literal expression is unnecessary and can be simplified. declare const intersectionWithString: string & { _brand: 'test-brand' }; const wrappedIntersection = \`\${intersectionWithString}\`; - ~~~~~~~~~~~~~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. + ~~~~~~~~~~~~~~~~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. " `; diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-template-expression.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-template-expression.test.ts index 3902b404d9ec..d37c24f5c193 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-template-expression.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-template-expression.test.ts @@ -26,8 +26,8 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 5, + column: 2, + endColumn: 6, }, ], }, @@ -38,8 +38,8 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 6, + column: 2, + endColumn: 7, }, ], }, @@ -50,8 +50,8 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 8, + column: 2, + endColumn: 9, }, ], }, @@ -62,14 +62,14 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 10, + column: 2, + endColumn: 11, }, { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 14, - endColumn: 20, + column: 12, + endColumn: 21, }, ], }, @@ -80,8 +80,8 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 8, + column: 2, + endColumn: 9, }, ], }, @@ -92,8 +92,8 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 7, + column: 2, + endColumn: 8, }, ], }, @@ -104,8 +104,8 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 10, + column: 2, + endColumn: 11, }, ], }, @@ -157,8 +157,8 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 8, + column: 2, + endColumn: 9, }, ], }, @@ -180,8 +180,8 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 8, + column: 2, + endColumn: 9, }, ], }, @@ -203,8 +203,8 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 13, + column: 2, + endColumn: 14, }, ], }, @@ -226,8 +226,8 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 12, + column: 2, + endColumn: 13, }, ], }, @@ -239,8 +239,8 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 7, + column: 2, + endColumn: 8, }, ], }, @@ -252,14 +252,14 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 7, + column: 2, + endColumn: 8, }, { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 11, - endColumn: 14, + column: 9, + endColumn: 15, }, ], }, @@ -321,19 +321,21 @@ const invalidCases: readonly InvalidTestCase< errors: [ { messageId: 'noUnnecessaryTemplateExpression', - line: 4, + line: 2, }, { messageId: 'noUnnecessaryTemplateExpression', - line: 7, - column: 3, - endLine: 7, + line: 6, + column: 2, + endLine: 8, + endColumn: 2, }, { messageId: 'noUnnecessaryTemplateExpression', line: 7, - column: 10, + column: 6, endLine: 7, + endColumn: 17, }, ], }, @@ -349,9 +351,10 @@ const invalidCases: readonly InvalidTestCase< errors: [ { messageId: 'noUnnecessaryTemplateExpression', - line: 3, - column: 3, - endColumn: 9, + line: 2, + column: 5, + endLine: 4, + endColumn: 2, }, ], }, @@ -363,14 +366,14 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 13, + column: 2, + endColumn: 14, }, { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 17, - endColumn: 18, + column: 15, + endColumn: 19, }, ], }, @@ -382,14 +385,14 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 7, + column: 2, + endColumn: 8, }, { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 11, - endColumn: 15, + column: 9, + endColumn: 16, }, ], }, @@ -401,8 +404,8 @@ const invalidCases: readonly InvalidTestCase< { messageId: 'noUnnecessaryTemplateExpression', line: 1, - column: 4, - endColumn: 30, + column: 2, + endColumn: 31, }, ], }, @@ -1038,8 +1041,8 @@ ruleTester.run('no-unnecessary-template-expression', rule, { { messageId: 'noUnnecessaryTemplateExpression', line: 3, - column: 14, - endColumn: 17, + column: 12, + endColumn: 18, }, ], }, @@ -1056,8 +1059,8 @@ ruleTester.run('no-unnecessary-template-expression', rule, { { messageId: 'noUnnecessaryTemplateExpression', line: 3, - column: 17, - endColumn: 20, + column: 15, + endColumn: 21, }, ], }, @@ -1104,8 +1107,8 @@ declare const nested: string, interpolation: string; { messageId: 'noUnnecessaryTemplateExpression', line: 3, - column: 12, - endColumn: 18, + column: 10, + endColumn: 19, }, ], }, @@ -1122,8 +1125,8 @@ declare const nested: string, interpolation: string; { messageId: 'noUnnecessaryTemplateExpression', line: 3, - column: 12, - endColumn: 24, + column: 10, + endColumn: 25, }, ], }, From dda8eba2f2f5850e5c784f7a553ce3f027839608 Mon Sep 17 00:00:00 2001 From: "typescript-eslint[bot]" <53356952+typescript-eslint[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:20:40 +0930 Subject: [PATCH 04/18] chore: update sponsors (#10062) Co-authored-by: typescript-eslint[bot] --- packages/website/data/sponsors.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/website/data/sponsors.json b/packages/website/data/sponsors.json index c1ad55284953..24eceaad50d7 100644 --- a/packages/website/data/sponsors.json +++ b/packages/website/data/sponsors.json @@ -80,7 +80,7 @@ "id": "GitBook", "image": "https://images.opencollective.com/gitbook/820419f/logo.png", "name": "GitBook", - "totalDonations": 150000, + "totalDonations": 160000, "website": "https://www.gitbook.com" }, { @@ -244,6 +244,13 @@ "totalDonations": 21000, "website": "https://www.0x.se" }, + { + "id": "Trevor Burnham", + "image": "https://images.opencollective.com/trevorburnham/016f6da/avatar.png", + "name": "Trevor Burnham", + "totalDonations": 21000, + "website": "https://trevorburnham.com" + }, { "id": "David Johnston", "image": "https://images.opencollective.com/blacksheepcode/f186c05/avatar.png", @@ -258,13 +265,6 @@ "totalDonations": 20400, "website": "https://www.corellium.com" }, - { - "id": "Trevor Burnham", - "image": "https://images.opencollective.com/trevorburnham/016f6da/avatar.png", - "name": "Trevor Burnham", - "totalDonations": 20000, - "website": "https://trevorburnham.com" - }, { "id": "kartenmacherei", "image": "https://images.opencollective.com/kartenmacherei/21bfcfb/logo.png", From d50b97f9559731ba3a053d89ee1c574f4ae30d42 Mon Sep 17 00:00:00 2001 From: mdm317 Date: Fri, 27 Sep 2024 11:13:43 +0900 Subject: [PATCH 05/18] docs(website): open in playground button should not cover text (#10019) * style: increase padding-bottom to prevent overlap * Revert "style: increase padding-bottom to prevent overlap" This reverts commit 859928797ba1bdab6fb381078ee0155e998b316d. * fix : add padding bottom only when playground button is visible * Revert "fix : add padding bottom only when playground button is visible" This reverts commit 492c3c152e69018ed2969dc11a3495ae266859ef. * fix : Increase the padding if the length of the last line of code exceeds 50 characters. This reverts commit 492c3c152e69018ed2969dc11a3495ae266859ef. * fix : add padding when codeblocks has playground button --- .../website/src/theme/CodeBlock/Content/String.tsx | 11 ++++++++--- .../src/theme/CodeBlock/Content/styles.module.css | 5 +++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/website/src/theme/CodeBlock/Content/String.tsx b/packages/website/src/theme/CodeBlock/Content/String.tsx index dcd0b526ec52..e49a2e730663 100644 --- a/packages/website/src/theme/CodeBlock/Content/String.tsx +++ b/packages/website/src/theme/CodeBlock/Content/String.tsx @@ -50,15 +50,17 @@ export default function CodeBlockString({ const showLineNumbers = showLineNumbersProp ?? containsLineNumbers(metastring); - const copiedCode = code + const codeLines = code .split('\n') .filter( (c, i) => !(lineClassNames[i] as string[] | undefined)?.includes( 'code-block-removed-line', ), - ) - .join('\n'); + ); + const copiedCode = codeLines.join('\n'); + const lastLineOfCodeLength = codeLines.at(-1)?.length ?? 0; + const needsMorePadding = lastLineOfCodeLength > 50; const eslintrcHash = parseEslintrc(metastring); @@ -90,6 +92,9 @@ export default function CodeBlockString({ diff --git a/packages/website/src/theme/CodeBlock/Content/styles.module.css b/packages/website/src/theme/CodeBlock/Content/styles.module.css index b1daba6510b0..7fe4c1ffb3a9 100644 --- a/packages/website/src/theme/CodeBlock/Content/styles.module.css +++ b/packages/website/src/theme/CodeBlock/Content/styles.module.css @@ -46,6 +46,11 @@ padding: var(--ifm-pre-padding); } +.codeBlockLinesMorePadding { + padding: var(--ifm-pre-padding) var(--ifm-pre-padding) + calc(var(--ifm-pre-padding) * 2.5); +} + .codeBlockLinesWithNumbering { display: table; padding: var(--ifm-pre-padding) 0; From ced951b415c0c30b9e476f13561993ecccee76fc Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Thu, 26 Sep 2024 20:14:18 -0600 Subject: [PATCH 06/18] docs: [prefer-literal-enum-member] fix bad examples (#10035) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: [prefer-literal-enum-members] fix bad examples * Update packages/eslint-plugin/docs/rules/prefer-literal-enum-member.mdx Co-authored-by: Josh Goldberg ✨ * snapshots --------- Co-authored-by: Josh Goldberg ✨ --- .../docs/rules/prefer-literal-enum-member.mdx | 17 ++++---- .../src/rules/prefer-literal-enum-member.ts | 2 +- .../prefer-literal-enum-member.shot | 39 ++++++++++--------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-literal-enum-member.mdx b/packages/eslint-plugin/docs/rules/prefer-literal-enum-member.mdx index 5f9e4ac03736..31834a871a2b 100644 --- a/packages/eslint-plugin/docs/rules/prefer-literal-enum-member.mdx +++ b/packages/eslint-plugin/docs/rules/prefer-literal-enum-member.mdx @@ -38,12 +38,14 @@ This rule reports when an enum member is given a value that is not a literal. ```ts const str = 'Test'; +const string1 = 'string1'; +const string2 = 'string2'; + enum Invalid { A = str, // Variable assignment - B = {}, // Object assignment - C = `A template literal string`, // Template literal - D = new Set(1, 2, 3), // Constructor in assignment - E = 2 + 2, // Expression assignment + B = `Interpolates ${string1} and ${string2}`, // Template literal with interpolation + C = 2 + 2, // Expression assignment + D = C, // Assignment to another enum member } ``` @@ -52,11 +54,10 @@ enum Invalid { ```ts enum Valid { - A, + A, // No initializer; initialized with ascending integers starting from 0 B = 'TestStr', // A regular string - C = 4, // A number - D = null, - E = /some_regex/, + C = `A template literal string`, // A template literal without interpolation + D = 4, // A number } ``` diff --git a/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts b/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts index dcc31ba5742a..65336c9d2051 100644 --- a/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts +++ b/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts @@ -13,7 +13,7 @@ export default createRule({ requiresTypeChecking: false, }, messages: { - notLiteral: `Explicit enum value must only be a literal value (string, number, boolean, etc).`, + notLiteral: `Explicit enum value must only be a literal value (string or number).`, }, schema: [ { diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/prefer-literal-enum-member.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/prefer-literal-enum-member.shot index c9938b4bbb47..687ca0e7747a 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/prefer-literal-enum-member.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/prefer-literal-enum-member.shot @@ -4,16 +4,18 @@ exports[`Validating rule docs prefer-literal-enum-member.mdx code examples ESLin "Incorrect const str = 'Test'; +const string1 = 'string1'; +const string2 = 'string2'; + enum Invalid { A = str, // Variable assignment - ~ Explicit enum value must only be a literal value (string, number, boolean, etc). - B = {}, // Object assignment - ~ Explicit enum value must only be a literal value (string, number, boolean, etc). - C = \`A template literal string\`, // Template literal - D = new Set(1, 2, 3), // Constructor in assignment - ~ Explicit enum value must only be a literal value (string, number, boolean, etc). - E = 2 + 2, // Expression assignment - ~ Explicit enum value must only be a literal value (string, number, boolean, etc). + ~ Explicit enum value must only be a literal value (string or number). + B = \`Interpolates \${string1} and \${string2}\`, // Template literal with interpolation + ~ Explicit enum value must only be a literal value (string or number). + C = 2 + 2, // Expression assignment + ~ Explicit enum value must only be a literal value (string or number). + D = C, // Assignment to another enum member + ~ Explicit enum value must only be a literal value (string or number). } " `; @@ -22,11 +24,10 @@ exports[`Validating rule docs prefer-literal-enum-member.mdx code examples ESLin "Correct enum Valid { - A, + A, // No initializer; initialized with ascending integers starting from 0 B = 'TestStr', // A regular string - C = 4, // A number - D = null, - E = /some_regex/, + C = \`A template literal string\`, // A template literal without interpolation + D = 4, // A number } " `; @@ -38,19 +39,19 @@ Options: { "allowBitwiseExpressions": true } const x = 1; enum Foo { A = x << 0, - ~ Explicit enum value must only be a literal value (string, number, boolean, etc). + ~ Explicit enum value must only be a literal value (string or number). B = x >> 0, - ~ Explicit enum value must only be a literal value (string, number, boolean, etc). + ~ Explicit enum value must only be a literal value (string or number). C = x >>> 0, - ~ Explicit enum value must only be a literal value (string, number, boolean, etc). + ~ Explicit enum value must only be a literal value (string or number). D = x | 0, - ~ Explicit enum value must only be a literal value (string, number, boolean, etc). + ~ Explicit enum value must only be a literal value (string or number). E = x & 0, - ~ Explicit enum value must only be a literal value (string, number, boolean, etc). + ~ Explicit enum value must only be a literal value (string or number). F = x ^ 0, - ~ Explicit enum value must only be a literal value (string, number, boolean, etc). + ~ Explicit enum value must only be a literal value (string or number). G = ~x, - ~ Explicit enum value must only be a literal value (string, number, boolean, etc). + ~ Explicit enum value must only be a literal value (string or number). } " `; From 94c5484f747be901f1610fc37192714a619bb355 Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Fri, 27 Sep 2024 21:54:21 +0300 Subject: [PATCH 07/18] fix(type-utils): check for type parameters on `isBuiltinSymbolLikeRecurser()` (#10026) * check type parameter on isBuiltinSymbolLikeRecurser * basic tests * use tsutils.isTypeParameter() instead of type.isTypeParameter() --- .../tests/rules/only-throw-error.test.ts | 17 +++++++++++++ .../prefer-promise-reject-errors.test.ts | 24 +++++++++++++++++++ packages/type-utils/src/builtinSymbolLikes.ts | 11 +++++++++ 3 files changed, 52 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/only-throw-error.test.ts b/packages/eslint-plugin/tests/rules/only-throw-error.test.ts index 0ae3f0e00769..a836485f8a8d 100644 --- a/packages/eslint-plugin/tests/rules/only-throw-error.test.ts +++ b/packages/eslint-plugin/tests/rules/only-throw-error.test.ts @@ -132,6 +132,11 @@ function fun(value: any) { ` function fun(value: unknown) { throw value; +} + `, + ` +function fun(t: T): void { + throw t; } `, ], @@ -468,5 +473,17 @@ function fun(value: unknown) { }, ], }, + { + code: ` +function fun(t: T): void { + throw t; +} + `, + errors: [ + { + messageId: 'object', + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/prefer-promise-reject-errors.test.ts b/packages/eslint-plugin/tests/rules/prefer-promise-reject-errors.test.ts index e78575881255..c8981a31b3f0 100644 --- a/packages/eslint-plugin/tests/rules/prefer-promise-reject-errors.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-promise-reject-errors.test.ts @@ -275,6 +275,12 @@ ruleTester.run('prefer-promise-reject-errors', rule, { } } `, + ` + declare const foo: PromiseConstructor; + function fun(t: T): void { + foo.reject(t); + } + `, ], invalid: [ { @@ -1443,5 +1449,23 @@ Bar.reject(5); }, ], }, + { + code: ` +declare const foo: PromiseConstructor; +function fun(t: T): void { + foo.reject(t); +} + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 4, + endLine: 4, + column: 3, + endColumn: 16, + }, + ], + }, ], }); diff --git a/packages/type-utils/src/builtinSymbolLikes.ts b/packages/type-utils/src/builtinSymbolLikes.ts index c1095be513a3..7686531a40d3 100644 --- a/packages/type-utils/src/builtinSymbolLikes.ts +++ b/packages/type-utils/src/builtinSymbolLikes.ts @@ -1,3 +1,4 @@ +import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; import { isSymbolFromDefaultLibrary } from './isSymbolFromDefaultLibrary'; @@ -158,6 +159,16 @@ export function isBuiltinSymbolLikeRecurser( isBuiltinSymbolLikeRecurser(program, t, predicate), ); } + // https://github.com/JoshuaKGoldberg/ts-api-utils/issues/382 + if ((tsutils.isTypeParameter as (type: ts.Type) => boolean)(type)) { + const t = type.getConstraint(); + + if (t) { + return isBuiltinSymbolLikeRecurser(program, t, predicate); + } + + return false; + } const predicateResult = predicate(type); if (typeof predicateResult === 'boolean') { From 53650c4fc49f47bf3bad9640d6db63022bb4dbc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Sun, 29 Sep 2024 18:44:29 -0400 Subject: [PATCH 08/18] fix(utils): update missing type information message (#10043) --- packages/utils/src/eslint-utils/getParserServices.ts | 2 +- packages/utils/tests/eslint-utils/getParserServices.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/eslint-utils/getParserServices.ts b/packages/utils/src/eslint-utils/getParserServices.ts index 9c8f3bb8db39..1fa6de72001a 100644 --- a/packages/utils/src/eslint-utils/getParserServices.ts +++ b/packages/utils/src/eslint-utils/getParserServices.ts @@ -7,7 +7,7 @@ import type { import { parserSeemsToBeTSESLint } from './parserSeemsToBeTSESLint'; const ERROR_MESSAGE_REQUIRES_PARSER_SERVICES = - 'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.'; + "You have used a rule which requires type information, but don't have parserOptions set to generate type information for this file. See https://typescript-eslint.io/getting-started/typed-linting for enabling linting with type information."; const ERROR_MESSAGE_UNKNOWN_PARSER = 'Note: detected a parser other than @typescript-eslint/parser. Make sure the parser is configured to forward "parserOptions.project" to @typescript-eslint/parser.'; diff --git a/packages/utils/tests/eslint-utils/getParserServices.test.ts b/packages/utils/tests/eslint-utils/getParserServices.test.ts index 74494d9ac269..c083c40e76c0 100644 --- a/packages/utils/tests/eslint-utils/getParserServices.test.ts +++ b/packages/utils/tests/eslint-utils/getParserServices.test.ts @@ -27,7 +27,7 @@ const createMockRuleContext = ( }) as unknown as UnknownRuleContext; const requiresParserServicesMessageTemplate = (parser = '\\S*'): string => - 'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.\n' + + 'You have used a rule which requires type information, .+\n' + `Parser: ${parser}`; const baseErrorRegex = (parser?: string): RegExp => new RegExp(requiresParserServicesMessageTemplate(parser)); From 7a216fe7b859fdd2498fcfa84ac8deb8c41530e8 Mon Sep 17 00:00:00 2001 From: Tarun Chauhan Date: Mon, 30 Sep 2024 04:14:59 +0530 Subject: [PATCH 09/18] fix(eslint-plugin): [no-deprecated] max callstack exceeded when class implements itself (#10040) --- packages/eslint-plugin/src/rules/no-deprecated.ts | 11 ++++++++--- .../tests/rules/no-deprecated.test.ts | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index 42eb522d3ca9..8eea3c8a1d35 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -152,9 +152,14 @@ export default createRule({ function getJsDocDeprecation( symbol: ts.Signature | ts.Symbol | undefined, ): string | undefined { - const tag = symbol - ?.getJsDocTags(checker) - .find(tag => tag.name === 'deprecated'); + let jsDocTags: ts.JSDocTagInfo[] | undefined; + try { + jsDocTags = symbol?.getJsDocTags(checker); + } catch { + // workaround for https://github.com/microsoft/TypeScript/issues/60024 + return; + } + const tag = jsDocTags?.find(tag => tag.name === 'deprecated'); if (!tag) { return undefined; diff --git a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts index e8451d82b2cb..4b9798a4dbf0 100644 --- a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts +++ b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts @@ -253,6 +253,20 @@ ruleTester.run('no-deprecated', rule, { }, }, 'call();', + + // this test is to ensure the rule doesn't crash when class implements itself + // https://github.com/typescript-eslint/typescript-eslint/issues/10031 + ` + class Foo implements Foo { + get bar(): number { + return 42; + } + + baz(): number { + return this.bar; + } + } + `, ], invalid: [ { From b36d524d37c58cf8102e24dae021b57b37687159 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Mon, 30 Sep 2024 07:45:22 +0900 Subject: [PATCH 10/18] fix(eslint-plugin): [no-misused-promises] check contextual type (#10042) * fix(eslint-plugin): [no-misused-promises] check contextual type * fix * apply review * remove comment --- .../src/rules/no-misused-promises.ts | 14 +++ .../tests/rules/no-misused-promises.test.ts | 107 ++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 9edd31141e0a..dd219fa95d3f 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -743,6 +743,20 @@ function checkThenableOrVoidArgument( ) { voidReturnIndices.add(index); } + const contextualType = checker.getContextualTypeForArgumentAtIndex( + node, + index, + ); + if (contextualType !== type) { + checkThenableOrVoidArgument( + checker, + node, + contextualType, + index, + thenableReturnIndices, + voidReturnIndices, + ); + } } // Get the positions of arguments which are void functions (and not also diff --git a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts index 0c6b33582825..0f4330f890ca 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -1051,6 +1051,21 @@ interface MyInterface extends MyCall, MyIndex, MyConstruct, MyMethods { const array: number[] = [1, 2, 3]; array.filter(a => a > 1); `, + ` +type ReturnsPromiseVoid = () => Promise; +declare const useCallback: unknown>( + fn: T, +) => T; +useCallback(async () => {}); + `, + ` +type ReturnsVoid = () => void; +type ReturnsPromiseVoid = () => Promise; +declare const useCallback: unknown>( + fn: T, +) => T; +useCallback(async () => {}); + `, ], invalid: [ @@ -2322,5 +2337,97 @@ tuple.find(() => Promise.resolve(false)); }, ], }, + { + code: ` +type ReturnsVoid = () => void; +declare const useCallback: unknown>( + fn: T, +) => T; +declare const useCallbackReturningVoid: typeof useCallback; +useCallbackReturningVoid(async () => {}); + `, + errors: [ + { + line: 7, + messageId: 'voidReturnArgument', + }, + ], + }, + { + code: ` +type ReturnsVoid = () => void; +declare const useCallback: unknown>( + fn: T, +) => T; +useCallback(async () => {}); + `, + errors: [ + { + line: 6, + messageId: 'voidReturnArgument', + }, + ], + }, + { + code: ` +interface Foo { + (callback: () => T): void; + (callback: () => number): void; +} +declare const foo: Foo; + +foo(async () => {}); + `, + errors: [ + { + line: 8, + messageId: 'voidReturnArgument', + }, + ], + }, + { + code: ` +declare function tupleFn unknown>( + ...fns: [T, string, T] +): void; +tupleFn<() => void>( + async () => {}, + 'foo', + async () => {}, +); + `, + errors: [ + { + line: 6, + messageId: 'voidReturnArgument', + }, + { + line: 8, + messageId: 'voidReturnArgument', + }, + ], + }, + { + code: ` +declare function arrayFn unknown>( + ...fns: (T | string)[] +): void; +arrayFn<() => void>( + async () => {}, + 'foo', + async () => {}, +); + `, + errors: [ + { + line: 6, + messageId: 'voidReturnArgument', + }, + { + line: 8, + messageId: 'voidReturnArgument', + }, + ], + }, ], }); From 032918a91a0cf655a46ae11761a40fc637f007f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Sep 2024 18:51:29 -0400 Subject: [PATCH 11/18] chore(deps): update dependency esbuild to ~0.24.0 (#10073) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- packages/website-eslint/package.json | 2 +- yarn.lock | 204 +++++++++++++-------------- 2 files changed, 103 insertions(+), 103 deletions(-) diff --git a/packages/website-eslint/package.json b/packages/website-eslint/package.json index ad4c0f9a44d6..29dbea9a6d6c 100644 --- a/packages/website-eslint/package.json +++ b/packages/website-eslint/package.json @@ -28,7 +28,7 @@ "@typescript-eslint/scope-manager": "workspace:*", "@typescript-eslint/typescript-estree": "workspace:*", "@typescript-eslint/visitor-keys": "workspace:*", - "esbuild": "~0.23.0", + "esbuild": "~0.24.0", "eslint": "*", "esquery": "*", "prettier": "^3.2.5", diff --git a/yarn.lock b/yarn.lock index d607f912b0b0..985188ee7fdf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3026,9 +3026,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/aix-ppc64@npm:0.23.0" +"@esbuild/aix-ppc64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/aix-ppc64@npm:0.24.0" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard @@ -3040,9 +3040,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/android-arm64@npm:0.23.0" +"@esbuild/android-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/android-arm64@npm:0.24.0" conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -3054,9 +3054,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/android-arm@npm:0.23.0" +"@esbuild/android-arm@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/android-arm@npm:0.24.0" conditions: os=android & cpu=arm languageName: node linkType: hard @@ -3068,9 +3068,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/android-x64@npm:0.23.0" +"@esbuild/android-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/android-x64@npm:0.24.0" conditions: os=android & cpu=x64 languageName: node linkType: hard @@ -3082,9 +3082,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/darwin-arm64@npm:0.23.0" +"@esbuild/darwin-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/darwin-arm64@npm:0.24.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -3096,9 +3096,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/darwin-x64@npm:0.23.0" +"@esbuild/darwin-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/darwin-x64@npm:0.24.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -3110,9 +3110,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/freebsd-arm64@npm:0.23.0" +"@esbuild/freebsd-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/freebsd-arm64@npm:0.24.0" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard @@ -3124,9 +3124,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/freebsd-x64@npm:0.23.0" +"@esbuild/freebsd-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/freebsd-x64@npm:0.24.0" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard @@ -3138,9 +3138,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/linux-arm64@npm:0.23.0" +"@esbuild/linux-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-arm64@npm:0.24.0" conditions: os=linux & cpu=arm64 languageName: node linkType: hard @@ -3152,9 +3152,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/linux-arm@npm:0.23.0" +"@esbuild/linux-arm@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-arm@npm:0.24.0" conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -3166,9 +3166,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/linux-ia32@npm:0.23.0" +"@esbuild/linux-ia32@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-ia32@npm:0.24.0" conditions: os=linux & cpu=ia32 languageName: node linkType: hard @@ -3180,9 +3180,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/linux-loong64@npm:0.23.0" +"@esbuild/linux-loong64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-loong64@npm:0.24.0" conditions: os=linux & cpu=loong64 languageName: node linkType: hard @@ -3194,9 +3194,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/linux-mips64el@npm:0.23.0" +"@esbuild/linux-mips64el@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-mips64el@npm:0.24.0" conditions: os=linux & cpu=mips64el languageName: node linkType: hard @@ -3208,9 +3208,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/linux-ppc64@npm:0.23.0" +"@esbuild/linux-ppc64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-ppc64@npm:0.24.0" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard @@ -3222,9 +3222,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/linux-riscv64@npm:0.23.0" +"@esbuild/linux-riscv64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-riscv64@npm:0.24.0" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard @@ -3236,9 +3236,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/linux-s390x@npm:0.23.0" +"@esbuild/linux-s390x@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-s390x@npm:0.24.0" conditions: os=linux & cpu=s390x languageName: node linkType: hard @@ -3250,9 +3250,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/linux-x64@npm:0.23.0" +"@esbuild/linux-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-x64@npm:0.24.0" conditions: os=linux & cpu=x64 languageName: node linkType: hard @@ -3264,16 +3264,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/netbsd-x64@npm:0.23.0" +"@esbuild/netbsd-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/netbsd-x64@npm:0.24.0" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/openbsd-arm64@npm:0.23.0" +"@esbuild/openbsd-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/openbsd-arm64@npm:0.24.0" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard @@ -3285,9 +3285,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/openbsd-x64@npm:0.23.0" +"@esbuild/openbsd-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/openbsd-x64@npm:0.24.0" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard @@ -3299,9 +3299,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/sunos-x64@npm:0.23.0" +"@esbuild/sunos-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/sunos-x64@npm:0.24.0" conditions: os=sunos & cpu=x64 languageName: node linkType: hard @@ -3313,9 +3313,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/win32-arm64@npm:0.23.0" +"@esbuild/win32-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/win32-arm64@npm:0.24.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -3327,9 +3327,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/win32-ia32@npm:0.23.0" +"@esbuild/win32-ia32@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/win32-ia32@npm:0.24.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -3341,9 +3341,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.23.0": - version: 0.23.0 - resolution: "@esbuild/win32-x64@npm:0.23.0" +"@esbuild/win32-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/win32-x64@npm:0.24.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -6025,7 +6025,7 @@ __metadata: "@typescript-eslint/scope-manager": "workspace:*" "@typescript-eslint/typescript-estree": "workspace:*" "@typescript-eslint/visitor-keys": "workspace:*" - esbuild: ~0.23.0 + esbuild: ~0.24.0 eslint: "*" esquery: "*" prettier: ^3.2.5 @@ -9591,34 +9591,34 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:~0.23.0": - version: 0.23.0 - resolution: "esbuild@npm:0.23.0" - dependencies: - "@esbuild/aix-ppc64": 0.23.0 - "@esbuild/android-arm": 0.23.0 - "@esbuild/android-arm64": 0.23.0 - "@esbuild/android-x64": 0.23.0 - "@esbuild/darwin-arm64": 0.23.0 - "@esbuild/darwin-x64": 0.23.0 - "@esbuild/freebsd-arm64": 0.23.0 - "@esbuild/freebsd-x64": 0.23.0 - "@esbuild/linux-arm": 0.23.0 - "@esbuild/linux-arm64": 0.23.0 - "@esbuild/linux-ia32": 0.23.0 - "@esbuild/linux-loong64": 0.23.0 - "@esbuild/linux-mips64el": 0.23.0 - "@esbuild/linux-ppc64": 0.23.0 - "@esbuild/linux-riscv64": 0.23.0 - "@esbuild/linux-s390x": 0.23.0 - "@esbuild/linux-x64": 0.23.0 - "@esbuild/netbsd-x64": 0.23.0 - "@esbuild/openbsd-arm64": 0.23.0 - "@esbuild/openbsd-x64": 0.23.0 - "@esbuild/sunos-x64": 0.23.0 - "@esbuild/win32-arm64": 0.23.0 - "@esbuild/win32-ia32": 0.23.0 - "@esbuild/win32-x64": 0.23.0 +"esbuild@npm:~0.24.0": + version: 0.24.0 + resolution: "esbuild@npm:0.24.0" + dependencies: + "@esbuild/aix-ppc64": 0.24.0 + "@esbuild/android-arm": 0.24.0 + "@esbuild/android-arm64": 0.24.0 + "@esbuild/android-x64": 0.24.0 + "@esbuild/darwin-arm64": 0.24.0 + "@esbuild/darwin-x64": 0.24.0 + "@esbuild/freebsd-arm64": 0.24.0 + "@esbuild/freebsd-x64": 0.24.0 + "@esbuild/linux-arm": 0.24.0 + "@esbuild/linux-arm64": 0.24.0 + "@esbuild/linux-ia32": 0.24.0 + "@esbuild/linux-loong64": 0.24.0 + "@esbuild/linux-mips64el": 0.24.0 + "@esbuild/linux-ppc64": 0.24.0 + "@esbuild/linux-riscv64": 0.24.0 + "@esbuild/linux-s390x": 0.24.0 + "@esbuild/linux-x64": 0.24.0 + "@esbuild/netbsd-x64": 0.24.0 + "@esbuild/openbsd-arm64": 0.24.0 + "@esbuild/openbsd-x64": 0.24.0 + "@esbuild/sunos-x64": 0.24.0 + "@esbuild/win32-arm64": 0.24.0 + "@esbuild/win32-ia32": 0.24.0 + "@esbuild/win32-x64": 0.24.0 dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -9670,7 +9670,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 22138538225d5ce79f84fc0d3d3e31b57a91ef50ef00f2d6a9c8a4be4ed28d4b1d0ed14239e54341d1b9a7079f25e69761d0266f3c255da94e647b079b790421 + checksum: dd386d92a05c7eb03078480522cdd8b40c434777b5f08487c27971d30933ecaae3f08bd221958dd8f9c66214915cdc85f844283ca9bdbf8ee703d889ae526edd languageName: node linkType: hard From a916ff27c151f10db9fdbb259828ed50796dfe61 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sun, 29 Sep 2024 16:52:01 -0600 Subject: [PATCH 12/18] feat(eslint-plugin): [no-unnecessary-condition] add checkTypePredicates (#10009) * [no-unnecessary-condition]: add checkTruthinessAssertions * test -u * change internal reports * added some coverage * hugely simplify * uge * yooj * changes * some changes * test changes * finishing touches * snapshots * fixup * remove type predicate --- eslint.config.mjs | 2 +- .../docs/rules/no-unnecessary-condition.mdx | 40 ++++ .../src/rules/no-unnecessary-condition.ts | 49 +++++ .../src/rules/prefer-optional-chain.ts | 7 +- .../rules/prefer-string-starts-ends-with.ts | 6 +- .../src/rules/strict-boolean-expressions.ts | 125 +---------- .../src/util/assertionFunctionUtils.ts | 119 +++++++++++ .../no-unnecessary-condition.shot | 38 ++++ .../rules/no-unnecessary-condition.test.ts | 194 ++++++++++++++++++ .../rules/strict-boolean-expressions.test.ts | 103 ++++++---- .../no-unnecessary-condition.shot | 6 + packages/type-utils/src/getTypeName.ts | 3 +- .../tests/lib/semanticInfo.test.ts | 13 +- .../tests/lib/source-files.test.ts | 1 + .../tests/test-utils/test-utils.ts | 2 +- .../typeDetails/SimplifiedTreeView.tsx | 4 +- 16 files changed, 525 insertions(+), 187 deletions(-) create mode 100644 packages/eslint-plugin/src/util/assertionFunctionUtils.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index c87098636b74..6e510fb45010 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -129,7 +129,7 @@ export default tseslint.config( 'no-constant-condition': 'off', '@typescript-eslint/no-unnecessary-condition': [ 'error', - { allowConstantLoopConditions: true }, + { allowConstantLoopConditions: true, checkTypePredicates: true }, ], '@typescript-eslint/no-unnecessary-type-parameters': 'error', '@typescript-eslint/no-unused-expressions': 'error', diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-condition.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-condition.mdx index ec5f234ac981..befeea8b2401 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-condition.mdx +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-condition.mdx @@ -90,6 +90,46 @@ for (; true; ) {} do {} while (true); ``` +### `checkTypePredicates` + +Example of additional incorrect code with `{ checkTypePredicates: true }`: + +```ts option='{ "checkTypePredicates": true }' showPlaygroundButton +function assert(condition: unknown): asserts condition { + if (!condition) { + throw new Error('Condition is falsy'); + } +} + +assert(false); // Unnecessary; condition is always falsy. + +const neverNull = {}; +assert(neverNull); // Unnecessary; condition is always truthy. + +function isString(value: unknown): value is string { + return typeof value === 'string'; +} + +declare const s: string; + +// Unnecessary; s is always a string. +if (isString(s)) { +} + +function assertIsString(value: unknown): asserts value is string { + if (!isString(value)) { + throw new Error('Value is not a string'); + } +} + +assertIsString(s); // Unnecessary; s is always a string. +``` + +Whether this option makes sense for your project may vary. +Some projects may intentionally use type predicates to ensure that runtime values do indeed match the types according to TypeScript, especially in test code. +Often, it makes sense to use eslint-disable comments in these cases, with a comment indicating why the condition should be checked at runtime, despite appearing unnecessary. +However, in some contexts, it may be more appropriate to keep this option disabled entirely. + ### `allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing` :::danger Deprecated diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 035ba000f33d..4befec262769 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -18,6 +18,10 @@ import { nullThrows, NullThrowsReasons, } from '../util'; +import { + findTruthinessAssertedArgument, + findTypeGuardAssertedArgument, +} from '../util/assertionFunctionUtils'; // Truthiness utilities // #region @@ -71,6 +75,7 @@ export type Options = [ { allowConstantLoopConditions?: boolean; allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean; + checkTypePredicates?: boolean; }, ]; @@ -81,6 +86,8 @@ export type MessageId = | 'alwaysTruthy' | 'alwaysTruthyFunc' | 'literalBooleanExpression' + | 'typeGuardAlreadyIsType' + | 'replaceWithTrue' | 'never' | 'neverNullish' | 'neverOptionalChain' @@ -111,6 +118,11 @@ export default createRule({ 'Whether to not error when running with a tsconfig that has strictNullChecks turned.', type: 'boolean', }, + checkTypePredicates: { + description: + 'Whether to check the asserted argument of a type predicate function for unnecessary conditions', + type: 'boolean', + }, }, additionalProperties: false, }, @@ -129,18 +141,22 @@ export default createRule({ 'Unnecessary conditional, left-hand side of `??` operator is always `null` or `undefined`.', literalBooleanExpression: 'Unnecessary conditional, both sides of the expression are literal values.', + replaceWithTrue: 'Replace always true expression with `true`.', noOverlapBooleanExpression: 'Unnecessary conditional, the types have no overlap.', never: 'Unnecessary conditional, value is `never`.', neverOptionalChain: 'Unnecessary optional chain on a non-nullish value.', noStrictNullCheck: 'This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.', + typeGuardAlreadyIsType: + 'Unnecessary conditional, expression already has the type being checked by the {{typeGuardOrAssertionFunction}}.', }, }, defaultOptions: [ { allowConstantLoopConditions: false, allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false, + checkTypePredicates: false, }, ], create( @@ -149,6 +165,7 @@ export default createRule({ { allowConstantLoopConditions, allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing, + checkTypePredicates, }, ], ) { @@ -463,6 +480,38 @@ export default createRule({ } function checkCallExpression(node: TSESTree.CallExpression): void { + if (checkTypePredicates) { + const truthinessAssertedArgument = findTruthinessAssertedArgument( + services, + node, + ); + if (truthinessAssertedArgument != null) { + checkNode(truthinessAssertedArgument); + } + + const typeGuardAssertedArgument = findTypeGuardAssertedArgument( + services, + node, + ); + if (typeGuardAssertedArgument != null) { + const typeOfArgument = getConstrainedTypeAtLocation( + services, + typeGuardAssertedArgument.argument, + ); + if (typeOfArgument === typeGuardAssertedArgument.type) { + context.report({ + node: typeGuardAssertedArgument.argument, + messageId: 'typeGuardAlreadyIsType', + data: { + typeGuardOrAssertionFunction: typeGuardAssertedArgument.asserts + ? 'assertion function' + : 'type guard', + }, + }); + } + } + } + // If this is something like arr.filter(x => /*condition*/), check `condition` if ( isArrayMethodCallWithPredicate(context, services, node) && diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts index 1e979e47a4c8..cea09251e53f 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts @@ -1,7 +1,6 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import type { RuleFix } from '@typescript-eslint/utils/ts-eslint'; -import * as ts from 'typescript'; import { createRule, @@ -130,14 +129,10 @@ export default createRule< function isLeftSideLowerPrecedence(): boolean { const logicalTsNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const leftTsNode = parserServices.esTreeNodeToTSNodeMap.get(leftNode); - const operator = ts.isBinaryExpression(logicalTsNode) - ? logicalTsNode.operatorToken.kind - : ts.SyntaxKind.Unknown; const leftPrecedence = getOperatorPrecedence( leftTsNode.kind, - operator, + logicalTsNode.operatorToken.kind, ); return leftPrecedence < OperatorPrecedence.LeftHandSide; diff --git a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts index d65fd9736ad5..ca0bd74c03bd 100644 --- a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts +++ b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts @@ -535,11 +535,7 @@ export default createRule({ const callNode = getParent(node) as TSESTree.CallExpression; const parentNode = getParent(callNode) as TSESTree.BinaryExpression; - if ( - !isEqualityComparison(parentNode) || - !isNull(parentNode.right) || - !isStringType(node.object) - ) { + if (!isNull(parentNode.right) || !isStringType(node.object)) { return; } diff --git a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts index 7bd708ac6f9c..7adbad1e525d 100644 --- a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts +++ b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts @@ -13,6 +13,7 @@ import { getWrappingFixer, isTypeArrayTypeOrUnionOfArrayTypes, } from '../util'; +import { findTruthinessAssertedArgument } from '../util/assertionFunctionUtils'; export type Options = [ { @@ -267,134 +268,12 @@ export default createRule({ } function traverseCallExpression(node: TSESTree.CallExpression): void { - const assertedArgument = findAssertedArgument(node); + const assertedArgument = findTruthinessAssertedArgument(services, node); if (assertedArgument != null) { traverseNode(assertedArgument, true); } } - /** - * Inspect a call expression to see if it's a call to an assertion function. - * If it is, return the node of the argument that is asserted. - */ - function findAssertedArgument( - node: TSESTree.CallExpression, - ): TSESTree.Expression | undefined { - // If the call looks like `assert(expr1, expr2, ...c, d, e, f)`, then we can - // only care if `expr1` or `expr2` is asserted, since anything that happens - // within or after a spread argument is out of scope to reason about. - const checkableArguments: TSESTree.Expression[] = []; - for (const argument of node.arguments) { - if (argument.type === AST_NODE_TYPES.SpreadElement) { - break; - } - - checkableArguments.push(argument); - } - - // nothing to do - if (checkableArguments.length === 0) { - return undefined; - } - - // Game plan: we're going to check the type of the callee. If it has call - // signatures and they _ALL_ agree that they assert on a parameter at the - // _SAME_ position, we'll consider the argument in that position to be an - // asserted argument. - const calleeType = getConstrainedTypeAtLocation(services, node.callee); - const callSignatures = tsutils.getCallSignaturesOfType(calleeType); - - let assertedParameterIndex: number | undefined = undefined; - for (const signature of callSignatures) { - const declaration = signature.getDeclaration(); - const returnTypeAnnotation = declaration.type; - - // Be sure we're dealing with a truthiness assertion function. - if ( - !( - returnTypeAnnotation != null && - ts.isTypePredicateNode(returnTypeAnnotation) && - // This eliminates things like `x is string` and `asserts x is T` - // leaving us with just the `asserts x` cases. - returnTypeAnnotation.type == null && - // I think this is redundant but, still, it needs to be true - returnTypeAnnotation.assertsModifier != null - ) - ) { - return undefined; - } - - const assertionTarget = returnTypeAnnotation.parameterName; - if (assertionTarget.kind !== ts.SyntaxKind.Identifier) { - // This can happen when asserting on `this`. Ignore! - return undefined; - } - - // If the first parameter is `this`, skip it, so that our index matches - // the index of the argument at the call site. - const firstParameter = declaration.parameters.at(0); - const nonThisParameters = - firstParameter?.name.kind === ts.SyntaxKind.Identifier && - firstParameter.name.text === 'this' - ? declaration.parameters.slice(1) - : declaration.parameters; - - // Don't bother inspecting parameters past the number of - // arguments we have at the call site. - const checkableNonThisParameters = nonThisParameters.slice( - 0, - checkableArguments.length, - ); - - let assertedParameterIndexForThisSignature: number | undefined; - for (const [index, parameter] of checkableNonThisParameters.entries()) { - if (parameter.dotDotDotToken != null) { - // Cannot assert a rest parameter, and can't have a rest parameter - // before the asserted parameter. It's not only a TS error, it's - // not something we can logically make sense of, so give up here. - return undefined; - } - - if (parameter.name.kind !== ts.SyntaxKind.Identifier) { - // Only identifiers are valid for assertion targets, so skip over - // anything like `{ destructuring: parameter }: T` - continue; - } - - // we've found a match between the "target"s in - // `function asserts(target: T): asserts target;` - if (parameter.name.text === assertionTarget.text) { - assertedParameterIndexForThisSignature = index; - break; - } - } - - if (assertedParameterIndexForThisSignature == null) { - // Didn't find an assertion target in this signature that could match - // the call site. - return undefined; - } - - if ( - assertedParameterIndex != null && - assertedParameterIndex !== assertedParameterIndexForThisSignature - ) { - // The asserted parameter we found for this signature didn't match - // previous signatures. - return undefined; - } - - assertedParameterIndex = assertedParameterIndexForThisSignature; - } - - // Didn't find a unique assertion index. - if (assertedParameterIndex == null) { - return undefined; - } - - return checkableArguments[assertedParameterIndex]; - } - /** * Inspects any node. * diff --git a/packages/eslint-plugin/src/util/assertionFunctionUtils.ts b/packages/eslint-plugin/src/util/assertionFunctionUtils.ts new file mode 100644 index 000000000000..c561d1c59df8 --- /dev/null +++ b/packages/eslint-plugin/src/util/assertionFunctionUtils.ts @@ -0,0 +1,119 @@ +import type { + ParserServicesWithTypeInformation, + TSESTree, +} from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import * as ts from 'typescript'; + +/** + * Inspect a call expression to see if it's a call to an assertion function. + * If it is, return the node of the argument that is asserted. + */ +export function findTruthinessAssertedArgument( + services: ParserServicesWithTypeInformation, + node: TSESTree.CallExpression, +): TSESTree.Expression | undefined { + // If the call looks like `assert(expr1, expr2, ...c, d, e, f)`, then we can + // only care if `expr1` or `expr2` is asserted, since anything that happens + // within or after a spread argument is out of scope to reason about. + const checkableArguments: TSESTree.Expression[] = []; + for (const argument of node.arguments) { + if (argument.type === AST_NODE_TYPES.SpreadElement) { + break; + } + checkableArguments.push(argument); + } + + // nothing to do + if (checkableArguments.length === 0) { + return undefined; + } + + const checker = services.program.getTypeChecker(); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); + const signature = checker.getResolvedSignature(tsNode); + + if (signature == null) { + return undefined; + } + + const firstTypePredicateResult = + checker.getTypePredicateOfSignature(signature); + + if (firstTypePredicateResult == null) { + return undefined; + } + + const { parameterIndex, kind, type } = firstTypePredicateResult; + if (!(kind === ts.TypePredicateKind.AssertsIdentifier && type == null)) { + return undefined; + } + + return checkableArguments.at(parameterIndex); +} + +/** + * Inspect a call expression to see if it's a call to an assertion function. + * If it is, return the node of the argument that is asserted and other useful info. + */ +export function findTypeGuardAssertedArgument( + services: ParserServicesWithTypeInformation, + node: TSESTree.CallExpression, +): + | { + asserts: boolean; + argument: TSESTree.Expression; + type: ts.Type; + } + | undefined { + // If the call looks like `assert(expr1, expr2, ...c, d, e, f)`, then we can + // only care if `expr1` or `expr2` is asserted, since anything that happens + // within or after a spread argument is out of scope to reason about. + const checkableArguments: TSESTree.Expression[] = []; + for (const argument of node.arguments) { + if (argument.type === AST_NODE_TYPES.SpreadElement) { + break; + } + checkableArguments.push(argument); + } + + // nothing to do + if (checkableArguments.length === 0) { + return undefined; + } + + const checker = services.program.getTypeChecker(); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); + const callSignature = checker.getResolvedSignature(tsNode); + + if (callSignature == null) { + return undefined; + } + + const typePredicateInfo = checker.getTypePredicateOfSignature(callSignature); + + if (typePredicateInfo == null) { + return undefined; + } + + const { parameterIndex, kind, type } = typePredicateInfo; + if ( + !( + (kind === ts.TypePredicateKind.AssertsIdentifier || + kind === ts.TypePredicateKind.Identifier) && + type != null + ) + ) { + return undefined; + } + + if (parameterIndex >= checkableArguments.length) { + return undefined; + } + + return { + type, + asserts: kind === ts.TypePredicateKind.AssertsIdentifier, + argument: checkableArguments[parameterIndex], + }; +} diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-condition.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-condition.shot index 508ffbe028f6..3ec0af465b4f 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-condition.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-condition.shot @@ -66,3 +66,41 @@ for (; true; ) {} do {} while (true); " `; + +exports[`Validating rule docs no-unnecessary-condition.mdx code examples ESLint output 4`] = ` +"Options: { "checkTypePredicates": true } + +function assert(condition: unknown): asserts condition { + if (!condition) { + throw new Error('Condition is falsy'); + } +} + +assert(false); // Unnecessary; condition is always falsy. + ~~~~~ Unnecessary conditional, value is always falsy. + +const neverNull = {}; +assert(neverNull); // Unnecessary; condition is always truthy. + ~~~~~~~~~ Unnecessary conditional, value is always truthy. + +function isString(value: unknown): value is string { + return typeof value === 'string'; +} + +declare const s: string; + +// Unnecessary; s is always a string. +if (isString(s)) { + ~ Unnecessary conditional, expression already has the type being checked by the type guard. +} + +function assertIsString(value: unknown): asserts value is string { + if (!isString(value)) { + throw new Error('Value is not a string'); + } +} + +assertIsString(s); // Unnecessary; s is always a string. + ~ Unnecessary conditional, expression already has the type being checked by the assertion function. +" +`; diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts index 9fbd0eb7ac6f..91e770cefef9 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -894,7 +894,103 @@ class ConsistentRand { } `, }, + { + code: ` +declare function assert(x: unknown): asserts x; + +assert(Math.random() > 0.5); + `, + options: [{ checkTypePredicates: true }], + }, + { + code: ` +declare function assert(x: unknown, y: unknown): asserts x; + +assert(Math.random() > 0.5, true); + `, + options: [{ checkTypePredicates: true }], + }, + { + // should not report because option is disabled. + code: ` +declare function assert(x: unknown): asserts x; +assert(true); + `, + options: [{ checkTypePredicates: false }], + }, + { + // could be argued that this should report since `thisAsserter` is truthy. + code: ` +class ThisAsserter { + assertThis(this: unknown, arg2: unknown): asserts this {} +} + +const thisAsserter: ThisAsserter = new ThisAsserter(); +thisAsserter.assertThis(true); + `, + options: [{ checkTypePredicates: true }], + }, + { + // could be argued that this should report since `thisAsserter` is truthy. + code: ` +class ThisAsserter { + assertThis(this: unknown, arg2: unknown): asserts this {} +} + +const thisAsserter: ThisAsserter = new ThisAsserter(); +thisAsserter.assertThis(Math.random()); + `, + options: [{ checkTypePredicates: true }], + }, + { + code: ` +declare function assert(x: unknown): asserts x; +assert(...[]); + `, + options: [{ checkTypePredicates: true }], + }, + { + // ok to report if we start unpacking spread params one day. + code: ` +declare function assert(x: unknown): asserts x; +assert(...[], {}); + `, + options: [{ checkTypePredicates: true }], + }, + { + code: ` +declare function assertString(x: unknown): asserts x is string; +declare const a: string; +assertString(a); + `, + options: [{ checkTypePredicates: false }], + }, + { + code: ` +declare function isString(x: unknown): x is string; +declare const a: string; +isString(a); + `, + options: [{ checkTypePredicates: false }], + }, + { + // Technically, this has type 'falafel' and not string. + code: ` +declare function assertString(x: unknown): asserts x is string; +assertString('falafel'); + `, + options: [{ checkTypePredicates: true }], + }, + { + // Technically, this has type 'falafel' and not string. + code: ` +declare function isString(x: unknown): x is string; +isString('falafel'); + `, + options: [{ checkTypePredicates: true }], + }, ], + invalid: [ // Ensure that it's checking in all the right places { @@ -2305,6 +2401,104 @@ foo?.['bar']?.().toExponential(); `, errors: [ruleError(3, 13, 'alwaysTruthy')], }, + { + code: ` +declare function assert(x: unknown): asserts x; +assert(true); + `, + errors: [ + { + line: 3, + messageId: 'alwaysTruthy', + }, + ], + options: [{ checkTypePredicates: true }], + }, + { + code: ` +declare function assert(x: unknown): asserts x; +assert(false); + `, + errors: [ + { + line: 3, + column: 8, + messageId: 'alwaysFalsy', + }, + ], + options: [{ checkTypePredicates: true }], + }, + { + code: ` +declare function assert(x: unknown, y: unknown): asserts x; + +assert(true, Math.random() > 0.5); + `, + options: [{ checkTypePredicates: true }], + errors: [ + { + messageId: 'alwaysTruthy', + line: 4, + column: 8, + }, + ], + }, + { + code: ` +declare function assert(x: unknown): asserts x; +assert({}); + `, + options: [{ checkTypePredicates: true }], + errors: [ + { + messageId: 'alwaysTruthy', + line: 3, + column: 8, + }, + ], + }, + { + code: ` +declare function assertsString(x: unknown): asserts x is string; +declare const a: string; +assertsString(a); + `, + options: [{ checkTypePredicates: true }], + errors: [ + { + messageId: 'typeGuardAlreadyIsType', + line: 4, + }, + ], + }, + { + code: ` +declare function isString(x: unknown): x is string; +declare const a: string; +isString(a); + `, + options: [{ checkTypePredicates: true }], + errors: [ + { + messageId: 'typeGuardAlreadyIsType', + line: 4, + }, + ], + }, + { + code: ` +declare function isString(x: unknown): x is string; +declare const a: string; +isString('fa' + 'lafel'); + `, + options: [{ checkTypePredicates: true }], + errors: [ + { + messageId: 'typeGuardAlreadyIsType', + line: 4, + }, + ], + }, // "branded" types unnecessaryConditionTest('"" & {}', 'alwaysFalsy'), diff --git a/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts b/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts index 6706f6e10c6b..50395575a81f 100644 --- a/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts @@ -439,14 +439,6 @@ assert(boo, nullableString); declare function assert(a: boolean, b: unknown): asserts b is string; declare const nullableString: string | null; declare const boo: boolean; -assert(boo, nullableString); - `, - // Intentional TS error - cannot assert a parameter in a binding pattern. - ` -declare function assert(a: boolean, b: unknown): asserts b; -declare function assert(a: boolean, { b }: { b: unknown }): asserts b; -declare const nullableString: string | null; -declare const boo: boolean; assert(boo, nullableString); `, ` @@ -516,39 +508,6 @@ declare const nullableString: string | null; assert(3 as any, nullableString); `, }, - // Intentional TS error - A rest parameter must be last in a parameter list. - // This is just to test that we don't crash or falsely report. - ` -declare function assert(...a: boolean[], b: unknown): asserts b; -declare const nullableString: string | null; -declare const boo: boolean; -assert(boo, nullableString); - `, - // Intentional TS error - A type predicate cannot reference a rest parameter. - // This is just to test that we don't crash or falsely report. - ` -declare function assert(a: boolean, ...b: unknown[]): asserts b; -declare const nullableString: string | null; -declare const boo: boolean; -assert(boo, nullableString); - `, - // Intentional TS error - An assertion function must have a parameter to assert. - // This is just to test that we don't crash or falsely report. - ` -declare function assert(): asserts x; -declare const nullableString: string | null; -assert(nullableString); - `, - ` -function assert(one: unknown): asserts one; -function assert(one: unknown, two: unknown): asserts two; -function assert(...args: unknown[]) { - throw new Error('not implemented'); -} -declare const nullableString: string | null; -assert(nullableString); -assert('one', nullableString); - `, // Intentional use of `any` to test a function call with no call signatures. ` declare const assert: any; @@ -2578,6 +2537,12 @@ assert(foo, Boolean(nullableString)); ], }, { + // This should be checkable, but the TS API doesn't currently report + // `someAssert(maybeString)` as a type predicate call, which appears to be + // a bug. + // + // See https://github.com/microsoft/TypeScript/issues/59707 + skip: true, code: ` function asserts1(x: string | number | undefined): asserts x {} function asserts2(x: string | number | undefined): asserts x {} @@ -2889,5 +2854,61 @@ assert(boo, Boolean(nullableString)); }, ], }, + { + // This report matches TS's analysis, which selects the assertion overload. + code: ` +function assert(one: unknown): asserts one; +function assert(one: unknown, two: unknown): asserts two; +function assert(...args: unknown[]) { + throw new Error('not implemented'); +} +declare const nullableString: string | null; +assert(nullableString); + `, + errors: [ + { + messageId: 'conditionErrorNullableString', + line: 8, + suggestions: [ + { + messageId: 'conditionFixCompareNullish', + output: ` +function assert(one: unknown): asserts one; +function assert(one: unknown, two: unknown): asserts two; +function assert(...args: unknown[]) { + throw new Error('not implemented'); +} +declare const nullableString: string | null; +assert(nullableString != null); + `, + }, + { + messageId: 'conditionFixDefaultEmptyString', + output: ` +function assert(one: unknown): asserts one; +function assert(one: unknown, two: unknown): asserts two; +function assert(...args: unknown[]) { + throw new Error('not implemented'); +} +declare const nullableString: string | null; +assert(nullableString ?? ""); + `, + }, + { + messageId: 'conditionFixCastBoolean', + output: ` +function assert(one: unknown): asserts one; +function assert(one: unknown, two: unknown): asserts two; +function assert(...args: unknown[]) { + throw new Error('not implemented'); +} +declare const nullableString: string | null; +assert(Boolean(nullableString)); + `, + }, + ], + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-condition.shot b/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-condition.shot index 4f7fef6518ec..824987f01dbd 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-condition.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-condition.shot @@ -15,6 +15,10 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing": { "description": "Whether to not error when running with a tsconfig that has strictNullChecks turned.", "type": "boolean" + }, + "checkTypePredicates": { + "description": "Whether to check the asserted argument of a type predicate function for unnecessary conditions", + "type": "boolean" } }, "type": "object" @@ -30,6 +34,8 @@ type Options = [ allowConstantLoopConditions?: boolean; /** Whether to not error when running with a tsconfig that has strictNullChecks turned. */ allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean; + /** Whether to check the asserted argument of a type predicate function for unnecessary conditions */ + checkTypePredicates?: boolean; }, ]; " diff --git a/packages/type-utils/src/getTypeName.ts b/packages/type-utils/src/getTypeName.ts index ea3f69e4b5d6..72395f996ca0 100644 --- a/packages/type-utils/src/getTypeName.ts +++ b/packages/type-utils/src/getTypeName.ts @@ -23,8 +23,9 @@ export function getTypeName( // via AST. const symbol = type.getSymbol(); const decls = symbol?.getDeclarations(); - const typeParamDecl = decls?.[0] as ts.TypeParameterDeclaration; + const typeParamDecl = decls?.[0]; if ( + typeParamDecl != null && ts.isTypeParameterDeclaration(typeParamDecl) && typeParamDecl.constraint != null ) { diff --git a/packages/typescript-estree/tests/lib/semanticInfo.test.ts b/packages/typescript-estree/tests/lib/semanticInfo.test.ts index c191fc65c356..a5378e52bc9a 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.test.ts @@ -172,7 +172,7 @@ describe('semanticInfo', () => { ).declarations[0].init!; const tsBinaryExpression = parseResult.services.esTreeNodeToTSNodeMap.get(binaryExpression); - expectToBeDefined(tsBinaryExpression); + expect(tsBinaryExpression).toBeDefined(); expect(tsBinaryExpression.kind).toEqual(ts.SyntaxKind.BinaryExpression); const computedPropertyString = ( @@ -181,7 +181,7 @@ describe('semanticInfo', () => { ).key; const tsComputedPropertyString = parseResult.services.esTreeNodeToTSNodeMap.get(computedPropertyString); - expectToBeDefined(tsComputedPropertyString); + expect(tsComputedPropertyString).toBeDefined(); expect(tsComputedPropertyString.kind).toEqual(ts.SyntaxKind.StringLiteral); }); @@ -210,7 +210,7 @@ describe('semanticInfo', () => { expectToHaveParserServices(parseResult.services); const tsArrayBoundName = parseResult.services.esTreeNodeToTSNodeMap.get(arrayBoundName); - expectToBeDefined(tsArrayBoundName); + expect(tsArrayBoundName).toBeDefined(); checkNumberArrayType(checker, tsArrayBoundName); expect( @@ -235,7 +235,6 @@ describe('semanticInfo', () => { const tsBoundName = parseResult.services.esTreeNodeToTSNodeMap.get(boundName); - expectToBeDefined(tsBoundName); expect(tsBoundName).toBeDefined(); expect(parseResult.services.tsNodeToESTreeNodeMap.get(tsBoundName)).toBe( @@ -420,7 +419,7 @@ function testIsolatedFile( // get type checker expectToHaveParserServices(parseResult.services); const checker = parseResult.services.program.getTypeChecker(); - expectToBeDefined(checker); + expect(checker).toBeDefined(); // get number node (ast shape validated by snapshot) const declaration = (parseResult.ast.body[0] as TSESTree.VariableDeclaration) @@ -431,7 +430,7 @@ function testIsolatedFile( // get corresponding TS node const tsArrayMember = parseResult.services.esTreeNodeToTSNodeMap.get(arrayMember); - expectToBeDefined(tsArrayMember); + expect(tsArrayMember).toBeDefined(); expect(tsArrayMember.kind).toBe(ts.SyntaxKind.NumericLiteral); expect((tsArrayMember as ts.NumericLiteral).text).toBe('3'); @@ -451,7 +450,7 @@ function testIsolatedFile( const boundName = declaration.id as TSESTree.Identifier; expect(boundName.name).toBe('x'); const tsBoundName = parseResult.services.esTreeNodeToTSNodeMap.get(boundName); - expectToBeDefined(tsBoundName); + expect(tsBoundName).toBeDefined(); checkNumberArrayType(checker, tsBoundName); expect(parseResult.services.tsNodeToESTreeNodeMap.get(tsBoundName)).toBe( boundName, diff --git a/packages/typescript-estree/tests/lib/source-files.test.ts b/packages/typescript-estree/tests/lib/source-files.test.ts index e6edb1c9c657..1986b25094e3 100644 --- a/packages/typescript-estree/tests/lib/source-files.test.ts +++ b/packages/typescript-estree/tests/lib/source-files.test.ts @@ -13,6 +13,7 @@ describe('isSourceFile', () => { it('returns true when given a real source file', () => { const input = ts.createSourceFile('test.ts', '', ts.ScriptTarget.ESNext); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- intentionally testing that the types match reality expect(isSourceFile(input)).toBe(true); }); }); diff --git a/packages/typescript-estree/tests/test-utils/test-utils.ts b/packages/typescript-estree/tests/test-utils/test-utils.ts index ed18eb6fb3fe..b307e755e880 100644 --- a/packages/typescript-estree/tests/test-utils/test-utils.ts +++ b/packages/typescript-estree/tests/test-utils/test-utils.ts @@ -83,7 +83,7 @@ export function deeplyCopy>(ast: T): T { type UnknownObject = Record; -function isObjectLike(value: unknown): value is UnknownObject { +function isObjectLike(value: unknown): boolean { return ( typeof value === 'object' && !(value instanceof RegExp) && value != null ); diff --git a/packages/website/src/components/typeDetails/SimplifiedTreeView.tsx b/packages/website/src/components/typeDetails/SimplifiedTreeView.tsx index c5dc1b37926d..745fd08992fa 100644 --- a/packages/website/src/components/typeDetails/SimplifiedTreeView.tsx +++ b/packages/website/src/components/typeDetails/SimplifiedTreeView.tsx @@ -6,7 +6,7 @@ import styles from '../ast/ASTViewer.module.css'; import PropertyName from '../ast/PropertyName'; import { tsEnumToString } from '../ast/tsUtils'; import type { OnHoverNodeFn } from '../ast/types'; -import { getRange, isTSNode } from '../ast/utils'; +import { getRange } from '../ast/utils'; export interface SimplifiedTreeViewProps { readonly value: ts.Node; @@ -31,7 +31,7 @@ function SimplifiedItem({ const onHover = useCallback( (v: boolean) => { - if (isTSNode(value) && onHoverNode) { + if (onHoverNode) { return onHoverNode(v ? getRange(value, 'tsNode') : undefined); } }, From 6ce66b59ec3b4ab01d3a6505fb9d08955ef12008 Mon Sep 17 00:00:00 2001 From: auvred <61150013+auvred@users.noreply.github.com> Date: Mon, 30 Sep 2024 13:15:16 +0000 Subject: [PATCH 13/18] fix: remove `export type *` in d.ts to support TS<5.0 (#10070) --- eslint.config.mjs | 4 +- packages/ast-spec/src/declaration/spec.ts | 28 +++--- packages/ast-spec/src/element/spec.ts | 30 +++--- .../expression/AssignmentExpression/spec.ts | 2 +- .../src/expression/BinaryExpression/spec.ts | 2 +- .../ast-spec/src/expression/literal/spec.ts | 12 +-- packages/ast-spec/src/expression/spec.ts | 68 +++++++------- packages/ast-spec/src/index.ts | 94 +++++++++---------- packages/ast-spec/src/jsx/spec.ts | 26 ++--- packages/ast-spec/src/parameter/spec.ts | 10 +- packages/ast-spec/src/special/spec.ts | 48 +++++----- packages/ast-spec/src/statement/spec.ts | 36 +++---- .../src/token/PunctuatorToken/spec.ts | 2 +- packages/ast-spec/src/token/spec.ts | 26 ++--- packages/ast-spec/src/type/spec.ts | 90 +++++++++--------- packages/eslint-plugin/src/util/index.ts | 2 +- .../scope-manager/src/definition/index.ts | 2 +- packages/scope-manager/src/scope/index.ts | 2 +- packages/types/src/index.ts | 4 +- .../typescript-estree/src/ts-estree/index.ts | 4 +- packages/utils/src/eslint-utils/index.ts | 2 +- packages/utils/src/index.ts | 2 +- packages/utils/src/ts-eslint/index.ts | 12 +-- 23 files changed, 255 insertions(+), 253 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 6e510fb45010..7fc85c328dcd 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -113,8 +113,10 @@ export default tseslint.config( minimumDescriptionLength: 5, }, ], + // TODO: enable it once we drop support for TS<5.0 + // https://github.com/typescript-eslint/typescript-eslint/issues/10065 '@typescript-eslint/consistent-type-exports': [ - 'error', + 'off', // 'error', { fixMixedExportsWithInlineTypeSpecifier: true }, ], '@typescript-eslint/consistent-type-imports': [ diff --git a/packages/ast-spec/src/declaration/spec.ts b/packages/ast-spec/src/declaration/spec.ts index 51e982c3089b..52ab7c72bab6 100644 --- a/packages/ast-spec/src/declaration/spec.ts +++ b/packages/ast-spec/src/declaration/spec.ts @@ -1,14 +1,14 @@ -export type * from './ClassDeclaration/spec'; -export type * from './ExportAllDeclaration/spec'; -export type * from './ExportDefaultDeclaration/spec'; -export type * from './ExportNamedDeclaration/spec'; -export type * from './FunctionDeclaration/spec'; -export type * from './ImportDeclaration/spec'; -export type * from './TSDeclareFunction/spec'; -export type * from './TSEnumDeclaration/spec'; -export type * from './TSImportEqualsDeclaration/spec'; -export type * from './TSInterfaceDeclaration/spec'; -export type * from './TSModuleDeclaration/spec'; -export type * from './TSNamespaceExportDeclaration/spec'; -export type * from './TSTypeAliasDeclaration/spec'; -export type * from './VariableDeclaration/spec'; +export * from './ClassDeclaration/spec'; +export * from './ExportAllDeclaration/spec'; +export * from './ExportDefaultDeclaration/spec'; +export * from './ExportNamedDeclaration/spec'; +export * from './FunctionDeclaration/spec'; +export * from './ImportDeclaration/spec'; +export * from './TSDeclareFunction/spec'; +export * from './TSEnumDeclaration/spec'; +export * from './TSImportEqualsDeclaration/spec'; +export * from './TSInterfaceDeclaration/spec'; +export * from './TSModuleDeclaration/spec'; +export * from './TSNamespaceExportDeclaration/spec'; +export * from './TSTypeAliasDeclaration/spec'; +export * from './VariableDeclaration/spec'; diff --git a/packages/ast-spec/src/element/spec.ts b/packages/ast-spec/src/element/spec.ts index 5a3105246aa5..6edf6efe39af 100644 --- a/packages/ast-spec/src/element/spec.ts +++ b/packages/ast-spec/src/element/spec.ts @@ -1,15 +1,15 @@ -export type * from './AccessorProperty/spec'; -export type * from './MethodDefinition/spec'; -export type * from './Property/spec'; -export type * from './PropertyDefinition/spec'; -export type * from './SpreadElement/spec'; -export type * from './StaticBlock/spec'; -export type * from './TSAbstractAccessorProperty/spec'; -export type * from './TSAbstractMethodDefinition/spec'; -export type * from './TSAbstractPropertyDefinition/spec'; -export type * from './TSCallSignatureDeclaration/spec'; -export type * from './TSConstructSignatureDeclaration/spec'; -export type * from './TSEnumMember/spec'; -export type * from './TSIndexSignature/spec'; -export type * from './TSMethodSignature/spec'; -export type * from './TSPropertySignature/spec'; +export * from './AccessorProperty/spec'; +export * from './MethodDefinition/spec'; +export * from './Property/spec'; +export * from './PropertyDefinition/spec'; +export * from './SpreadElement/spec'; +export * from './StaticBlock/spec'; +export * from './TSAbstractAccessorProperty/spec'; +export * from './TSAbstractMethodDefinition/spec'; +export * from './TSAbstractPropertyDefinition/spec'; +export * from './TSCallSignatureDeclaration/spec'; +export * from './TSConstructSignatureDeclaration/spec'; +export * from './TSEnumMember/spec'; +export * from './TSIndexSignature/spec'; +export * from './TSMethodSignature/spec'; +export * from './TSPropertySignature/spec'; diff --git a/packages/ast-spec/src/expression/AssignmentExpression/spec.ts b/packages/ast-spec/src/expression/AssignmentExpression/spec.ts index ebbd420cb05e..d4cfeea7ef28 100644 --- a/packages/ast-spec/src/expression/AssignmentExpression/spec.ts +++ b/packages/ast-spec/src/expression/AssignmentExpression/spec.ts @@ -4,7 +4,7 @@ import type { Expression } from '../../unions/Expression'; import type { ValueOf } from '../../utils'; import type { AssignmentOperatorToText } from './AssignmentOperatorToText'; -export type * from './AssignmentOperatorToText'; +export * from './AssignmentOperatorToText'; export interface AssignmentExpression extends BaseNode { left: Expression; diff --git a/packages/ast-spec/src/expression/BinaryExpression/spec.ts b/packages/ast-spec/src/expression/BinaryExpression/spec.ts index 2c8b82fe0968..9547483403bb 100644 --- a/packages/ast-spec/src/expression/BinaryExpression/spec.ts +++ b/packages/ast-spec/src/expression/BinaryExpression/spec.ts @@ -5,7 +5,7 @@ import type { Expression } from '../../unions/Expression'; import type { ValueOf } from '../../utils'; import type { BinaryOperatorToText } from './BinaryOperatorToText'; -export type * from './BinaryOperatorToText'; +export * from './BinaryOperatorToText'; export interface BinaryExpression extends BaseNode { left: Expression | PrivateIdentifier; diff --git a/packages/ast-spec/src/expression/literal/spec.ts b/packages/ast-spec/src/expression/literal/spec.ts index ea08cb1b1259..d40804b424e1 100644 --- a/packages/ast-spec/src/expression/literal/spec.ts +++ b/packages/ast-spec/src/expression/literal/spec.ts @@ -1,6 +1,6 @@ -export type * from './BigIntLiteral/spec'; -export type * from './BooleanLiteral/spec'; -export type * from './NullLiteral/spec'; -export type * from './NumberLiteral/spec'; -export type * from './RegExpLiteral/spec'; -export type * from './StringLiteral/spec'; +export * from './BigIntLiteral/spec'; +export * from './BooleanLiteral/spec'; +export * from './NullLiteral/spec'; +export * from './NumberLiteral/spec'; +export * from './RegExpLiteral/spec'; +export * from './StringLiteral/spec'; diff --git a/packages/ast-spec/src/expression/spec.ts b/packages/ast-spec/src/expression/spec.ts index 5540ebdf10c7..5162ebf49116 100644 --- a/packages/ast-spec/src/expression/spec.ts +++ b/packages/ast-spec/src/expression/spec.ts @@ -1,34 +1,34 @@ -export type * from './ArrayExpression/spec'; -export type * from './ArrowFunctionExpression/spec'; -export type * from './AssignmentExpression/spec'; -export type * from './AwaitExpression/spec'; -export type * from './BinaryExpression/spec'; -export type * from './CallExpression/spec'; -export type * from './ChainExpression/spec'; -export type * from './ClassExpression/spec'; -export type * from './ConditionalExpression/spec'; -export type * from './FunctionExpression/spec'; -export type * from './Identifier/spec'; -export type * from './ImportExpression/spec'; -export type * from './JSXElement/spec'; -export type * from './JSXFragment/spec'; -export type * from './literal/spec'; -export type * from './LogicalExpression/spec'; -export type * from './MemberExpression/spec'; -export type * from './MetaProperty/spec'; -export type * from './NewExpression/spec'; -export type * from './ObjectExpression/spec'; -export type * from './SequenceExpression/spec'; -export type * from './Super/spec'; -export type * from './TaggedTemplateExpression/spec'; -export type * from './TemplateLiteral/spec'; -export type * from './ThisExpression/spec'; -export type * from './TSAsExpression/spec'; -export type * from './TSEmptyBodyFunctionExpression/spec'; -export type * from './TSInstantiationExpression/spec'; -export type * from './TSNonNullExpression/spec'; -export type * from './TSSatisfiesExpression/spec'; -export type * from './TSTypeAssertion/spec'; -export type * from './UnaryExpression/spec'; -export type * from './UpdateExpression/spec'; -export type * from './YieldExpression/spec'; +export * from './ArrayExpression/spec'; +export * from './ArrowFunctionExpression/spec'; +export * from './AssignmentExpression/spec'; +export * from './AwaitExpression/spec'; +export * from './BinaryExpression/spec'; +export * from './CallExpression/spec'; +export * from './ChainExpression/spec'; +export * from './ClassExpression/spec'; +export * from './ConditionalExpression/spec'; +export * from './FunctionExpression/spec'; +export * from './Identifier/spec'; +export * from './ImportExpression/spec'; +export * from './JSXElement/spec'; +export * from './JSXFragment/spec'; +export * from './literal/spec'; +export * from './LogicalExpression/spec'; +export * from './MemberExpression/spec'; +export * from './MetaProperty/spec'; +export * from './NewExpression/spec'; +export * from './ObjectExpression/spec'; +export * from './SequenceExpression/spec'; +export * from './Super/spec'; +export * from './TaggedTemplateExpression/spec'; +export * from './TemplateLiteral/spec'; +export * from './ThisExpression/spec'; +export * from './TSAsExpression/spec'; +export * from './TSEmptyBodyFunctionExpression/spec'; +export * from './TSInstantiationExpression/spec'; +export * from './TSNonNullExpression/spec'; +export * from './TSSatisfiesExpression/spec'; +export * from './TSTypeAssertion/spec'; +export * from './UnaryExpression/spec'; +export * from './UpdateExpression/spec'; +export * from './YieldExpression/spec'; diff --git a/packages/ast-spec/src/index.ts b/packages/ast-spec/src/index.ts index 22cc288141ad..f46a8a804b5b 100644 --- a/packages/ast-spec/src/index.ts +++ b/packages/ast-spec/src/index.ts @@ -1,49 +1,49 @@ export * from './ast-node-types'; export * from './ast-token-types'; -export type * from './base/Accessibility'; -export type * from './base/BaseNode'; // this is exported so that the `types` package can merge the decl and add the `parent` property -export type * from './base/NodeOrTokenData'; -export type * from './base/OptionalRangeAndLoc'; -export type * from './base/Position'; -export type * from './base/Range'; -export type * from './base/SourceLocation'; -export type * from './declaration/spec'; -export type * from './element/spec'; -export type * from './expression/spec'; -export type * from './jsx/spec'; -export type * from './parameter/spec'; -export type * from './special/spec'; -export type * from './statement/spec'; -export type * from './token/spec'; -export type * from './type/spec'; -export type * from './unions/BindingName'; -export type * from './unions/BindingPattern'; -export type * from './unions/CallExpressionArgument'; -export type * from './unions/ChainElement'; -export type * from './unions/ClassElement'; -export type * from './unions/Comment'; -export type * from './unions/DeclarationStatement'; -export type * from './unions/DestructuringPattern'; -export type * from './unions/EntityName'; -export type * from './unions/ExportDeclaration'; -export type * from './unions/Expression'; -export type * from './unions/ForInitialiser'; -export type * from './unions/FunctionLike'; -export type * from './unions/ImportClause'; -export type * from './unions/IterationStatement'; -export type * from './unions/JSXChild'; -export type * from './unions/JSXExpression'; -export type * from './unions/JSXTagNameExpression'; -export type * from './unions/LeftHandSideExpression'; -export type * from './unions/Literal'; -export type * from './unions/LiteralExpression'; -export type * from './unions/Node'; -export type * from './unions/ObjectLiteralElement'; -export type * from './unions/Parameter'; -export type * from './unions/PrimaryExpression'; -export type * from './unions/PropertyName'; -export type * from './unions/Statement'; -export type * from './unions/Token'; -export type * from './unions/TSUnaryExpression'; -export type * from './unions/TypeElement'; -export type * from './unions/TypeNode'; +export * from './base/Accessibility'; +export * from './base/BaseNode'; // this is exported so that the `types` package can merge the decl and add the `parent` property +export * from './base/NodeOrTokenData'; +export * from './base/OptionalRangeAndLoc'; +export * from './base/Position'; +export * from './base/Range'; +export * from './base/SourceLocation'; +export * from './declaration/spec'; +export * from './element/spec'; +export * from './expression/spec'; +export * from './jsx/spec'; +export * from './parameter/spec'; +export * from './special/spec'; +export * from './statement/spec'; +export * from './token/spec'; +export * from './type/spec'; +export * from './unions/BindingName'; +export * from './unions/BindingPattern'; +export * from './unions/CallExpressionArgument'; +export * from './unions/ChainElement'; +export * from './unions/ClassElement'; +export * from './unions/Comment'; +export * from './unions/DeclarationStatement'; +export * from './unions/DestructuringPattern'; +export * from './unions/EntityName'; +export * from './unions/ExportDeclaration'; +export * from './unions/Expression'; +export * from './unions/ForInitialiser'; +export * from './unions/FunctionLike'; +export * from './unions/ImportClause'; +export * from './unions/IterationStatement'; +export * from './unions/JSXChild'; +export * from './unions/JSXExpression'; +export * from './unions/JSXTagNameExpression'; +export * from './unions/LeftHandSideExpression'; +export * from './unions/Literal'; +export * from './unions/LiteralExpression'; +export * from './unions/Node'; +export * from './unions/ObjectLiteralElement'; +export * from './unions/Parameter'; +export * from './unions/PrimaryExpression'; +export * from './unions/PropertyName'; +export * from './unions/Statement'; +export * from './unions/Token'; +export * from './unions/TSUnaryExpression'; +export * from './unions/TypeElement'; +export * from './unions/TypeNode'; diff --git a/packages/ast-spec/src/jsx/spec.ts b/packages/ast-spec/src/jsx/spec.ts index 51f583d22347..1efb134bed7f 100644 --- a/packages/ast-spec/src/jsx/spec.ts +++ b/packages/ast-spec/src/jsx/spec.ts @@ -1,13 +1,13 @@ -export type * from './JSXAttribute/spec'; -export type * from './JSXClosingElement/spec'; -export type * from './JSXClosingFragment/spec'; -export type * from './JSXEmptyExpression/spec'; -export type * from './JSXExpressionContainer/spec'; -export type * from './JSXIdentifier/spec'; -export type * from './JSXMemberExpression/spec'; -export type * from './JSXNamespacedName/spec'; -export type * from './JSXOpeningElement/spec'; -export type * from './JSXOpeningFragment/spec'; -export type * from './JSXSpreadAttribute/spec'; -export type * from './JSXSpreadChild/spec'; -export type * from './JSXText/spec'; +export * from './JSXAttribute/spec'; +export * from './JSXClosingElement/spec'; +export * from './JSXClosingFragment/spec'; +export * from './JSXEmptyExpression/spec'; +export * from './JSXExpressionContainer/spec'; +export * from './JSXIdentifier/spec'; +export * from './JSXMemberExpression/spec'; +export * from './JSXNamespacedName/spec'; +export * from './JSXOpeningElement/spec'; +export * from './JSXOpeningFragment/spec'; +export * from './JSXSpreadAttribute/spec'; +export * from './JSXSpreadChild/spec'; +export * from './JSXText/spec'; diff --git a/packages/ast-spec/src/parameter/spec.ts b/packages/ast-spec/src/parameter/spec.ts index 54b1aa1ae048..b006664a36ae 100644 --- a/packages/ast-spec/src/parameter/spec.ts +++ b/packages/ast-spec/src/parameter/spec.ts @@ -1,5 +1,5 @@ -export type * from './ArrayPattern/spec'; -export type * from './AssignmentPattern/spec'; -export type * from './ObjectPattern/spec'; -export type * from './RestElement/spec'; -export type * from './TSParameterProperty/spec'; +export * from './ArrayPattern/spec'; +export * from './AssignmentPattern/spec'; +export * from './ObjectPattern/spec'; +export * from './RestElement/spec'; +export * from './TSParameterProperty/spec'; diff --git a/packages/ast-spec/src/special/spec.ts b/packages/ast-spec/src/special/spec.ts index aff8d8f5975b..1e7ad26877a9 100644 --- a/packages/ast-spec/src/special/spec.ts +++ b/packages/ast-spec/src/special/spec.ts @@ -1,24 +1,24 @@ -export type * from './CatchClause/spec'; -export type * from './ClassBody/spec'; -export type * from './Decorator/spec'; -export type * from './EmptyStatement/spec'; -export type * from './ExportSpecifier/spec'; -export type * from './ImportAttribute/spec'; -export type * from './ImportDefaultSpecifier/spec'; -export type * from './ImportNamespaceSpecifier/spec'; -export type * from './ImportSpecifier/spec'; -export type * from './PrivateIdentifier/spec'; -export type * from './Program/spec'; -export type * from './SwitchCase/spec'; -export type * from './TemplateElement/spec'; -export type * from './TSClassImplements/spec'; -export type * from './TSEnumBody/spec'; -export type * from './TSExternalModuleReference/spec'; -export type * from './TSInterfaceBody/spec'; -export type * from './TSInterfaceHeritage/spec'; -export type * from './TSModuleBlock/spec'; -export type * from './TSTypeAnnotation/spec'; -export type * from './TSTypeParameter/spec'; -export type * from './TSTypeParameterDeclaration/spec'; -export type * from './TSTypeParameterInstantiation/spec'; -export type * from './VariableDeclarator/spec'; +export * from './CatchClause/spec'; +export * from './ClassBody/spec'; +export * from './Decorator/spec'; +export * from './EmptyStatement/spec'; +export * from './ExportSpecifier/spec'; +export * from './ImportAttribute/spec'; +export * from './ImportDefaultSpecifier/spec'; +export * from './ImportNamespaceSpecifier/spec'; +export * from './ImportSpecifier/spec'; +export * from './PrivateIdentifier/spec'; +export * from './Program/spec'; +export * from './SwitchCase/spec'; +export * from './TemplateElement/spec'; +export * from './TSClassImplements/spec'; +export * from './TSEnumBody/spec'; +export * from './TSExternalModuleReference/spec'; +export * from './TSInterfaceBody/spec'; +export * from './TSInterfaceHeritage/spec'; +export * from './TSModuleBlock/spec'; +export * from './TSTypeAnnotation/spec'; +export * from './TSTypeParameter/spec'; +export * from './TSTypeParameterDeclaration/spec'; +export * from './TSTypeParameterInstantiation/spec'; +export * from './VariableDeclarator/spec'; diff --git a/packages/ast-spec/src/statement/spec.ts b/packages/ast-spec/src/statement/spec.ts index 5a961cf137b1..b581e0a26853 100644 --- a/packages/ast-spec/src/statement/spec.ts +++ b/packages/ast-spec/src/statement/spec.ts @@ -1,18 +1,18 @@ -export type * from './BlockStatement/spec'; -export type * from './BreakStatement/spec'; -export type * from './ContinueStatement/spec'; -export type * from './DebuggerStatement/spec'; -export type * from './DoWhileStatement/spec'; -export type * from './ExpressionStatement/spec'; -export type * from './ForInStatement/spec'; -export type * from './ForOfStatement/spec'; -export type * from './ForStatement/spec'; -export type * from './IfStatement/spec'; -export type * from './LabeledStatement/spec'; -export type * from './ReturnStatement/spec'; -export type * from './SwitchStatement/spec'; -export type * from './ThrowStatement/spec'; -export type * from './TryStatement/spec'; -export type * from './TSExportAssignment/spec'; -export type * from './WhileStatement/spec'; -export type * from './WithStatement/spec'; +export * from './BlockStatement/spec'; +export * from './BreakStatement/spec'; +export * from './ContinueStatement/spec'; +export * from './DebuggerStatement/spec'; +export * from './DoWhileStatement/spec'; +export * from './ExpressionStatement/spec'; +export * from './ForInStatement/spec'; +export * from './ForOfStatement/spec'; +export * from './ForStatement/spec'; +export * from './IfStatement/spec'; +export * from './LabeledStatement/spec'; +export * from './ReturnStatement/spec'; +export * from './SwitchStatement/spec'; +export * from './ThrowStatement/spec'; +export * from './TryStatement/spec'; +export * from './TSExportAssignment/spec'; +export * from './WhileStatement/spec'; +export * from './WithStatement/spec'; diff --git a/packages/ast-spec/src/token/PunctuatorToken/spec.ts b/packages/ast-spec/src/token/PunctuatorToken/spec.ts index 3ce886b69b64..733e0108d2f4 100644 --- a/packages/ast-spec/src/token/PunctuatorToken/spec.ts +++ b/packages/ast-spec/src/token/PunctuatorToken/spec.ts @@ -3,7 +3,7 @@ import type { BaseToken } from '../../base/BaseToken'; import type { ValueOf } from '../../utils'; import type { PunctuatorTokenToText } from './PunctuatorTokenToText'; -export type * from './PunctuatorTokenToText'; +export * from './PunctuatorTokenToText'; export interface PunctuatorToken extends BaseToken { type: AST_TOKEN_TYPES.Punctuator; diff --git a/packages/ast-spec/src/token/spec.ts b/packages/ast-spec/src/token/spec.ts index 91fbeb013601..3cffaf49e837 100644 --- a/packages/ast-spec/src/token/spec.ts +++ b/packages/ast-spec/src/token/spec.ts @@ -1,13 +1,13 @@ -export type * from './BlockComment/spec'; -export type * from './BooleanToken/spec'; -export type * from './IdentifierToken/spec'; -export type * from './JSXIdentifierToken/spec'; -export type * from './JSXTextToken/spec'; -export type * from './KeywordToken/spec'; -export type * from './LineComment/spec'; -export type * from './NullToken/spec'; -export type * from './NumericToken/spec'; -export type * from './PunctuatorToken/spec'; -export type * from './RegularExpressionToken/spec'; -export type * from './StringToken/spec'; -export type * from './TemplateToken/spec'; +export * from './BlockComment/spec'; +export * from './BooleanToken/spec'; +export * from './IdentifierToken/spec'; +export * from './JSXIdentifierToken/spec'; +export * from './JSXTextToken/spec'; +export * from './KeywordToken/spec'; +export * from './LineComment/spec'; +export * from './NullToken/spec'; +export * from './NumericToken/spec'; +export * from './PunctuatorToken/spec'; +export * from './RegularExpressionToken/spec'; +export * from './StringToken/spec'; +export * from './TemplateToken/spec'; diff --git a/packages/ast-spec/src/type/spec.ts b/packages/ast-spec/src/type/spec.ts index 92a78794f4a7..ae98ead2dc86 100644 --- a/packages/ast-spec/src/type/spec.ts +++ b/packages/ast-spec/src/type/spec.ts @@ -1,45 +1,45 @@ -export type * from './TSAbstractKeyword/spec'; -export type * from './TSAnyKeyword/spec'; -export type * from './TSArrayType/spec'; -export type * from './TSAsyncKeyword/spec'; -export type * from './TSBigIntKeyword/spec'; -export type * from './TSBooleanKeyword/spec'; -export type * from './TSConditionalType/spec'; -export type * from './TSConstructorType/spec'; -export type * from './TSDeclareKeyword/spec'; -export type * from './TSExportKeyword/spec'; -export type * from './TSFunctionType/spec'; -export type * from './TSImportType/spec'; -export type * from './TSIndexedAccessType/spec'; -export type * from './TSInferType/spec'; -export type * from './TSIntersectionType/spec'; -export type * from './TSIntrinsicKeyword/spec'; -export type * from './TSLiteralType/spec'; -export type * from './TSMappedType/spec'; -export type * from './TSNamedTupleMember/spec'; -export type * from './TSNeverKeyword/spec'; -export type * from './TSNullKeyword/spec'; -export type * from './TSNumberKeyword/spec'; -export type * from './TSObjectKeyword/spec'; -export type * from './TSOptionalType/spec'; -export type * from './TSPrivateKeyword/spec'; -export type * from './TSProtectedKeyword/spec'; -export type * from './TSPublicKeyword/spec'; -export type * from './TSQualifiedName/spec'; -export type * from './TSReadonlyKeyword/spec'; -export type * from './TSRestType/spec'; -export type * from './TSStaticKeyword/spec'; -export type * from './TSStringKeyword/spec'; -export type * from './TSSymbolKeyword/spec'; -export type * from './TSTemplateLiteralType/spec'; -export type * from './TSThisType/spec'; -export type * from './TSTupleType/spec'; -export type * from './TSTypeLiteral/spec'; -export type * from './TSTypeOperator/spec'; -export type * from './TSTypePredicate/spec'; -export type * from './TSTypeQuery/spec'; -export type * from './TSTypeReference/spec'; -export type * from './TSUndefinedKeyword/spec'; -export type * from './TSUnionType/spec'; -export type * from './TSUnknownKeyword/spec'; -export type * from './TSVoidKeyword/spec'; +export * from './TSAbstractKeyword/spec'; +export * from './TSAnyKeyword/spec'; +export * from './TSArrayType/spec'; +export * from './TSAsyncKeyword/spec'; +export * from './TSBigIntKeyword/spec'; +export * from './TSBooleanKeyword/spec'; +export * from './TSConditionalType/spec'; +export * from './TSConstructorType/spec'; +export * from './TSDeclareKeyword/spec'; +export * from './TSExportKeyword/spec'; +export * from './TSFunctionType/spec'; +export * from './TSImportType/spec'; +export * from './TSIndexedAccessType/spec'; +export * from './TSInferType/spec'; +export * from './TSIntersectionType/spec'; +export * from './TSIntrinsicKeyword/spec'; +export * from './TSLiteralType/spec'; +export * from './TSMappedType/spec'; +export * from './TSNamedTupleMember/spec'; +export * from './TSNeverKeyword/spec'; +export * from './TSNullKeyword/spec'; +export * from './TSNumberKeyword/spec'; +export * from './TSObjectKeyword/spec'; +export * from './TSOptionalType/spec'; +export * from './TSPrivateKeyword/spec'; +export * from './TSProtectedKeyword/spec'; +export * from './TSPublicKeyword/spec'; +export * from './TSQualifiedName/spec'; +export * from './TSReadonlyKeyword/spec'; +export * from './TSRestType/spec'; +export * from './TSStaticKeyword/spec'; +export * from './TSStringKeyword/spec'; +export * from './TSSymbolKeyword/spec'; +export * from './TSTemplateLiteralType/spec'; +export * from './TSThisType/spec'; +export * from './TSTupleType/spec'; +export * from './TSTypeLiteral/spec'; +export * from './TSTypeOperator/spec'; +export * from './TSTypePredicate/spec'; +export * from './TSTypeQuery/spec'; +export * from './TSTypeReference/spec'; +export * from './TSUndefinedKeyword/spec'; +export * from './TSUnionType/spec'; +export * from './TSUnknownKeyword/spec'; +export * from './TSVoidKeyword/spec'; diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index 95f756d6822d..b13b5855231d 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -18,7 +18,7 @@ export * from './misc'; export * from './needsPrecedingSemiColon'; export * from './objectIterators'; export * from './scopeUtils'; -export type * from './types'; +export * from './types'; export * from './isAssignee'; export * from './getFixOrSuggest'; export * from './isArrayMethodCallWithPredicate'; diff --git a/packages/scope-manager/src/definition/index.ts b/packages/scope-manager/src/definition/index.ts index d85a0d1c1e08..9bd1f0123370 100644 --- a/packages/scope-manager/src/definition/index.ts +++ b/packages/scope-manager/src/definition/index.ts @@ -1,6 +1,6 @@ export * from './CatchClauseDefinition'; export * from './ClassNameDefinition'; -export type * from './Definition'; +export * from './Definition'; export * from './DefinitionType'; export * from './FunctionNameDefinition'; export * from './ImplicitGlobalVariableDefinition'; diff --git a/packages/scope-manager/src/scope/index.ts b/packages/scope-manager/src/scope/index.ts index f1f31ced44c4..c94cc2982e1f 100644 --- a/packages/scope-manager/src/scope/index.ts +++ b/packages/scope-manager/src/scope/index.ts @@ -10,7 +10,7 @@ export * from './FunctionTypeScope'; export * from './GlobalScope'; export * from './MappedTypeScope'; export * from './ModuleScope'; -export type * from './Scope'; +export * from './Scope'; export * from './ScopeType'; export * from './SwitchScope'; export * from './TSEnumScope'; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 4429294e77e2..bb733d5275aa 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,4 +1,4 @@ export { AST_NODE_TYPES, AST_TOKEN_TYPES } from './generated/ast-spec'; -export type * from './lib'; -export type * from './parser-options'; +export * from './lib'; +export * from './parser-options'; export * from './ts-estree'; diff --git a/packages/typescript-estree/src/ts-estree/index.ts b/packages/typescript-estree/src/ts-estree/index.ts index f80c711bc8be..a7f64d91ce6b 100644 --- a/packages/typescript-estree/src/ts-estree/index.ts +++ b/packages/typescript-estree/src/ts-estree/index.ts @@ -4,5 +4,5 @@ export { AST_TOKEN_TYPES, TSESTree, } from '@typescript-eslint/types'; -export type * from './ts-nodes'; -export type * from './estree-to-ts-node-types'; +export * from './ts-nodes'; +export * from './estree-to-ts-node-types'; diff --git a/packages/utils/src/eslint-utils/index.ts b/packages/utils/src/eslint-utils/index.ts index e8a5401bbc09..632b6e051981 100644 --- a/packages/utils/src/eslint-utils/index.ts +++ b/packages/utils/src/eslint-utils/index.ts @@ -1,6 +1,6 @@ export * from './applyDefault'; export * from './deepMerge'; export * from './getParserServices'; -export type * from './InferTypesFromRule'; +export * from './InferTypesFromRule'; export * from './nullThrows'; export * from './RuleCreator'; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 42bc96dbcbbe..d6d907cd0ef4 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,7 +1,7 @@ export * as ASTUtils from './ast-utils'; export * as ESLintUtils from './eslint-utils'; -export type * as JSONSchema from './json-schema'; +export * as JSONSchema from './json-schema'; export * as TSESLint from './ts-eslint'; export * from './ts-estree'; export * as TSUtils from './ts-utils'; diff --git a/packages/utils/src/ts-eslint/index.ts b/packages/utils/src/ts-eslint/index.ts index d2e69666be1e..217b46dcbf7d 100644 --- a/packages/utils/src/ts-eslint/index.ts +++ b/packages/utils/src/ts-eslint/index.ts @@ -1,11 +1,11 @@ -export type * from './AST'; -export type * from './Config'; +export * from './AST'; +export * from './Config'; export * from './ESLint'; export * from './Linter'; -export type * from './Parser'; -export type * from './ParserOptions'; -export type * from './Processor'; -export type * from './Rule'; +export * from './Parser'; +export * from './ParserOptions'; +export * from './Processor'; +export * from './Rule'; export * from './RuleTester'; export * from './Scope'; export * from './SourceCode'; From 977e0a1c41b8715d107898072d599fbc85c13b79 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Mon, 30 Sep 2024 07:15:29 -0600 Subject: [PATCH 14/18] fix(eslint-plugin): [prefer-literal-enum-member] allow nested bitwise operations (#10037) * fix(eslint-plugin): [prefer-literal-enum-member] allow nested bitwise operations * tweak things slightly * move comment --- .../src/rules/prefer-literal-enum-member.ts | 97 +++++++++---------- .../rules/prefer-literal-enum-member.test.ts | 58 +++++++++++ 2 files changed, 105 insertions(+), 50 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts b/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts index 65336c9d2051..1535cca21ea7 100644 --- a/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts +++ b/packages/eslint-plugin/src/rules/prefer-literal-enum-member.ts @@ -77,69 +77,66 @@ export default createRule({ return false; } - function isAllowedBitwiseOperand( - decl: TSESTree.TSEnumDeclaration, - node: TSESTree.Node, - ): boolean { - return ( - node.type === AST_NODE_TYPES.Literal || isSelfEnumMember(decl, node) - ); - } - return { TSEnumMember(node): void { // If there is no initializer, then this node is just the name of the member, so ignore. if (node.initializer == null) { return; } - // any old literal - if (node.initializer.type === AST_NODE_TYPES.Literal) { - return; - } - // TemplateLiteral without expressions - if ( - node.initializer.type === AST_NODE_TYPES.TemplateLiteral && - node.initializer.expressions.length === 0 - ) { - return; - } const declaration = node.parent.parent; - // -1 and +1 - if (node.initializer.type === AST_NODE_TYPES.UnaryExpression) { - if ( - node.initializer.argument.type === AST_NODE_TYPES.Literal && - ['+', '-'].includes(node.initializer.operator) - ) { - return; + function isAllowedInitializerExpressionRecursive( + node: TSESTree.Expression | TSESTree.PrivateIdentifier, + partOfBitwiseComputation: boolean, + ): boolean { + // You can only refer to an enum member if it's part of a bitwise computation. + // so C = B isn't allowed (special case), but C = A | B is. + if (partOfBitwiseComputation && isSelfEnumMember(declaration, node)) { + return true; } - if ( - allowBitwiseExpressions && - node.initializer.operator === '~' && - isAllowedBitwiseOperand(declaration, node.initializer.argument) - ) { - return; + switch (node.type) { + // any old literal + case AST_NODE_TYPES.Literal: + return true; + + // TemplateLiteral without expressions + case AST_NODE_TYPES.TemplateLiteral: + return node.expressions.length === 0; + + case AST_NODE_TYPES.UnaryExpression: + // +123, -123, etc. + if (['+', '-'].includes(node.operator)) { + return isAllowedInitializerExpressionRecursive( + node.argument, + partOfBitwiseComputation, + ); + } + + if (allowBitwiseExpressions) { + return ( + node.operator === '~' && + isAllowedInitializerExpressionRecursive(node.argument, true) + ); + } + return false; + + case AST_NODE_TYPES.BinaryExpression: + if (allowBitwiseExpressions) { + return ( + ['|', '&', '^', '<<', '>>', '>>>'].includes(node.operator) && + isAllowedInitializerExpressionRecursive(node.left, true) && + isAllowedInitializerExpressionRecursive(node.right, true) + ); + } + return false; + + default: + return false; } } - if ( - node.initializer.type === AST_NODE_TYPES.UnaryExpression && - node.initializer.argument.type === AST_NODE_TYPES.Literal && - (['+', '-'].includes(node.initializer.operator) || - (allowBitwiseExpressions && node.initializer.operator === '~')) - ) { - return; - } - if ( - allowBitwiseExpressions && - node.initializer.type === AST_NODE_TYPES.BinaryExpression && - ['|', '&', '^', '<<', '>>', '>>>'].includes( - node.initializer.operator, - ) && - isAllowedBitwiseOperand(declaration, node.initializer.left) && - isAllowedBitwiseOperand(declaration, node.initializer.right) - ) { + if (isAllowedInitializerExpressionRecursive(node.initializer, false)) { return; } diff --git a/packages/eslint-plugin/tests/rules/prefer-literal-enum-member.test.ts b/packages/eslint-plugin/tests/rules/prefer-literal-enum-member.test.ts index 1247ee27bf29..d38232630cf6 100644 --- a/packages/eslint-plugin/tests/rules/prefer-literal-enum-member.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-literal-enum-member.test.ts @@ -110,6 +110,50 @@ enum Foo { enum Foo { ['A-1'] = 1 << 0, C = ~Foo['A-1'], +} + `, + options: [{ allowBitwiseExpressions: true }], + }, + { + code: ` +enum Foo { + A = 1 << 0, + B = 1 << 1, + C = 1 << 2, + D = A | B | C, +} + `, + options: [{ allowBitwiseExpressions: true }], + }, + { + code: ` +enum Foo { + A = 1 << 0, + B = 1 << 1, + C = 1 << 2, + D = Foo.A | Foo.B | Foo.C, +} + `, + options: [{ allowBitwiseExpressions: true }], + }, + { + code: ` +enum Foo { + A = 1 << 0, + B = 1 << 1, + C = 1 << 2, + D = Foo.A | (Foo.B & ~Foo.C), +} + `, + options: [{ allowBitwiseExpressions: true }], + }, + { + code: ` +enum Foo { + A = 1 << 0, + B = 1 << 1, + C = 1 << 2, + D = Foo.A | -Foo.B, } `, options: [{ allowBitwiseExpressions: true }], @@ -433,5 +477,19 @@ enum Foo { }, ], }, + { + code: ` +enum Foo { + A, + B = +A, +} + `, + errors: [ + { + messageId: 'notLiteral', + line: 4, + }, + ], + }, ], }); From 9028d9d3d751a9e68f6f4376d2a99ce9bb64def9 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Mon, 30 Sep 2024 07:16:40 -0600 Subject: [PATCH 15/18] docs: [no-unsafe-enum-comparison] clarify motivation and applicability (#10029) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josh Goldberg ✨ --- .../docs/rules/no-unsafe-enum-comparison.mdx | 28 +++++++++++++------ .../no-unsafe-enum-comparison.shot | 20 ++++++------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.mdx b/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.mdx index 7988317b5b6d..4b25e785e87d 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.mdx +++ b/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.mdx @@ -9,7 +9,9 @@ import TabItem from '@theme/TabItem'; > > See **https://typescript-eslint.io/rules/no-unsafe-enum-comparison** for documentation. -The TypeScript compiler can be surprisingly lenient when working with enums. String enums are widely considered to be safer than number enums, but even string enums have some pitfalls. For example, it is allowed to compare enum values against literals: +The TypeScript compiler can be surprisingly lenient when working with enums. +While overt safety problems with enums were [resolved in TypeScript 5.0](https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#all-enums-are-union-enums), some logical pitfalls remain permitted. +For example, it is allowed to compare enum values against non-enum values: ```ts enum Vegetable { @@ -21,7 +23,9 @@ declare const vegetable: Vegetable; vegetable === 'asparagus'; // No error ``` -The above code snippet should instead be written as `vegetable === Vegetable.Asparagus`. Allowing literals in comparisons subverts the point of using enums in the first place. By enforcing comparisons with properly typed enums: +The above code snippet should instead be written as `vegetable === Vegetable.Asparagus`. +Allowing non-enums in comparisons subverts the point of using enums in the first place. +By enforcing comparisons with properly typed enums: - It makes a codebase more resilient to enum members changing values. - It allows for code IDEs to use the "Rename Symbol" feature to quickly rename an enum. @@ -39,17 +43,22 @@ enum Fruit { declare let fruit: Fruit; +// bad - comparison between enum and explicit value instead of named enum member fruit === 0; -``` -```ts enum Vegetable { Asparagus = 'asparagus', } declare let vegetable: Vegetable; +// bad - comparison between enum and explicit value instead of named enum member vegetable === 'asparagus'; + +declare let anyString: string; + +// bad - comparison between enum and non-enum value +anyString === Vegetable.Asparagus; ``` @@ -63,9 +72,7 @@ enum Fruit { declare let fruit: Fruit; fruit === Fruit.Apple; -``` -```ts enum Vegetable { Asparagus = 'asparagus', } @@ -80,7 +87,12 @@ vegetable === Vegetable.Asparagus; ## When Not To Use It -If you don't mind number and/or literal string constants being compared against enums, you likely don't need this rule. +If you don't mind enums being treated as a namespaced bag of values, rather than opaque identifiers, you likely don't need this rule. + +Sometimes, you may want to ingest a value from an API or user input, then use it as an enum throughout your application. +While validating the input, it may be appropriate to disable the rule. +Alternately, you might consider making use of a validation library like [Zod](https://zod.dev/?id=native-enums). +See further discussion of this topic in [#8557](https://github.com/typescript-eslint/typescript-eslint/issues/8557). -Separately, in the rare case of relying on an third party enums that are only imported as `type`s, it may be difficult to adhere to this rule. +Finally, in the rare case of relying on an third party enums that are only imported as `type`s, it may be difficult to adhere to this rule. You might consider using [ESLint disable comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1) for those specific situations instead of completely disabling this rule. diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-enum-comparison.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-enum-comparison.shot index 2d883379f083..3b26a4df1d59 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-enum-comparison.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-enum-comparison.shot @@ -9,13 +9,9 @@ enum Fruit { declare let fruit: Fruit; +// bad - comparison between enum and explicit value instead of named enum member fruit === 0; ~~~~~~~~~~~ The two values in this comparison do not have a shared enum type. -" -`; - -exports[`Validating rule docs no-unsafe-enum-comparison.mdx code examples ESLint output 2`] = ` -"Incorrect enum Vegetable { Asparagus = 'asparagus', @@ -23,12 +19,19 @@ enum Vegetable { declare let vegetable: Vegetable; +// bad - comparison between enum and explicit value instead of named enum member vegetable === 'asparagus'; ~~~~~~~~~~~~~~~~~~~~~~~~~ The two values in this comparison do not have a shared enum type. + +declare let anyString: string; + +// bad - comparison between enum and non-enum value +anyString === Vegetable.Asparagus; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The two values in this comparison do not have a shared enum type. " `; -exports[`Validating rule docs no-unsafe-enum-comparison.mdx code examples ESLint output 3`] = ` +exports[`Validating rule docs no-unsafe-enum-comparison.mdx code examples ESLint output 2`] = ` "Correct enum Fruit { @@ -38,11 +41,6 @@ enum Fruit { declare let fruit: Fruit; fruit === Fruit.Apple; -" -`; - -exports[`Validating rule docs no-unsafe-enum-comparison.mdx code examples ESLint output 4`] = ` -"Correct enum Vegetable { Asparagus = 'asparagus', From 16f4d9081841e558f874aa94af654108b929913e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Mon, 30 Sep 2024 12:06:11 -0400 Subject: [PATCH 16/18] docs: add 'Typed Linting' blog post (#10025) * docs: add 'Typed Linting' blog post * fix: /getting-started/linting-with-type-information * fix tags * Maybe better explain type information... * more touchups * tuples * nit: traditional 'JS' rules * Apply suggestions from code review Co-authored-by: Kirk Waiblinger * mention: reliable --------- Co-authored-by: Kirk Waiblinger --- .cspell.json | 1 + docs/getting-started/Typed_Linting.mdx | 3 + .../website/blog/2024-09-30-typed-linting.md | 258 ++++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 packages/website/blog/2024-09-30-typed-linting.md diff --git a/.cspell.json b/.cspell.json index 93e100a70adf..deb654ab3735 100644 --- a/.cspell.json +++ b/.cspell.json @@ -83,6 +83,7 @@ "Cheung", "codebases", "Codecov", + "codemod", "contravariant", "Crockford", "declarators", diff --git a/docs/getting-started/Typed_Linting.mdx b/docs/getting-started/Typed_Linting.mdx index e669ac9cf11a..f6f04a21736a 100644 --- a/docs/getting-started/Typed_Linting.mdx +++ b/docs/getting-started/Typed_Linting.mdx @@ -16,6 +16,9 @@ To tap into TypeScript's additional powers, there are two small changes you need 2. Add `languageOptions.parserOptions` to tell our parser how to find the TSConfig for each source file. ```js title="eslint.config.mjs" +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; + export default tseslint.config( eslint.configs.recommended, // Remove this line diff --git a/packages/website/blog/2024-09-30-typed-linting.md b/packages/website/blog/2024-09-30-typed-linting.md new file mode 100644 index 000000000000..4037d6053ecb --- /dev/null +++ b/packages/website/blog/2024-09-30-typed-linting.md @@ -0,0 +1,258 @@ +--- +authors: joshuakgoldberg +description: Explaining what linting with type information means, why it's so powerful, and some of the useful rules you can enable that use it. +slug: typed-linting +tags: [types, type information, typed linting] +title: 'Typed Linting: The Most Powerful TypeScript Linting Ever' +--- + +[Linting with type information](https://typescript-eslint.io/getting-started/typed-linting), also called "typed linting" or "type-aware linting", is the act of writing lint rules that use type information to understand your code. +Typed linting rules as provided by typescript-eslint are the most powerful JavaScript/TypeScript linting in common use today. + +In this blog post, we'll give a high-level overview of how linting with type information works, why it's so much more powerful than traditional linting, and some of the useful rules you can enable that use it. + + + +## Recap: Type Information? + +Traditional JavaScript lint rules operate on one file at a time. +They look at a description of code in each file and report complaints if that file seems to contain bad practices. +That description is called an Abstract Syntax Tree, or AST. + +:::tip +For a primer on ASTs and linting, see _[ASTs and typescript-eslint](./2022-12-05-asts-and-typescript-eslint.md)_. +::: + +Each file's AST contains only information for that file, not any other files. +Lint rules that rely only on the file's AST don't have a way to understand code imported from other files, such as in ESM `import` statements. +Not being able to understand code from other files severely limits lint rules. + +As an example, suppose you enable a lint rule like [`@typescript-eslint/no-deprecated`](/rules/no-deprecated) to prevent calling to code with a `@deprecated` JSDoc. +Using just the following file's AST, the lint rule would have no way of knowing whether `work` is deprecated: + +```ts title="index.ts" +import { work } from './worker'; + +// Is this safe? Does calling work violate any rules? We don't know! +work(); +``` + +_Type information_ refers to the information a type checker such as TypeScript generates to understand your code. +Type checkers read code, determine what types each value may be, and store that "type information". +TypeScript and tools that call to TypeScript's APIs can then use that type information to understand the project's code. + +In the earlier example, type information would be able to inform a lint rule running in `index.ts` that the `work` import resolves to a function in another file: + +```ts title="worker.ts" +/** @deprecated - Don't do this! */ +export function work() { + // ... +} +``` + +...which would allow the lint rule to report a complaint that the `work()` call is to a function marked as `@deprecated`. + +typescript-eslint allows lint rules to retrieve type information using TypeScript's APIs. +In doing so, they can even make decisions on linted files using information outside each individual file. + +## Common Uses for Typed Linting + +Cross-file type information is a powerful addition to lint rules. +Knowing the types of pieces of your code allows lint rules to flag for risky behavior specific to certain types. +The following sections show several of the most common uses for lint rules that rely on type information. + +### Unsafe `any`s + +The `@typescript-eslint/no-unsafe-*` family of rules checks for risky uses of `any` typed values. +This is useful because the `any` type can easily slip into code and reduce type safety, despite being allowed by the TypeScript type checker. + +For example, the following code that logs a member of an object parsed from a string produces no type errors in type checking. +`JSON.parse()` returns `any`, and arbitrary property accesses are allowed on values of type `any`. + +However, [`@typescript-eslint/no-unsafe-member-access`](/rules/no-unsafe-member-access) would report `[key]` might not be a property on the object: + +```ts +function getDataKey(rawData: string, key: string): string { + return JSON.parse(rawData)[key]; + // ~~~~~ + // Unsafe member access [key] on an `any` value. + // eslint(@typescript-eslint/no-unsafe-member-access) +} +``` + +The lint rule is right to report. +Calls to the `getDataKey` function can return a value that's not a `string`, despite the function's explicit return type annotation. +That can lead to unexpected behavior at runtime: + +```ts +console.log(getDataKey(`{ "blue": "cheese" }`, 'bleu').toUpperCase()); +// Uncaught TypeError: Cannot read properties of undefined (reading 'toUpperCase') +``` + +Without type information to indicate the types of `JSON.parse` and `key`, there would have been no way to determine that the `[key]` member access was unsafe. + +### Method Call Scoping + + + +Runtime crashes caused by misuses of typed code are possible even with no `any`s. + +For example, class method functions don't preserve their class scope when passed as standalone variables ("unbound"). +TypeScript still allows them to be called without the proper `this` scope. + +The global `localStorage` object in browsers has several properties that must be called with a `this` bound to `localStorage`. +The [`@typescript-eslint/unbound-method`](/rules/unbound-method) lint rule can report on unsafe references to those properties, such as accessing `getItem`: + +```ts +const { getItem } = localStorage; +// ~~~~~~~ +// Avoid referencing unbound methods which may cause unintentional scoping of `this`. +// eslint(@typescript-eslint/unbound-method) +``` + +That's useful because calls to `getItem` that aren't bound to `localStorage` cause an exception at runtime: + +```ts +getItem('...'); +// Uncaught TypeError: Illegal invocation +``` + +Without type information to indicate the types of `localStorage` and its `getItem` property, there would have been no reliable way to determine that the `const { getItem }` access was unsafe. + +### Async Race Conditions + +Even if your code is 100% typed, has no `any`s, and doesn't misuse scopes, it's still possible to have bugs that can only easily be detected by typed linting. +Asynchronous code with `Promise`s in particular can introduce subtle issues that are completely type-safe. + +Suppose your code is meant to run an asynchronous `readFromCache` function before reading from the file system: + +```ts +import { fs } from 'node:fs/promises'; +import { readFromCache } from './caching'; + +const filePath = './data.json'; + +readFromCache(filePath); + +await fs.rm(filePath); +``` + +Do you see the potential bug? + +If `readFromCache` is asynchronous (returns a `Promise`), then calling it and not awaiting its returned Promise could lead to race conditions in code. +Its asynchronous or delayed logic might not get to reading from the `filePath` before `fs.rm(filePath)` runs. + +This is commonly referred to as a _"floating"_ Promise: one that is created but not appropriately handled. +The [`@typescript-eslint/no-floating-promises`](/rules/no-floating-promises) lint rule would report on that floating Promise: + +```ts +readFromCache(filePath); +// Promises must be awaited, end with a call to .catch, end with a call to .then +// with a rejection handler or be explicitly marked as ignored with the `void` operator. +// eslint(@typescript-eslint/no-floating-promises +``` + +...and can give an editor suggestion to add a missing `await`: + +```diff +- readFromCache(filePath); ++ await readFromCache(filePath); +``` + +Determining whether code is creating a floating Promise is only possible when the types of code are known. +Otherwise, lint rules would have no way of knowing which imports from other files could potentially create a Promise that needs to be handled. + +### Custom Rules + +Typed linting isn't restricted to just typescript-eslint rules. +It can be used in community ESLint plugins, as well as custom rules specific to your project. + +One common example used by teams is to codemod from a deprecated API to its replacement. +Typed linting is often necessary to determine which pieces of code call to the old API. + +As an example, consider the following `fetch()` POST call that sends data to an intake API. +Suppose the intake endpoint is migrating from sending `[string, string]` tuples to sending key-value pairs. +A typed lint rule could determine that the data is in the old format: + +```ts +import { endpoints } from "~/api"; + +const rawData = ["key", "value"] as const; + +await fetch(endpoints.intake, { + data: JSON.stringify(rawData) + // ~~~~~~~ + // Don't pass a tuple to endpoints.intake. Pass a key-value object instead. + // eslint(@my-team/custom-rule) + // ... + method: "POST", +}); +``` + +...and provide a code fix to automatically migrate to the new format: + +```diff +import { endpoints } from "~/api"; + +const rawData = ["key", "value"] as const; + +await fetch(endpoints.intake, { +- data: JSON.stringify(rawData) ++ data: JSON.stringify(Object.fromEntries(rawData)) + // ... + method: "POST", +}); +``` + +Knowing that the `fetch()` call was being sent to `endpoints.intake` and that the type of the data was a tuple takes typed linting. + +That kind of migration codemod is one of the ways typed linting can be utilized for project- or team-specific rules. +See [Developers > Custom Rules](/developers/custom-rules) for more documentation on building your own ESLint rules with typescript-eslint. + +## Enabling Typed Linting + +You can add typed linting to your ESLint configuration by following the steps in [Linting with Type Information](/getting-started/typed-linting). +We recommend doing so by enabling [`parserOptions.projectService`](/packages/parser#projectservice): + +```js title="eslint.config.js" +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + { + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, +); +``` + +typescript-eslint will then use TypeScript APIs behind-the-scenes, to, for each file being linted: + +1. Determine the appropriate TSConfig dictating how to generate that file's type information +2. Make APIs available to lint rules to retrieve type information for the file + +Lint rules that opt into type information will then be able to use those APIs when linting your code. + +## Drawbacks of Typed Linting + +Linting with type information comes with two drawbacks: configuration complexity and a performance penalty. + +For configuring typed linting, [`parserOptions.projectService`](/packages/parser#projectservice) solves configuration difficulties for most projects. +The more manual [`parserOptions.project`](/packages/parser#projectservice) is also available for more complex project setups. +See [Troubleshooting & FAQs > Typed Linting](/troubleshooting/typed-linting) for details on common issues. + +For performance, it is inevitable that typed linting will slow your linting down to roughly the speed of type checking your project. +Typed lint rules call to the same TypeScript APIs as the command-line `tsc`. +If linting your project is much slower than running `tsc` on the same set of files, see [Troubleshooting & FAQs > Typed Linting > Performance](/troubleshooting/typed-linting/performance). + +## Final Thoughts + +In our experience, the additional bug catching and features added by typed linting are well worth the costs of configuration and performance. +Typed linting allows lint rules to act with much greater confidence on a wider area of checking, including avoiding unsafe `any` uses, enforcing proper `this` scopes, and catching asynchronous code mishaps. + +If you haven't yet tried out typed linting using the typescript-eslint rules mentioned in this blog post, we'd strongly recommend going through our [Linting with Type Information](/getting-started/typed-linting) guide. From b121bd9557986799b6ccd5c4f9a6722c34329665 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Mon, 30 Sep 2024 10:29:36 -0600 Subject: [PATCH 17/18] feat(eslint-plugin): [return-await] check for-await loop iteree (#10008) * check for-await loop iteree * test fixup * better docs and stuff * tweak * change comment * await=true Co-authored-by: Joshua Chen * type and remove unnecessary condition * Use Josh Cena's writeup instead Co-authored-by: Joshua Chen * snapshot * cov --------- Co-authored-by: Joshua Chen --- .../docs/rules/await-thenable.mdx | 84 ++++++++++++++++- .../eslint-plugin/src/rules/await-thenable.ts | 48 +++++++++- .../await-thenable.shot | 60 +++++++++++++ .../tests/rules/await-thenable.test.ts | 90 +++++++++++++++++++ 4 files changed, 278 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/await-thenable.mdx b/packages/eslint-plugin/docs/rules/await-thenable.mdx index 3ad4c18de91d..f9905bfcf80a 100644 --- a/packages/eslint-plugin/docs/rules/await-thenable.mdx +++ b/packages/eslint-plugin/docs/rules/await-thenable.mdx @@ -10,9 +10,9 @@ import TabItem from '@theme/TabItem'; > See **https://typescript-eslint.io/rules/await-thenable** for documentation. A "Thenable" value is an object which has a `then` method, such as a Promise. -The `await` keyword is generally used to retrieve the result of calling a Thenable's `then` method. +The [`await` keyword](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) is generally used to retrieve the result of calling a Thenable's `then` method. -If the `await` keyword is used on a value that is not a Thenable, the value is directly resolved immediately. +If the `await` keyword is used on a value that is not a Thenable, the value is directly resolved, but will still pause execution until the next microtask. While doing so is valid JavaScript, it is often a programmer error, such as forgetting to add parenthesis to call a function that returns a Promise. ## Examples @@ -40,6 +40,86 @@ await createValue(); +## Async Iteration (`for await...of` Loops) + +This rule also inspects [`for await...of` statements](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of), and reports if the value being iterated over is not async-iterable. + +:::info[Why does the rule report on `for await...of` loops used on an array of Promises?] + +While `for await...of` can be used with synchronous iterables, and it will await each promise produced by the iterable, it is inadvisable to do so. +There are some tiny nuances that you may want to consider. + +The biggest difference between using `for await...of` and using `for...of` (plus awaiting each result yourself) is error handling. +When an error occurs within the loop body, `for await...of` does _not_ close the original sync iterable, while `for...of` does. +For detailed examples of this, see the [MDN documentation on using `for await...of` with sync-iterables](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of#iterating_over_sync_iterables_and_generators). + +Also consider whether you need sequential awaiting at all. Using `for await...of` may obscure potential opportunities for concurrent processing, such as those reported by [`no-await-in-loop`](https://eslint.org/docs/latest/rules/no-await-in-loop). Consider instead using one of the [promise concurrency methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#promise_concurrency) for better performance. + +::: + +### Examples + + + + +```ts +async function syncIterable() { + const arrayOfValues = [1, 2, 3]; + for await (const value of arrayOfValues) { + console.log(value); + } +} + +async function syncIterableOfPromises() { + const arrayOfPromises = [ + Promise.resolve(1), + Promise.resolve(2), + Promise.resolve(3), + ]; + for await (const promisedValue of arrayOfPromises) { + console.log(promisedValue); + } +} +``` + + + + +```ts +async function syncIterable() { + const arrayOfValues = [1, 2, 3]; + for (const value of arrayOfValues) { + console.log(value); + } +} + +async function syncIterableOfPromises() { + const arrayOfPromises = [ + Promise.resolve(1), + Promise.resolve(2), + Promise.resolve(3), + ]; + for (const promisedValue of await Promise.all(arrayOfPromises)) { + console.log(promisedValue); + } +} + +async function validUseOfForAwaitOnAsyncIterable() { + async function* yieldThingsAsynchronously() { + yield 1; + await new Promise(resolve => setTimeout(resolve, 1000)); + yield 2; + } + + for await (const promisedValue of yieldThingsAsynchronously()) { + console.log(promisedValue); + } +} +``` + + + + ## When Not To Use It If you want to allow code to `await` non-Promise values. diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts index 084ea2447e89..4c5a2e6dd171 100644 --- a/packages/eslint-plugin/src/rules/await-thenable.ts +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -1,4 +1,4 @@ -import type { TSESLint } from '@typescript-eslint/utils'; +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import { @@ -10,8 +10,15 @@ import { nullThrows, NullThrowsReasons, } from '../util'; +import { getForStatementHeadLoc } from '../util/getForStatementHeadLoc'; -export default createRule({ +type MessageId = + | 'await' + | 'forAwaitOfNonThenable' + | 'removeAwait' + | 'convertToOrdinaryFor'; + +export default createRule<[], MessageId>({ name: 'await-thenable', meta: { docs: { @@ -22,7 +29,10 @@ export default createRule({ hasSuggestions: true, messages: { await: 'Unexpected `await` of a non-Promise (non-"Thenable") value.', + forAwaitOfNonThenable: + 'Unexpected `for await...of` of a value that is not async iterable.', removeAwait: 'Remove unnecessary `await`.', + convertToOrdinaryFor: 'Convert to an ordinary `for...of` loop.', }, schema: [], type: 'problem', @@ -62,6 +72,40 @@ export default createRule({ }); } }, + + 'ForOfStatement[await=true]'(node: TSESTree.ForOfStatement): void { + const type = services.getTypeAtLocation(node.right); + if (isTypeAnyType(type)) { + return; + } + + const asyncIteratorSymbol = tsutils.getWellKnownSymbolPropertyOfType( + type, + 'asyncIterator', + checker, + ); + + if (asyncIteratorSymbol == null) { + context.report({ + loc: getForStatementHeadLoc(context.sourceCode, node), + messageId: 'forAwaitOfNonThenable', + suggest: [ + // Note that this suggestion causes broken code for sync iterables + // of promises, since the loop variable is not awaited. + { + messageId: 'convertToOrdinaryFor', + fix(fixer): TSESLint.RuleFix { + const awaitToken = nullThrows( + context.sourceCode.getFirstToken(node, isAwaitKeyword), + NullThrowsReasons.MissingToken('await', 'for await loop'), + ); + return fixer.remove(awaitToken); + }, + }, + ], + }); + } + }, }; }, }); diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/await-thenable.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/await-thenable.shot index 10fdfdb9d0e2..176f8e64da8e 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/await-thenable.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/await-thenable.shot @@ -21,3 +21,63 @@ const createValue = async () => 'value'; await createValue(); " `; + +exports[`Validating rule docs await-thenable.mdx code examples ESLint output 3`] = ` +"Incorrect + +async function syncIterable() { + const arrayOfValues = [1, 2, 3]; + for await (const value of arrayOfValues) { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Unexpected \`for await...of\` of a value that is not async iterable. + console.log(value); + } +} + +async function syncIterableOfPromises() { + const arrayOfPromises = [ + Promise.resolve(1), + Promise.resolve(2), + Promise.resolve(3), + ]; + for await (const promisedValue of arrayOfPromises) { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Unexpected \`for await...of\` of a value that is not async iterable. + console.log(promisedValue); + } +} +" +`; + +exports[`Validating rule docs await-thenable.mdx code examples ESLint output 4`] = ` +"Correct + +async function syncIterable() { + const arrayOfValues = [1, 2, 3]; + for (const value of arrayOfValues) { + console.log(value); + } +} + +async function syncIterableOfPromises() { + const arrayOfPromises = [ + Promise.resolve(1), + Promise.resolve(2), + Promise.resolve(3), + ]; + for (const promisedValue of await Promise.all(arrayOfPromises)) { + console.log(promisedValue); + } +} + +async function validUseOfForAwaitOnAsyncIterable() { + async function* yieldThingsAsynchronously() { + yield 1; + await new Promise(resolve => setTimeout(resolve, 1000)); + yield 2; + } + + for await (const promisedValue of yieldThingsAsynchronously()) { + console.log(promisedValue); + } +} +" +`; diff --git a/packages/eslint-plugin/tests/rules/await-thenable.test.ts b/packages/eslint-plugin/tests/rules/await-thenable.test.ts index ebb8c6ef9537..7d88bea824a0 100644 --- a/packages/eslint-plugin/tests/rules/await-thenable.test.ts +++ b/packages/eslint-plugin/tests/rules/await-thenable.test.ts @@ -198,6 +198,28 @@ const doSomething = async ( await callback?.(); }; `, + { + code: ` +async function* asyncYieldNumbers() { + yield 1; + yield 2; + yield 3; +} +for await (const value of asyncYieldNumbers()) { + console.log(value); +} + `, + }, + { + code: ` +declare const anee: any; +async function forAwait() { + for await (const value of anee) { + console.log(value); + } +} + `, + }, ], invalid: [ @@ -378,5 +400,73 @@ declare const obj: { a: { b: { c?: () => void } } } | undefined; }, ], }, + { + code: ` +function* yieldNumbers() { + yield 1; + yield 2; + yield 3; +} +for await (const value of yieldNumbers()) { + console.log(value); +} + `, + errors: [ + { + messageId: 'forAwaitOfNonThenable', + line: 7, + endLine: 7, + column: 1, + endColumn: 42, + suggestions: [ + { + messageId: 'convertToOrdinaryFor', + output: ` +function* yieldNumbers() { + yield 1; + yield 2; + yield 3; +} +for (const value of yieldNumbers()) { + console.log(value); +} + `, + }, + ], + }, + ], + }, + { + code: ` +function* yieldNumberPromises() { + yield Promise.resolve(1); + yield Promise.resolve(2); + yield Promise.resolve(3); +} +for await (const value of yieldNumberPromises()) { + console.log(value); +} + `, + errors: [ + { + messageId: 'forAwaitOfNonThenable', + suggestions: [ + { + messageId: 'convertToOrdinaryFor', + output: ` +function* yieldNumberPromises() { + yield Promise.resolve(1); + yield Promise.resolve(2); + yield Promise.resolve(3); +} +for (const value of yieldNumberPromises()) { + console.log(value); +} + `, + }, + ], + }, + ], + }, ], }); From 2055cfbbdef5d9b7ee4ed7180f0af93eed245235 Mon Sep 17 00:00:00 2001 From: "typescript-eslint[bot]" Date: Mon, 30 Sep 2024 17:17:35 +0000 Subject: [PATCH 18/18] chore(release): publish 8.8.0 --- CHANGELOG.md | 29 +++++++ packages/ast-spec/CHANGELOG.md | 19 +++++ packages/ast-spec/package.json | 2 +- packages/eslint-plugin/CHANGELOG.md | 36 +++++++++ packages/eslint-plugin/package.json | 14 ++-- packages/parser/CHANGELOG.md | 6 ++ packages/parser/package.json | 10 +-- .../CHANGELOG.md | 6 ++ .../package.json | 6 +- packages/rule-tester/CHANGELOG.md | 6 ++ packages/rule-tester/package.json | 8 +- packages/scope-manager/CHANGELOG.md | 19 +++++ packages/scope-manager/package.json | 8 +- packages/type-utils/CHANGELOG.md | 24 ++++++ packages/type-utils/package.json | 8 +- packages/types/CHANGELOG.md | 19 +++++ packages/types/package.json | 2 +- packages/typescript-eslint/CHANGELOG.md | 6 ++ packages/typescript-eslint/package.json | 8 +- packages/typescript-estree/CHANGELOG.md | 24 ++++++ packages/typescript-estree/package.json | 6 +- packages/utils/CHANGELOG.md | 21 +++++ packages/utils/package.json | 8 +- packages/visitor-keys/CHANGELOG.md | 6 ++ packages/visitor-keys/package.json | 4 +- yarn.lock | 80 +++++++++---------- 26 files changed, 303 insertions(+), 82 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13b1ffdfcf6f..eb1f8bbcd970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +## 8.8.0 (2024-09-30) + + +### 🚀 Features + +- **eslint-plugin:** [no-unnecessary-condition] add checkTypePredicates ([#10009](https://github.com/typescript-eslint/typescript-eslint/pull/10009)) +- **eslint-plugin:** [return-await] check for-await loop iteree ([#10008](https://github.com/typescript-eslint/typescript-eslint/pull/10008)) + +### 🩹 Fixes + +- remove `export type *` in d.ts to support TS<5.0 ([#10070](https://github.com/typescript-eslint/typescript-eslint/pull/10070)) +- **eslint-plugin:** [no-unnecessary-template-expression] should underline template syntax with squiggly lines ([#10044](https://github.com/typescript-eslint/typescript-eslint/pull/10044)) +- **eslint-plugin:** [no-deprecated] max callstack exceeded when class implements itself ([#10040](https://github.com/typescript-eslint/typescript-eslint/pull/10040)) +- **eslint-plugin:** [no-misused-promises] check contextual type ([#10042](https://github.com/typescript-eslint/typescript-eslint/pull/10042)) +- **eslint-plugin:** [prefer-literal-enum-member] allow nested bitwise operations ([#10037](https://github.com/typescript-eslint/typescript-eslint/pull/10037)) +- **type-utils:** check for type parameters on `isBuiltinSymbolLikeRecurser()` ([#10026](https://github.com/typescript-eslint/typescript-eslint/pull/10026)) +- **utils:** update missing type information message ([#10043](https://github.com/typescript-eslint/typescript-eslint/pull/10043)) + +### ❤️ Thank You + +- auvred @auvred +- Josh Goldberg ✨ +- Kirk Waiblinger @kirkwaiblinger +- Ronen Amiel +- Tarun Chauhan @tarunrajput +- YeonJuan @yeonjuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.7.0 (2024-09-23) diff --git a/packages/ast-spec/CHANGELOG.md b/packages/ast-spec/CHANGELOG.md index a913f9901fd2..b2499bc39906 100644 --- a/packages/ast-spec/CHANGELOG.md +++ b/packages/ast-spec/CHANGELOG.md @@ -1,3 +1,22 @@ +## 8.8.0 (2024-09-30) + + +### 🩹 Fixes + +- remove `export type *` in d.ts to support TS<5.0 + + +### ❤️ Thank You + +- auvred +- Josh Goldberg ✨ +- Kirk Waiblinger +- Ronen Amiel +- Tarun Chauhan +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.7.0 (2024-09-23) diff --git a/packages/ast-spec/package.json b/packages/ast-spec/package.json index 12778f959474..a367966611f1 100644 --- a/packages/ast-spec/package.json +++ b/packages/ast-spec/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/ast-spec", - "version": "8.7.0", + "version": "8.8.0", "description": "Complete specification for the TypeScript-ESTree AST", "private": true, "keywords": [ diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 2cf93b418bb9..3bfb791aeb0e 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,3 +1,39 @@ +## 8.8.0 (2024-09-30) + + +### 🚀 Features + +- **eslint-plugin:** [no-unnecessary-condition] add checkTypePredicates + +- **eslint-plugin:** [return-await] check for-await loop iteree + + +### 🩹 Fixes + +- remove `export type *` in d.ts to support TS<5.0 + +- **eslint-plugin:** [no-unnecessary-template-expression] should underline template syntax with squiggly lines + +- **type-utils:** check for type parameters on `isBuiltinSymbolLikeRecurser()` + +- **eslint-plugin:** [no-deprecated] max callstack exceeded when class implements itself + +- **eslint-plugin:** [no-misused-promises] check contextual type + +- **eslint-plugin:** [prefer-literal-enum-member] allow nested bitwise operations + + +### ❤️ Thank You + +- auvred +- Josh Goldberg ✨ +- Kirk Waiblinger +- Ronen Amiel +- Tarun Chauhan +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.7.0 (2024-09-23) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 8ff9322985a9..1ad47457ae10 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "8.7.0", + "version": "8.8.0", "description": "TypeScript plugin for ESLint", "files": [ "dist", @@ -60,10 +60,10 @@ }, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.7.0", - "@typescript-eslint/type-utils": "8.7.0", - "@typescript-eslint/utils": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0", + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/type-utils": "8.8.0", + "@typescript-eslint/utils": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -74,8 +74,8 @@ "@types/marked": "^5.0.2", "@types/mdast": "^4.0.3", "@types/natural-compare": "*", - "@typescript-eslint/rule-schema-to-typescript-types": "8.7.0", - "@typescript-eslint/rule-tester": "8.7.0", + "@typescript-eslint/rule-schema-to-typescript-types": "8.8.0", + "@typescript-eslint/rule-tester": "8.8.0", "ajv": "^6.12.6", "cross-env": "^7.0.3", "cross-fetch": "*", diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 7fc1c81d900a..03fe069e48c3 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.8.0 (2024-09-30) + +This was a version bump only for parser to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.7.0 (2024-09-23) This was a version bump only for parser to align it with other projects, there were no code changes. diff --git a/packages/parser/package.json b/packages/parser/package.json index 424e46f1a7fa..7710ef600557 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "8.7.0", + "version": "8.8.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "files": [ "dist", @@ -52,10 +52,10 @@ "eslint": "^8.57.0 || ^9.0.0" }, "dependencies": { - "@typescript-eslint/scope-manager": "8.7.0", - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/typescript-estree": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0", + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", "debug": "^4.3.4" }, "devDependencies": { diff --git a/packages/rule-schema-to-typescript-types/CHANGELOG.md b/packages/rule-schema-to-typescript-types/CHANGELOG.md index c18ca1c9fb38..e3db3a2cee59 100644 --- a/packages/rule-schema-to-typescript-types/CHANGELOG.md +++ b/packages/rule-schema-to-typescript-types/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.8.0 (2024-09-30) + +This was a version bump only for rule-schema-to-typescript-types to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.7.0 (2024-09-23) This was a version bump only for rule-schema-to-typescript-types to align it with other projects, there were no code changes. diff --git a/packages/rule-schema-to-typescript-types/package.json b/packages/rule-schema-to-typescript-types/package.json index 6628061692a8..29a5572487d8 100644 --- a/packages/rule-schema-to-typescript-types/package.json +++ b/packages/rule-schema-to-typescript-types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/rule-schema-to-typescript-types", - "version": "8.7.0", + "version": "8.8.0", "private": true, "type": "commonjs", "exports": { @@ -34,8 +34,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/type-utils": "8.7.0", - "@typescript-eslint/utils": "8.7.0", + "@typescript-eslint/type-utils": "8.8.0", + "@typescript-eslint/utils": "8.8.0", "natural-compare": "^1.4.0", "prettier": "^3.2.5" }, diff --git a/packages/rule-tester/CHANGELOG.md b/packages/rule-tester/CHANGELOG.md index 3d1361dd36b0..c2d5f511d7c5 100644 --- a/packages/rule-tester/CHANGELOG.md +++ b/packages/rule-tester/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.8.0 (2024-09-30) + +This was a version bump only for rule-tester to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.7.0 (2024-09-23) This was a version bump only for rule-tester to align it with other projects, there were no code changes. diff --git a/packages/rule-tester/package.json b/packages/rule-tester/package.json index cb25f8b828bb..420f93c26d45 100644 --- a/packages/rule-tester/package.json +++ b/packages/rule-tester/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/rule-tester", - "version": "8.7.0", + "version": "8.8.0", "description": "Tooling to test ESLint rules", "files": [ "dist", @@ -48,8 +48,8 @@ }, "//": "NOTE - AJV is out-of-date, but it's intentionally synced with ESLint - https://github.com/eslint/eslint/blob/ad9dd6a933fd098a0d99c6a9aa059850535c23ee/package.json#L70", "dependencies": { - "@typescript-eslint/typescript-estree": "8.7.0", - "@typescript-eslint/utils": "8.7.0", + "@typescript-eslint/typescript-estree": "8.8.0", + "@typescript-eslint/utils": "8.8.0", "ajv": "^6.12.6", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "4.6.2", @@ -62,7 +62,7 @@ "@jest/types": "29.6.3", "@types/json-stable-stringify-without-jsonify": "^1.0.2", "@types/lodash.merge": "4.6.9", - "@typescript-eslint/parser": "8.7.0", + "@typescript-eslint/parser": "8.8.0", "chai": "^4.4.1", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.1", diff --git a/packages/scope-manager/CHANGELOG.md b/packages/scope-manager/CHANGELOG.md index 4c23dce5b0b1..1e7758d5793f 100644 --- a/packages/scope-manager/CHANGELOG.md +++ b/packages/scope-manager/CHANGELOG.md @@ -1,3 +1,22 @@ +## 8.8.0 (2024-09-30) + + +### 🩹 Fixes + +- remove `export type *` in d.ts to support TS<5.0 + + +### ❤️ Thank You + +- auvred +- Josh Goldberg ✨ +- Kirk Waiblinger +- Ronen Amiel +- Tarun Chauhan +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.7.0 (2024-09-23) diff --git a/packages/scope-manager/package.json b/packages/scope-manager/package.json index 59c358d9b578..54d1245c001a 100644 --- a/packages/scope-manager/package.json +++ b/packages/scope-manager/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/scope-manager", - "version": "8.7.0", + "version": "8.8.0", "description": "TypeScript scope analyser for ESLint", "files": [ "dist", @@ -46,13 +46,13 @@ "typecheck": "npx nx typecheck" }, "dependencies": { - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0" + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0" }, "devDependencies": { "@jest/types": "29.6.3", "@types/glob": "*", - "@typescript-eslint/typescript-estree": "8.7.0", + "@typescript-eslint/typescript-estree": "8.8.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/type-utils/CHANGELOG.md b/packages/type-utils/CHANGELOG.md index 0337fc6d6708..d1884f85e863 100644 --- a/packages/type-utils/CHANGELOG.md +++ b/packages/type-utils/CHANGELOG.md @@ -1,3 +1,27 @@ +## 8.8.0 (2024-09-30) + + +### 🚀 Features + +- **eslint-plugin:** [no-unnecessary-condition] add checkTypePredicates + + +### 🩹 Fixes + +- **type-utils:** check for type parameters on `isBuiltinSymbolLikeRecurser()` + + +### ❤️ Thank You + +- auvred +- Josh Goldberg ✨ +- Kirk Waiblinger +- Ronen Amiel +- Tarun Chauhan +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.7.0 (2024-09-23) This was a version bump only for type-utils to align it with other projects, there were no code changes. diff --git a/packages/type-utils/package.json b/packages/type-utils/package.json index df2f0ec420e9..33b05de04122 100644 --- a/packages/type-utils/package.json +++ b/packages/type-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/type-utils", - "version": "8.7.0", + "version": "8.8.0", "description": "Type utilities for working with TypeScript + ESLint together", "files": [ "dist", @@ -46,14 +46,14 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/typescript-estree": "8.7.0", - "@typescript-eslint/utils": "8.7.0", + "@typescript-eslint/typescript-estree": "8.8.0", + "@typescript-eslint/utils": "8.8.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "devDependencies": { "@jest/types": "29.6.3", - "@typescript-eslint/parser": "8.7.0", + "@typescript-eslint/parser": "8.8.0", "ajv": "^6.12.6", "downlevel-dts": "*", "jest": "29.7.0", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 2d011ea3672e..8373dbb83084 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,3 +1,22 @@ +## 8.8.0 (2024-09-30) + + +### 🩹 Fixes + +- remove `export type *` in d.ts to support TS<5.0 + + +### ❤️ Thank You + +- auvred +- Josh Goldberg ✨ +- Kirk Waiblinger +- Ronen Amiel +- Tarun Chauhan +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.7.0 (2024-09-23) diff --git a/packages/types/package.json b/packages/types/package.json index 8f84805ea191..baf3e4bc262e 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/types", - "version": "8.7.0", + "version": "8.8.0", "description": "Types for the TypeScript-ESTree AST spec", "files": [ "dist", diff --git a/packages/typescript-eslint/CHANGELOG.md b/packages/typescript-eslint/CHANGELOG.md index b317550bca1f..c68cb2dcf15c 100644 --- a/packages/typescript-eslint/CHANGELOG.md +++ b/packages/typescript-eslint/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.8.0 (2024-09-30) + +This was a version bump only for typescript-eslint to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.7.0 (2024-09-23) This was a version bump only for typescript-eslint to align it with other projects, there were no code changes. diff --git a/packages/typescript-eslint/package.json b/packages/typescript-eslint/package.json index 61f52f24e2da..6348bb03a939 100644 --- a/packages/typescript-eslint/package.json +++ b/packages/typescript-eslint/package.json @@ -1,6 +1,6 @@ { "name": "typescript-eslint", - "version": "8.7.0", + "version": "8.8.0", "description": "Tooling which enables you to use TypeScript with ESLint", "files": [ "dist", @@ -52,9 +52,9 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.7.0", - "@typescript-eslint/parser": "8.7.0", - "@typescript-eslint/utils": "8.7.0" + "@typescript-eslint/eslint-plugin": "8.8.0", + "@typescript-eslint/parser": "8.8.0", + "@typescript-eslint/utils": "8.8.0" }, "devDependencies": { "@jest/types": "29.6.3", diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 3f35f359ce53..d939a122780b 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -1,3 +1,27 @@ +## 8.8.0 (2024-09-30) + + +### 🚀 Features + +- **eslint-plugin:** [no-unnecessary-condition] add checkTypePredicates + + +### 🩹 Fixes + +- remove `export type *` in d.ts to support TS<5.0 + + +### ❤️ Thank You + +- auvred +- Josh Goldberg ✨ +- Kirk Waiblinger +- Ronen Amiel +- Tarun Chauhan +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.7.0 (2024-09-23) diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 325a74003e9a..a022b5b16a2f 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "8.7.0", + "version": "8.8.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "files": [ "dist", @@ -54,8 +54,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index ba88fd48c4eb..a26a87b066a6 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -1,3 +1,24 @@ +## 8.8.0 (2024-09-30) + + +### 🩹 Fixes + +- remove `export type *` in d.ts to support TS<5.0 + +- **utils:** update missing type information message + + +### ❤️ Thank You + +- auvred +- Josh Goldberg ✨ +- Kirk Waiblinger +- Ronen Amiel +- Tarun Chauhan +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.7.0 (2024-09-23) diff --git a/packages/utils/package.json b/packages/utils/package.json index d801d5323121..063847b91ba3 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/utils", - "version": "8.7.0", + "version": "8.8.0", "description": "Utilities for working with TypeScript + ESLint together", "files": [ "dist", @@ -64,9 +64,9 @@ }, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.7.0", - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/typescript-estree": "8.7.0" + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" diff --git a/packages/visitor-keys/CHANGELOG.md b/packages/visitor-keys/CHANGELOG.md index 4b258c7919b7..c8487863d438 100644 --- a/packages/visitor-keys/CHANGELOG.md +++ b/packages/visitor-keys/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.8.0 (2024-09-30) + +This was a version bump only for visitor-keys to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.7.0 (2024-09-23) This was a version bump only for visitor-keys to align it with other projects, there were no code changes. diff --git a/packages/visitor-keys/package.json b/packages/visitor-keys/package.json index e4b6d44687d1..c64e651b9071 100644 --- a/packages/visitor-keys/package.json +++ b/packages/visitor-keys/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/visitor-keys", - "version": "8.7.0", + "version": "8.8.0", "description": "Visitor keys used to help traverse the TypeScript-ESTree AST", "files": [ "dist", @@ -47,7 +47,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/types": "8.7.0", + "@typescript-eslint/types": "8.8.0", "eslint-visitor-keys": "^3.4.3" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 985188ee7fdf..dd1b6fc68f8b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5638,7 +5638,7 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/eslint-plugin@8.7.0, @typescript-eslint/eslint-plugin@workspace:*, @typescript-eslint/eslint-plugin@workspace:^, @typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin": +"@typescript-eslint/eslint-plugin@8.8.0, @typescript-eslint/eslint-plugin@workspace:*, @typescript-eslint/eslint-plugin@workspace:^, @typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin": version: 0.0.0-use.local resolution: "@typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin" dependencies: @@ -5647,12 +5647,12 @@ __metadata: "@types/marked": ^5.0.2 "@types/mdast": ^4.0.3 "@types/natural-compare": "*" - "@typescript-eslint/rule-schema-to-typescript-types": 8.7.0 - "@typescript-eslint/rule-tester": 8.7.0 - "@typescript-eslint/scope-manager": 8.7.0 - "@typescript-eslint/type-utils": 8.7.0 - "@typescript-eslint/utils": 8.7.0 - "@typescript-eslint/visitor-keys": 8.7.0 + "@typescript-eslint/rule-schema-to-typescript-types": 8.8.0 + "@typescript-eslint/rule-tester": 8.8.0 + "@typescript-eslint/scope-manager": 8.8.0 + "@typescript-eslint/type-utils": 8.8.0 + "@typescript-eslint/utils": 8.8.0 + "@typescript-eslint/visitor-keys": 8.8.0 ajv: ^6.12.6 cross-env: ^7.0.3 cross-fetch: "*" @@ -5696,16 +5696,16 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/parser@8.7.0, @typescript-eslint/parser@workspace:*, @typescript-eslint/parser@workspace:packages/parser": +"@typescript-eslint/parser@8.8.0, @typescript-eslint/parser@workspace:*, @typescript-eslint/parser@workspace:packages/parser": version: 0.0.0-use.local resolution: "@typescript-eslint/parser@workspace:packages/parser" dependencies: "@jest/types": 29.6.3 "@types/glob": "*" - "@typescript-eslint/scope-manager": 8.7.0 - "@typescript-eslint/types": 8.7.0 - "@typescript-eslint/typescript-estree": 8.7.0 - "@typescript-eslint/visitor-keys": 8.7.0 + "@typescript-eslint/scope-manager": 8.8.0 + "@typescript-eslint/types": 8.8.0 + "@typescript-eslint/typescript-estree": 8.8.0 + "@typescript-eslint/visitor-keys": 8.8.0 debug: ^4.3.4 downlevel-dts: "*" glob: "*" @@ -5721,28 +5721,28 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/rule-schema-to-typescript-types@8.7.0, @typescript-eslint/rule-schema-to-typescript-types@workspace:*, @typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types": +"@typescript-eslint/rule-schema-to-typescript-types@8.8.0, @typescript-eslint/rule-schema-to-typescript-types@workspace:*, @typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types": version: 0.0.0-use.local resolution: "@typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/type-utils": 8.7.0 - "@typescript-eslint/utils": 8.7.0 + "@typescript-eslint/type-utils": 8.8.0 + "@typescript-eslint/utils": 8.8.0 natural-compare: ^1.4.0 prettier: ^3.2.5 languageName: unknown linkType: soft -"@typescript-eslint/rule-tester@8.7.0, @typescript-eslint/rule-tester@workspace:*, @typescript-eslint/rule-tester@workspace:packages/rule-tester": +"@typescript-eslint/rule-tester@8.8.0, @typescript-eslint/rule-tester@workspace:*, @typescript-eslint/rule-tester@workspace:packages/rule-tester": version: 0.0.0-use.local resolution: "@typescript-eslint/rule-tester@workspace:packages/rule-tester" dependencies: "@jest/types": 29.6.3 "@types/json-stable-stringify-without-jsonify": ^1.0.2 "@types/lodash.merge": 4.6.9 - "@typescript-eslint/parser": 8.7.0 - "@typescript-eslint/typescript-estree": 8.7.0 - "@typescript-eslint/utils": 8.7.0 + "@typescript-eslint/parser": 8.8.0 + "@typescript-eslint/typescript-estree": 8.8.0 + "@typescript-eslint/utils": 8.8.0 ajv: ^6.12.6 chai: ^4.4.1 eslint-visitor-keys: ^4.0.0 @@ -5760,15 +5760,15 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/scope-manager@8.7.0, @typescript-eslint/scope-manager@workspace:*, @typescript-eslint/scope-manager@workspace:^, @typescript-eslint/scope-manager@workspace:packages/scope-manager": +"@typescript-eslint/scope-manager@8.8.0, @typescript-eslint/scope-manager@workspace:*, @typescript-eslint/scope-manager@workspace:^, @typescript-eslint/scope-manager@workspace:packages/scope-manager": version: 0.0.0-use.local resolution: "@typescript-eslint/scope-manager@workspace:packages/scope-manager" dependencies: "@jest/types": 29.6.3 "@types/glob": "*" - "@typescript-eslint/types": 8.7.0 - "@typescript-eslint/typescript-estree": 8.7.0 - "@typescript-eslint/visitor-keys": 8.7.0 + "@typescript-eslint/types": 8.8.0 + "@typescript-eslint/typescript-estree": 8.8.0 + "@typescript-eslint/visitor-keys": 8.8.0 glob: "*" jest-specific-snapshot: "*" make-dir: "*" @@ -5787,14 +5787,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/type-utils@8.7.0, @typescript-eslint/type-utils@workspace:*, @typescript-eslint/type-utils@workspace:packages/type-utils": +"@typescript-eslint/type-utils@8.8.0, @typescript-eslint/type-utils@workspace:*, @typescript-eslint/type-utils@workspace:packages/type-utils": version: 0.0.0-use.local resolution: "@typescript-eslint/type-utils@workspace:packages/type-utils" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/parser": 8.7.0 - "@typescript-eslint/typescript-estree": 8.7.0 - "@typescript-eslint/utils": 8.7.0 + "@typescript-eslint/parser": 8.8.0 + "@typescript-eslint/typescript-estree": 8.8.0 + "@typescript-eslint/utils": 8.8.0 ajv: ^6.12.6 debug: ^4.3.4 downlevel-dts: "*" @@ -5809,7 +5809,7 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/types@8.7.0, @typescript-eslint/types@^8.3.0, @typescript-eslint/types@workspace:*, @typescript-eslint/types@workspace:^, @typescript-eslint/types@workspace:packages/types": +"@typescript-eslint/types@8.8.0, @typescript-eslint/types@^8.3.0, @typescript-eslint/types@workspace:*, @typescript-eslint/types@workspace:^, @typescript-eslint/types@workspace:packages/types": version: 0.0.0-use.local resolution: "@typescript-eslint/types@workspace:packages/types" dependencies: @@ -5910,13 +5910,13 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/typescript-estree@8.7.0, @typescript-eslint/typescript-estree@workspace:*, @typescript-eslint/typescript-estree@workspace:^, @typescript-eslint/typescript-estree@workspace:packages/typescript-estree": +"@typescript-eslint/typescript-estree@8.8.0, @typescript-eslint/typescript-estree@workspace:*, @typescript-eslint/typescript-estree@workspace:^, @typescript-eslint/typescript-estree@workspace:packages/typescript-estree": version: 0.0.0-use.local resolution: "@typescript-eslint/typescript-estree@workspace:packages/typescript-estree" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/types": 8.7.0 - "@typescript-eslint/visitor-keys": 8.7.0 + "@typescript-eslint/types": 8.8.0 + "@typescript-eslint/visitor-keys": 8.8.0 debug: ^4.3.4 fast-glob: ^3.3.2 glob: "*" @@ -5953,14 +5953,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@8.7.0, @typescript-eslint/utils@^8.3.0, @typescript-eslint/utils@workspace:*, @typescript-eslint/utils@workspace:^, @typescript-eslint/utils@workspace:packages/utils": +"@typescript-eslint/utils@8.8.0, @typescript-eslint/utils@^8.3.0, @typescript-eslint/utils@workspace:*, @typescript-eslint/utils@workspace:^, @typescript-eslint/utils@workspace:packages/utils": version: 0.0.0-use.local resolution: "@typescript-eslint/utils@workspace:packages/utils" dependencies: "@eslint-community/eslint-utils": ^4.4.0 - "@typescript-eslint/scope-manager": 8.7.0 - "@typescript-eslint/types": 8.7.0 - "@typescript-eslint/typescript-estree": 8.7.0 + "@typescript-eslint/scope-manager": 8.8.0 + "@typescript-eslint/types": 8.8.0 + "@typescript-eslint/typescript-estree": 8.8.0 downlevel-dts: "*" jest: 29.7.0 prettier: ^3.2.5 @@ -5989,13 +5989,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@8.7.0, @typescript-eslint/visitor-keys@workspace:*, @typescript-eslint/visitor-keys@workspace:packages/visitor-keys": +"@typescript-eslint/visitor-keys@8.8.0, @typescript-eslint/visitor-keys@workspace:*, @typescript-eslint/visitor-keys@workspace:packages/visitor-keys": version: 0.0.0-use.local resolution: "@typescript-eslint/visitor-keys@workspace:packages/visitor-keys" dependencies: "@jest/types": 29.6.3 "@types/eslint-visitor-keys": "*" - "@typescript-eslint/types": 8.7.0 + "@typescript-eslint/types": 8.8.0 downlevel-dts: "*" eslint-visitor-keys: ^3.4.3 jest: 29.7.0 @@ -19543,9 +19543,9 @@ __metadata: resolution: "typescript-eslint@workspace:packages/typescript-eslint" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/eslint-plugin": 8.7.0 - "@typescript-eslint/parser": 8.7.0 - "@typescript-eslint/utils": 8.7.0 + "@typescript-eslint/eslint-plugin": 8.8.0 + "@typescript-eslint/parser": 8.8.0 + "@typescript-eslint/utils": 8.8.0 downlevel-dts: "*" jest: 29.7.0 prettier: ^3.2.5 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy