From fca72762772833d1d696d9860c535c294ea24245 Mon Sep 17 00:00:00 2001 From: "typescript-eslint[bot]" <53356952+typescript-eslint[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 20:05:21 -0500 Subject: [PATCH 01/23] chore: update sponsors (#8112) Co-authored-by: typescript-eslint[bot] --- packages/website/data/sponsors.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/website/data/sponsors.json b/packages/website/data/sponsors.json index 2b2604e9524d..03aab330eae1 100644 --- a/packages/website/data/sponsors.json +++ b/packages/website/data/sponsors.json @@ -125,6 +125,13 @@ "totalDonations": 50000, "website": "https://skunk.team" }, + { + "id": "JetBrains", + "image": "https://images.opencollective.com/jetbrains/eb04ddc/logo.png", + "name": "JetBrains", + "totalDonations": 50000, + "website": "https://www.jetbrains.com/" + }, { "id": "Joe Alden", "image": "https://images.opencollective.com/joealden/44a6738/avatar.png", From eff7da182714244bb5ba2d46c6428729f791ff8f Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 23 Dec 2023 06:34:46 -0500 Subject: [PATCH 02/23] docs: fix example for no-shadow (#8080) --- .../eslint-plugin/docs/rules/no-shadow.md | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-shadow.md b/packages/eslint-plugin/docs/rules/no-shadow.md index 5a9b52a818e1..d4eb49fa84ad 100644 --- a/packages/eslint-plugin/docs/rules/no-shadow.md +++ b/packages/eslint-plugin/docs/rules/no-shadow.md @@ -28,25 +28,31 @@ const defaultOptions: Options = { ### `ignoreTypeValueShadow` -When set to `true`, the rule will ignore the case when you name a type the same as a variable. - -TypeScript allows types and variables to shadow one-another. This is generally safe because you cannot use variables in type locations without a `typeof` operator, so there's little risk of confusion. +When set to `true`, the rule will ignore the case when you name a type the same as a variable. This is generally safe because you cannot use variables in type locations without a `typeof` operator, so there's little risk of confusion. Examples of **correct** code with `{ ignoreTypeValueShadow: true }`: ```ts option='{ "ignoreTypeValueShadow": true }' showPlaygroundButton type Foo = number; -const Foo = 1; - interface Bar { prop: number; } -const Bar = 'test'; + +function f() { + const Foo = 1; + const Bar = 'test'; +} ``` +:::note + +_Shadowing_ specifically refers to two identical identifiers that are in different, nested scopes. This is different from _redeclaration_, which is when two identical identifiers are in the same scope. Redeclaration is covered by the [`no-redeclare`](./no-redeclare.md) rule instead. + +::: + ### `ignoreFunctionTypeParameterNameValueShadow` -When set to `true`, the rule will ignore the case when you name a function type argument the same as a variable. +When set to `true`, the rule will ignore the case when you name a parameter in a function type the same as a variable. Each of a function type's arguments creates a value variable within the scope of the function type. This is done so that you can reference the type later using the `typeof` operator: From 79fbbdf447345155b082cdc74a02a8eb5ed7316f Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 23 Dec 2023 06:35:41 -0500 Subject: [PATCH 03/23] docs: add FAQ about incorrect types (#8081) --- docs/linting/Troubleshooting.mdx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/linting/Troubleshooting.mdx b/docs/linting/Troubleshooting.mdx index ec964d14e843..526a79e03c63 100644 --- a/docs/linting/Troubleshooting.mdx +++ b/docs/linting/Troubleshooting.mdx @@ -153,6 +153,27 @@ module.exports = { }; ``` +## typescript-eslint thinks my variable is never nullish / is `any` / etc., but that is clearly not the case to me + +Our type-aware rules almost always trust the type information provided by the TypeScript compiler. Therefore, an easy way to check if our rule is behaving correctly is to inspect the type of the variable in question, such as by hovering over it in your IDE. + +If the IDE also shows that the type is never nullish / is `any`, you need to fix the type. A very common case is with the [`no-unnecessary-condition`](/rules/no-unnecessary-condition) rule. Take this code for example: + +```ts +let condition = false; + +const f = () => (condition = true); +f(); + +if (condition) { + //^^^^^^^^^ Unnecessary conditional, value is always falsy. +} +``` + +You can see that the type of `condition` is actually the literal type `false` by hovering over it in your IDE. In this case, typescript-eslint cannot possible know better than TypeScript itself, so you need to fix the report by fixing the type, such as through an assertion (`let condition = false as boolean`). + +If the IDE provides different type information from typescript-eslint's report, then make sure that the TypeScript setup used for your IDE, typescript-eslint, and `tsc` are the same: the same TypeScript version, the same type-checking compiler options, and the same files being included in the project. For example, if a type is declared in another file but that file is not included, the type will become `any`, and cause our `no-unsafe-*` rules to report. + ## I use a framework (like Vue) that requires custom file extensions, and I get errors like "You should add `parserOptions.extraFileExtensions` to your config" You can use `parserOptions.extraFileExtensions` to specify an array of non-TypeScript extensions to allow, for example: From 24a48b2009a4934e28b75aaceed6684f99e1bb14 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 23 Dec 2023 06:35:56 -0500 Subject: [PATCH 04/23] chore: add MDX files to precommit hook (#8083) --- .lintstagedrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.lintstagedrc b/.lintstagedrc index c4eebcd2f404..62ea24d90961 100644 --- a/.lintstagedrc +++ b/.lintstagedrc @@ -1,3 +1,3 @@ { - "*.{ts,mts,cts,tsx,js,mjs,cjs,jsx,json,md,css}": ["prettier --write"] + "*.{ts,mts,cts,tsx,js,mjs,cjs,jsx,json,md,mdx,css}": ["prettier --write"] } From 26ba8eaecfcf67b2837f1401404711094f46ea3a Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 23 Dec 2023 06:37:03 -0500 Subject: [PATCH 05/23] docs: add more rationale for no-for-in-array (#8082) --- packages/eslint-plugin/docs/rules/no-for-in-array.md | 12 ++++++++---- packages/eslint-plugin/src/rules/no-for-in-array.ts | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-for-in-array.md b/packages/eslint-plugin/docs/rules/no-for-in-array.md index 2777c32a1e5d..4fb481cf469c 100644 --- a/packages/eslint-plugin/docs/rules/no-for-in-array.md +++ b/packages/eslint-plugin/docs/rules/no-for-in-array.md @@ -6,10 +6,14 @@ description: 'Disallow iterating over an array with a for-in loop.' > > See **https://typescript-eslint.io/rules/no-for-in-array** for documentation. -A for-in loop (`for (var i in o)`) iterates over the properties of an Object. -While it is legal to use for-in loops with array types, it is not common. -for-in will iterate over the indices of the array as strings, omitting any "holes" in -the array. +A for-in loop (`for (const i in o)`) iterates over the properties of an Object. +While it is legal to use for-in loops with array values, it is not common. There are several potential bugs with this: + +1. It iterates over all enumerable properties, including non-index ones and the entire prototype chain. For example, [`RegExp.prototype.exec`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec) returns an array with additional properties, and `for-in` will iterate over them. Some libraries or even your own code may add additional methods to `Array.prototype` (either as polyfill or as custom methods), and if not done properly, they may be iterated over as well. +2. It skips holes in the array. While sparse arrays are rare and advised against, they are still possible and your code should be able to handle them. +3. The "index" is returned as a string, not a number. This can be caught by TypeScript, but can still lead to subtle bugs. + +You may have confused for-in with for-of, which iterates over the elements of the array. If you actually need the index, use a regular `for` loop or the `forEach` method. ## Examples diff --git a/packages/eslint-plugin/src/rules/no-for-in-array.ts b/packages/eslint-plugin/src/rules/no-for-in-array.ts index 75a04de8b9c0..ad70180f3570 100644 --- a/packages/eslint-plugin/src/rules/no-for-in-array.ts +++ b/packages/eslint-plugin/src/rules/no-for-in-array.ts @@ -17,7 +17,7 @@ export default createRule({ }, messages: { forInViolation: - 'For-in loops over arrays are forbidden. Use for-of or array.forEach instead.', + 'For-in loops over arrays skips holes, returns indices as strings, and may visit the prototype chain or other enumerable properties. Use a more robust iteration method such as for-of or array.forEach instead.', }, schema: [], type: 'problem', From f1292bf3beccac0330c3e3ddba7469e659aabf33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Sat, 23 Dec 2023 20:23:23 -0500 Subject: [PATCH 06/23] chore(website): only min-height code blocks with playground buttons (#8111) --- packages/website/src/theme/CodeBlock/Content/styles.module.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/website/src/theme/CodeBlock/Content/styles.module.css b/packages/website/src/theme/CodeBlock/Content/styles.module.css index 76fbfd377e23..b1daba6510b0 100644 --- a/packages/website/src/theme/CodeBlock/Content/styles.module.css +++ b/packages/website/src/theme/CodeBlock/Content/styles.module.css @@ -6,6 +6,9 @@ /* rtl:ignore */ direction: ltr; border-radius: inherit; +} + +.codeBlockContent:has(.playgroundButton) { min-height: 5.5rem; } From b1c92bbca17021605950308987ebc0383037f649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Sat, 23 Dec 2023 20:47:55 -0500 Subject: [PATCH 07/23] chore(website): auto-generate type checked rule notice in rule docs (#7951) * chore(website): auto-generate type checked rule notice in rule docs * Remove called-out now-unnecessary blips * fix /troubleshooting/performance-troubleshooting link * Standardized insertion helper naming, and added to a folder * Consolidated into a utils.ts file * Add a thematic line break after existing content * Refactored into a class * Update packages/website/plugins/generated-rule-docs/index.ts --- .cspell.json | 1 - .../docs/rules/no-unnecessary-condition.md | 3 +- .../website/plugins/generated-rule-docs.ts | 505 ------------------ .../generated-rule-docs/RuleDocsPage.ts | 72 +++ .../addESLintHashToCodeBlocksMeta.ts | 61 +++ .../generated-rule-docs/createRuleDocsPage.ts | 59 ++ .../plugins/generated-rule-docs/index.ts | 46 ++ .../insertions/insertBaseRuleReferences.ts | 68 +++ .../insertions/insertFormattingNotice.ts | 17 + .../insertions/insertNewRuleReferences.ts | 113 ++++ .../insertions/insertResources.ts | 103 ++++ .../insertions/insertRuleDescription.ts | 29 + .../insertions/insertSpecialCaseOptions.ts | 40 ++ .../insertions/insertWhenNotToUseIt.ts | 69 +++ .../removeSourceCodeNotice.ts | 11 + .../plugins/generated-rule-docs/utils.ts | 104 ++++ 16 files changed, 793 insertions(+), 508 deletions(-) delete mode 100644 packages/website/plugins/generated-rule-docs.ts create mode 100644 packages/website/plugins/generated-rule-docs/RuleDocsPage.ts create mode 100644 packages/website/plugins/generated-rule-docs/addESLintHashToCodeBlocksMeta.ts create mode 100644 packages/website/plugins/generated-rule-docs/createRuleDocsPage.ts create mode 100644 packages/website/plugins/generated-rule-docs/index.ts create mode 100644 packages/website/plugins/generated-rule-docs/insertions/insertBaseRuleReferences.ts create mode 100644 packages/website/plugins/generated-rule-docs/insertions/insertFormattingNotice.ts create mode 100644 packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts create mode 100644 packages/website/plugins/generated-rule-docs/insertions/insertResources.ts create mode 100644 packages/website/plugins/generated-rule-docs/insertions/insertRuleDescription.ts create mode 100644 packages/website/plugins/generated-rule-docs/insertions/insertSpecialCaseOptions.ts create mode 100644 packages/website/plugins/generated-rule-docs/insertions/insertWhenNotToUseIt.ts create mode 100644 packages/website/plugins/generated-rule-docs/removeSourceCodeNotice.ts create mode 100644 packages/website/plugins/generated-rule-docs/utils.ts diff --git a/.cspell.json b/.cspell.json index 080101b25f08..4ffaadbefaf5 100644 --- a/.cspell.json +++ b/.cspell.json @@ -137,7 +137,6 @@ "unfixable", "unoptimized", "unprefixed", - "upcasting", "upsert", "warnonunsupportedtypescriptversion", "Zacher" diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md b/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md index 7afa80861999..59c9c126d2d8 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md @@ -101,8 +101,7 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates This rule has a known edge case of triggering on conditions that were modified within function calls (as side effects). It is due to limitations of TypeScript's type narrowing. See [#9998](https://github.com/microsoft/TypeScript/issues/9998) for details. - -We recommend upcasting the variable with a [type assertion](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions). +We recommend using a [type assertion](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions) in those cases. ```ts let condition = false as boolean; diff --git a/packages/website/plugins/generated-rule-docs.ts b/packages/website/plugins/generated-rule-docs.ts deleted file mode 100644 index 816df1635a4f..000000000000 --- a/packages/website/plugins/generated-rule-docs.ts +++ /dev/null @@ -1,505 +0,0 @@ -import prettier from '@prettier/sync'; -import pluginRules from '@typescript-eslint/eslint-plugin/use-at-your-own-risk/rules'; -import { compile } from '@typescript-eslint/rule-schema-to-typescript-types'; -import * as fs from 'fs'; -import * as lz from 'lz-string'; -import type * as mdast from 'mdast'; -import { EOL } from 'os'; -import * as path from 'path'; -import type { Plugin } from 'unified'; -import type * as unist from 'unist'; - -/** - * Rules whose options schema generate annoyingly complex schemas. - * - * @remarks These need to be typed in manually in their .md docs file. - * @todo Get these schemas printing nicely in their .md docs files! - */ -const COMPLICATED_RULE_OPTIONS = new Set([ - 'member-ordering', - 'naming-convention', -]); -/** - * Rules that do funky things with their defaults and require special code - * rather than just JSON.stringify-ing their defaults blob - */ -const SPECIAL_CASE_DEFAULTS = new Map([ - // - ['ban-types', '[{ /* See below for default options */ }]'], -]); - -const prettierConfig = { - ...(prettier.resolveConfig(__filename) ?? {}), - filepath: path.join(__dirname, 'defaults.ts'), -}; - -const sourceUrlPrefix = - 'https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/'; - -const eslintPluginDirectory = path.resolve( - path.join(__dirname, '../../eslint-plugin'), -); - -const optionRegex = /option='(?