From 43943d2726d4213883181ad74031630e69960de9 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 4 Feb 2024 09:20:37 -0500 Subject: [PATCH 1/8] feat(eslint-plugin): add meta.docs.recommended setting for strict config options --- .../eslint-plugin/src/rules/ban-ts-comment.ts | 5 ++++- .../src/rules/no-floating-promises.ts | 5 ++++- .../src/rules/restrict-plus-operands.ts | 13 +++++++++++- .../rules/restrict-template-expressions.ts | 14 ++++++++++++- .../utils/src/eslint-utils/RuleCreator.ts | 19 ++++++++++-------- packages/utils/src/ts-eslint/CLIEngine.ts | 14 +++++++++---- packages/utils/src/ts-eslint/Rule.ts | 20 ++++++++++++++----- 7 files changed, 69 insertions(+), 21 deletions(-) diff --git a/packages/eslint-plugin/src/rules/ban-ts-comment.ts b/packages/eslint-plugin/src/rules/ban-ts-comment.ts index 5c70c7b9179b..54b317dbdb6c 100644 --- a/packages/eslint-plugin/src/rules/ban-ts-comment.ts +++ b/packages/eslint-plugin/src/rules/ban-ts-comment.ts @@ -32,7 +32,10 @@ export default createRule<[Options], MessageIds>({ docs: { description: 'Disallow `@ts-` comments or require descriptions after directives', - recommended: 'recommended', + recommended: { + recommended: true, + strict: [{ minimumDescriptionLength: 10 }], + }, }, messages: { tsDirectiveComment: diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index b3ac65296992..b939ac2e9356 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -50,7 +50,10 @@ export default createRule({ docs: { description: 'Require Promise-like statements to be handled appropriately', - recommended: 'recommended', + recommended: { + recommended: true, + strict: [{ ignoreVoid: true }], + }, requiresTypeChecking: true, }, hasSuggestions: true, diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index 9e17fa486b52..1953c15c70cb 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -31,7 +31,18 @@ export default createRule({ docs: { description: 'Require both operands of addition to be the same type and be `bigint`, `number`, or `string`', - recommended: 'recommended', + recommended: { + recommended: true, + strict: [ + { + allowAny: false, + allowBoolean: false, + allowNullish: false, + allowNumberAndString: false, + allowRegExp: false, + }, + ], + }, requiresTypeChecking: true, }, messages: { diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 63a0b171306a..54e6733fce85 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -32,7 +32,19 @@ export default createRule({ docs: { description: 'Enforce template literal expressions to be of `string` type', - recommended: 'recommended', + recommended: { + recommended: true, + strict: [ + { + allowAny: false, + allowBoolean: false, + allowNullish: false, + allowNumber: false, + allowRegExp: false, + allowNever: false, + }, + ], + }, requiresTypeChecking: true, }, messages: { diff --git a/packages/utils/src/eslint-utils/RuleCreator.ts b/packages/utils/src/eslint-utils/RuleCreator.ts index 2ca060bef169..373e8e6680f0 100644 --- a/packages/utils/src/eslint-utils/RuleCreator.ts +++ b/packages/utils/src/eslint-utils/RuleCreator.ts @@ -10,12 +10,15 @@ import { applyDefault } from './applyDefault'; export type { RuleListener, RuleModule }; // we automatically add the url -export type NamedCreateRuleMetaDocs = Omit; -export type NamedCreateRuleMeta = Omit< - RuleMetaData, - 'docs' -> & { - docs: NamedCreateRuleMetaDocs; +export type NamedCreateRuleMetaDocs = Omit< + RuleMetaDataDocs, + 'url' +>; +export type NamedCreateRuleMeta< + TMessageIds extends string, + TOptions extends readonly unknown[], +> = Omit, 'docs'> & { + docs: NamedCreateRuleMetaDocs; }; export interface RuleCreateAndOptions< @@ -33,14 +36,14 @@ export interface RuleWithMeta< TOptions extends readonly unknown[], TMessageIds extends string, > extends RuleCreateAndOptions { - meta: RuleMetaData; + meta: RuleMetaData; } export interface RuleWithMetaAndName< TOptions extends readonly unknown[], TMessageIds extends string, > extends RuleCreateAndOptions { - meta: NamedCreateRuleMeta; + meta: NamedCreateRuleMeta; name: string; } diff --git a/packages/utils/src/ts-eslint/CLIEngine.ts b/packages/utils/src/ts-eslint/CLIEngine.ts index 2e668b49bfcd..c2fb15ecaaa6 100644 --- a/packages/utils/src/ts-eslint/CLIEngine.ts +++ b/packages/utils/src/ts-eslint/CLIEngine.ts @@ -155,13 +155,19 @@ namespace CLIEngine { replacedBy: string[]; } - export interface LintResultData { - rulesMeta: Record>; + export interface LintResultData< + TMessageIds extends string, + TOptions extends readonly unknown[], + > { + rulesMeta: Record>; } - export type Formatter = ( + export type Formatter = < + TMessageIds extends string, + TOptions extends readonly unknown[], + >( results: LintResult[], - data?: LintResultData, + data?: LintResultData, ) => string; } diff --git a/packages/utils/src/ts-eslint/Rule.ts b/packages/utils/src/ts-eslint/Rule.ts index 5d23b07afb0c..6bf2dae73655 100644 --- a/packages/utils/src/ts-eslint/Rule.ts +++ b/packages/utils/src/ts-eslint/Rule.ts @@ -8,7 +8,14 @@ import type { SourceCode } from './SourceCode'; export type RuleRecommendation = 'recommended' | 'strict' | 'stylistic'; -interface RuleMetaDataDocs { +export interface RuleRecommendationAcrossConfigs< + TOptions extends readonly unknown[], +> { + recommended: true; + strict: Partial; +} + +interface RuleMetaDataDocs { /** * Concise description of the rule */ @@ -18,7 +25,7 @@ interface RuleMetaDataDocs { * Used by the build tools to generate the recommended and strict configs. * Exclude to not include it as a recommendation. */ - recommended?: RuleRecommendation; + recommended?: RuleRecommendation | RuleRecommendationAcrossConfigs; /** * The URL of the rule's docs */ @@ -35,7 +42,10 @@ interface RuleMetaDataDocs { */ extendsBaseRule?: boolean | string; } -interface RuleMetaData { +interface RuleMetaData< + TMessageIds extends string, + TOptions extends readonly unknown[], +> { /** * True if the rule is deprecated, false otherwise */ @@ -43,7 +53,7 @@ interface RuleMetaData { /** * Documentation for the rule, unnecessary for custom rules/plugins */ - docs?: RuleMetaDataDocs; + docs?: RuleMetaDataDocs; /** * The fixer category. Omit if there is no fixer */ @@ -631,7 +641,7 @@ interface RuleModule< /** * Metadata about the rule */ - meta: RuleMetaData; + meta: RuleMetaData; /** * Function which returns an object with methods that ESLint calls to “visit” From daa3513e74fe7a4b8c4e4631bedb8b2e2b619ec0 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 4 Feb 2024 09:31:17 -0500 Subject: [PATCH 2/8] Also updated generate:configs script --- .../src/configs/strict-type-checked.ts | 30 ++++++++++-- packages/eslint-plugin/src/configs/strict.ts | 5 +- packages/repo-tools/src/generate-configs.mts | 47 +++++++++++++++---- 3 files changed, 67 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/src/configs/strict-type-checked.ts b/packages/eslint-plugin/src/configs/strict-type-checked.ts index 5666c64035da..38e585c05578 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -11,7 +11,10 @@ export = { extends: ['./configs/base', './configs/eslint-recommended'], rules: { '@typescript-eslint/await-thenable': 'error', - '@typescript-eslint/ban-ts-comment': 'error', + '@typescript-eslint/ban-ts-comment': [ + 'error', + { minimumDescriptionLength: 10 }, + ], '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', @@ -24,7 +27,7 @@ export = { '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-extraneous-class': 'error', - '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }], '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': 'error', @@ -71,8 +74,27 @@ export = { '@typescript-eslint/prefer-ts-expect-error': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', - '@typescript-eslint/restrict-plus-operands': 'error', - '@typescript-eslint/restrict-template-expressions': 'error', + '@typescript-eslint/restrict-plus-operands': [ + 'error', + { + allowAny: false, + allowBoolean: false, + allowNullish: false, + allowNumberAndString: false, + allowRegExp: false, + }, + ], + '@typescript-eslint/restrict-template-expressions': [ + 'error', + { + allowAny: false, + allowBoolean: false, + allowNullish: false, + allowNumber: false, + allowRegExp: false, + allowNever: false, + }, + ], '@typescript-eslint/triple-slash-reference': 'error', '@typescript-eslint/unbound-method': 'error', '@typescript-eslint/unified-signatures': 'error', diff --git a/packages/eslint-plugin/src/configs/strict.ts b/packages/eslint-plugin/src/configs/strict.ts index 75952e10634b..96ddc78b2cfd 100644 --- a/packages/eslint-plugin/src/configs/strict.ts +++ b/packages/eslint-plugin/src/configs/strict.ts @@ -10,7 +10,10 @@ import type { ClassicConfig } from '@typescript-eslint/utils/ts-eslint'; export = { extends: ['./configs/base', './configs/eslint-recommended'], rules: { - '@typescript-eslint/ban-ts-comment': 'error', + '@typescript-eslint/ban-ts-comment': [ + 'error', + { minimumDescriptionLength: 10 }, + ], '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', diff --git a/packages/repo-tools/src/generate-configs.mts b/packages/repo-tools/src/generate-configs.mts index 58ab129a8a8f..362df16c6295 100644 --- a/packages/repo-tools/src/generate-configs.mts +++ b/packages/repo-tools/src/generate-configs.mts @@ -38,7 +38,10 @@ async function main(): Promise { const prettierConfig = await prettier.resolveConfig(REPO_ROOT); - type LinterConfigRules = Record; + type LinterConfigRules = Record< + string, + ClassicConfig.RuleLevel | [ClassicConfig.RuleLevel, ...unknown[]] + >; interface LinterConfig extends ClassicConfig.Config { extends?: string[] | string; @@ -73,6 +76,7 @@ async function main(): Promise { interface RuleFilter { deprecated?: 'exclude'; + getOptions?: GetRuleOptions | undefined; typeChecked?: 'exclude' | 'include-only'; baseRuleForExtensionRule?: 'exclude'; forcedRuleLevel?: Linter.RuleLevel; @@ -126,7 +130,11 @@ async function main(): Promise { '=', chalk.red('error'), ); - config[ruleName] = settings.forcedRuleLevel ?? 'error'; + + const ruleLevel = settings.forcedRuleLevel ?? 'error'; + const ruleOptions = settings.getOptions?.(value); + + config[ruleName] = ruleOptions ? [ruleLevel, ...ruleOptions] : ruleLevel; return config; } @@ -157,16 +165,21 @@ async function main(): Promise { ); } + type GetRuleOptions = ( + rule: RuleModule, + ) => readonly unknown[] | undefined; + interface ExtendedConfigSettings { extraExtends?: readonly string[]; + getOptions?: GetRuleOptions; name: string; - filters?: RuleFilter; + settings?: RuleFilter; ruleEntries: readonly RuleEntry[]; } async function writeExtendedConfig({ extraExtends = [], - filters: ruleFilter, + settings, name, ruleEntries, }: ExtendedConfigSettings): Promise { @@ -174,7 +187,7 @@ async function main(): Promise { () => ({ extends: [...EXTENDS, ...extraExtends], rules: ruleEntries.reduce( - (config, entry) => reducer(config, entry, ruleFilter), + (config, entry) => reducer(config, entry, settings), {}, ), }), @@ -186,7 +199,11 @@ async function main(): Promise { ...recommendations: (RuleRecommendation | undefined)[] ): RuleEntry[] { return allRuleEntries.filter(([, rule]) => - recommendations.includes(rule.meta.docs?.recommended), + typeof rule.meta.docs?.recommended === 'object' + ? Object.keys(rule.meta.docs.recommended).some(level => + recommendations.includes(level as RuleRecommendation), + ) + : recommendations.includes(rule.meta.docs?.recommended), ); } @@ -214,14 +231,14 @@ async function main(): Promise { await writeExtendedConfig({ name: 'all', - filters: { + settings: { deprecated: 'exclude', }, ruleEntries: allRuleEntries, }); await writeExtendedConfig({ - filters: { + settings: { typeChecked: 'exclude', }, name: 'recommended', @@ -234,7 +251,11 @@ async function main(): Promise { }); await writeExtendedConfig({ - filters: { + settings: { + getOptions: rule => + typeof rule.meta.docs?.recommended === 'object' + ? rule.meta.docs.recommended.strict + : undefined, typeChecked: 'exclude', }, name: 'strict', @@ -244,10 +265,16 @@ async function main(): Promise { await writeExtendedConfig({ name: 'strict-type-checked', ruleEntries: filterRuleEntriesTo('recommended', 'strict'), + settings: { + getOptions: rule => + typeof rule.meta.docs?.recommended === 'object' + ? rule.meta.docs.recommended.strict + : undefined, + }, }); await writeExtendedConfig({ - filters: { + settings: { typeChecked: 'exclude', }, name: 'stylistic', From 50ba30ae8d60094571047a2ffa48427f278ce469 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 5 Mar 2024 09:12:28 -0500 Subject: [PATCH 3/8] fix: use recommended, not strict, in recommended configs --- .../configs/recommended-type-checked-only.ts | 25 ++------------ .../src/configs/recommended-type-checked.ts | 30 +++-------------- packages/repo-tools/src/generate-configs.mts | 5 +-- .../configs/recommended-type-checked-only.ts | 28 ++-------------- .../src/configs/recommended-type-checked.ts | 33 +++---------------- 5 files changed, 17 insertions(+), 104 deletions(-) diff --git a/packages/eslint-plugin/src/configs/recommended-type-checked-only.ts b/packages/eslint-plugin/src/configs/recommended-type-checked-only.ts index d77599ad3234..13a1f5fb4271 100644 --- a/packages/eslint-plugin/src/configs/recommended-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/recommended-type-checked-only.ts @@ -13,7 +13,7 @@ export = { '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', - '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }], + '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': 'error', @@ -28,27 +28,8 @@ export = { '@typescript-eslint/no-unsafe-return': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', - '@typescript-eslint/restrict-plus-operands': [ - 'error', - { - allowAny: false, - allowBoolean: false, - allowNullish: false, - allowNumberAndString: false, - allowRegExp: false, - }, - ], - '@typescript-eslint/restrict-template-expressions': [ - 'error', - { - allowAny: false, - allowBoolean: false, - allowNullish: false, - allowNumber: false, - allowRegExp: false, - allowNever: false, - }, - ], + '@typescript-eslint/restrict-plus-operands': 'error', + '@typescript-eslint/restrict-template-expressions': 'error', '@typescript-eslint/unbound-method': 'error', }, } satisfies ClassicConfig.Config; diff --git a/packages/eslint-plugin/src/configs/recommended-type-checked.ts b/packages/eslint-plugin/src/configs/recommended-type-checked.ts index e72a3683fec9..38d36c132851 100644 --- a/packages/eslint-plugin/src/configs/recommended-type-checked.ts +++ b/packages/eslint-plugin/src/configs/recommended-type-checked.ts @@ -11,10 +11,7 @@ export = { extends: ['./configs/base', './configs/eslint-recommended'], rules: { '@typescript-eslint/await-thenable': 'error', - '@typescript-eslint/ban-ts-comment': [ - 'error', - { minimumDescriptionLength: 10 }, - ], + '@typescript-eslint/ban-ts-comment': 'error', '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', @@ -23,7 +20,7 @@ export = { '@typescript-eslint/no-duplicate-type-constituents': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', - '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }], + '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': 'error', @@ -50,27 +47,8 @@ export = { '@typescript-eslint/prefer-as-const': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', - '@typescript-eslint/restrict-plus-operands': [ - 'error', - { - allowAny: false, - allowBoolean: false, - allowNullish: false, - allowNumberAndString: false, - allowRegExp: false, - }, - ], - '@typescript-eslint/restrict-template-expressions': [ - 'error', - { - allowAny: false, - allowBoolean: false, - allowNullish: false, - allowNumber: false, - allowRegExp: false, - allowNever: false, - }, - ], + '@typescript-eslint/restrict-plus-operands': 'error', + '@typescript-eslint/restrict-template-expressions': 'error', '@typescript-eslint/triple-slash-reference': 'error', '@typescript-eslint/unbound-method': 'error', }, diff --git a/packages/repo-tools/src/generate-configs.mts b/packages/repo-tools/src/generate-configs.mts index 7dcbb47b6961..fa5a2467db01 100644 --- a/packages/repo-tools/src/generate-configs.mts +++ b/packages/repo-tools/src/generate-configs.mts @@ -313,6 +313,7 @@ async function main(): Promise { await writeExtendedConfig({ settings: { + getOptions: createGetOptionsForLevel('recommended'), typeChecked: 'exclude', }, name: 'recommended', @@ -323,13 +324,13 @@ async function main(): Promise { name: 'recommended-type-checked', ruleEntries: filterRuleEntriesTo('recommended'), settings: { - getOptions: createGetOptionsForLevel('strict'), + getOptions: createGetOptionsForLevel('recommended'), }, }); await writeExtendedConfig({ settings: { - getOptions: createGetOptionsForLevel('strict'), + getOptions: createGetOptionsForLevel('recommended'), typeChecked: 'include-only', }, name: 'recommended-type-checked-only', diff --git a/packages/typescript-eslint/src/configs/recommended-type-checked-only.ts b/packages/typescript-eslint/src/configs/recommended-type-checked-only.ts index 1a4e61d13204..a2b39143c6cd 100644 --- a/packages/typescript-eslint/src/configs/recommended-type-checked-only.ts +++ b/packages/typescript-eslint/src/configs/recommended-type-checked-only.ts @@ -21,10 +21,7 @@ export default ( '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', - '@typescript-eslint/no-floating-promises': [ - 'error', - { ignoreVoid: true }, - ], + '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': 'error', @@ -39,27 +36,8 @@ export default ( '@typescript-eslint/no-unsafe-return': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', - '@typescript-eslint/restrict-plus-operands': [ - 'error', - { - allowAny: false, - allowBoolean: false, - allowNullish: false, - allowNumberAndString: false, - allowRegExp: false, - }, - ], - '@typescript-eslint/restrict-template-expressions': [ - 'error', - { - allowAny: false, - allowBoolean: false, - allowNullish: false, - allowNumber: false, - allowRegExp: false, - allowNever: false, - }, - ], + '@typescript-eslint/restrict-plus-operands': 'error', + '@typescript-eslint/restrict-template-expressions': 'error', '@typescript-eslint/unbound-method': 'error', }, }, diff --git a/packages/typescript-eslint/src/configs/recommended-type-checked.ts b/packages/typescript-eslint/src/configs/recommended-type-checked.ts index 1ddaa432e535..77feebb92cba 100644 --- a/packages/typescript-eslint/src/configs/recommended-type-checked.ts +++ b/packages/typescript-eslint/src/configs/recommended-type-checked.ts @@ -19,10 +19,7 @@ export default ( { rules: { '@typescript-eslint/await-thenable': 'error', - '@typescript-eslint/ban-ts-comment': [ - 'error', - { minimumDescriptionLength: 10 }, - ], + '@typescript-eslint/ban-ts-comment': 'error', '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', @@ -31,10 +28,7 @@ export default ( '@typescript-eslint/no-duplicate-type-constituents': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', - '@typescript-eslint/no-floating-promises': [ - 'error', - { ignoreVoid: true }, - ], + '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': 'error', @@ -61,27 +55,8 @@ export default ( '@typescript-eslint/prefer-as-const': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', - '@typescript-eslint/restrict-plus-operands': [ - 'error', - { - allowAny: false, - allowBoolean: false, - allowNullish: false, - allowNumberAndString: false, - allowRegExp: false, - }, - ], - '@typescript-eslint/restrict-template-expressions': [ - 'error', - { - allowAny: false, - allowBoolean: false, - allowNullish: false, - allowNumber: false, - allowRegExp: false, - allowNever: false, - }, - ], + '@typescript-eslint/restrict-plus-operands': 'error', + '@typescript-eslint/restrict-template-expressions': 'error', '@typescript-eslint/triple-slash-reference': 'error', '@typescript-eslint/unbound-method': 'error', }, From 98d1aa25081385f67767705c3e7e7c99c4bbf9b3 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 5 Mar 2024 09:14:24 -0500 Subject: [PATCH 4/8] fix: no-floating-promises disables ignoreVoid in strict --- packages/eslint-plugin/src/rules/no-floating-promises.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index b939ac2e9356..9e439118747f 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -52,7 +52,7 @@ export default createRule({ 'Require Promise-like statements to be handled appropriately', recommended: { recommended: true, - strict: [{ ignoreVoid: true }], + strict: [{ ignoreVoid: false }], }, requiresTypeChecking: true, }, From 60ef4129794989ca902be5001d0de2e2d244e334 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 5 Mar 2024 09:14:52 -0500 Subject: [PATCH 5/8] fix: no-floating-promises disables ignoreVoid in strict --- packages/eslint-plugin/src/configs/strict-type-checked-only.ts | 2 +- packages/eslint-plugin/src/configs/strict-type-checked.ts | 2 +- .../typescript-eslint/src/configs/strict-type-checked-only.ts | 2 +- packages/typescript-eslint/src/configs/strict-type-checked.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts index ee71aefe272a..6ab9b2cc0e53 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts @@ -15,7 +15,7 @@ export = { '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-confusing-void-expression': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', - '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }], + '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: false }], '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': 'error', diff --git a/packages/eslint-plugin/src/configs/strict-type-checked.ts b/packages/eslint-plugin/src/configs/strict-type-checked.ts index 8f36a59f2b1b..de748c677576 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -27,7 +27,7 @@ export = { '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-extraneous-class': 'error', - '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }], + '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: false }], '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': 'error', diff --git a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts index e6afe3a83e01..034ac9bf6c6f 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts @@ -25,7 +25,7 @@ export default ( '@typescript-eslint/no-duplicate-type-constituents': 'error', '@typescript-eslint/no-floating-promises': [ 'error', - { ignoreVoid: true }, + { ignoreVoid: false }, ], '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', diff --git a/packages/typescript-eslint/src/configs/strict-type-checked.ts b/packages/typescript-eslint/src/configs/strict-type-checked.ts index 34dfde7b2255..14d30d005440 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked.ts @@ -37,7 +37,7 @@ export default ( '@typescript-eslint/no-extraneous-class': 'error', '@typescript-eslint/no-floating-promises': [ 'error', - { ignoreVoid: true }, + { ignoreVoid: false }, ], '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', From 2c2f724ed50aea23aaff6c6b59dada5ad430b898 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 5 Mar 2024 09:25:12 -0500 Subject: [PATCH 6/8] fix introduced build and lint issues --- packages/repo-tools/src/postinstall.mts | 3 ++- packages/rule-tester/src/utils/config-validator.ts | 3 ++- packages/utils/src/ts-eslint/Rule.ts | 4 ++-- packages/website/plugins/generated-rule-docs/utils.ts | 4 ++-- packages/website/src/hooks/useClipboard.ts | 3 ++- .../website/src/theme/MDXComponents/RuleAttributes.tsx | 9 +++++++-- 6 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/repo-tools/src/postinstall.mts b/packages/repo-tools/src/postinstall.mts index 5facbf0defc9..b89cb17cc867 100644 --- a/packages/repo-tools/src/postinstall.mts +++ b/packages/repo-tools/src/postinstall.mts @@ -20,7 +20,8 @@ if (process.env.SKIP_POSTINSTALL) { process.exit(0); } -void (async function (): Promise { +// eslint-disable-next-line @typescript-eslint/no-floating-promises +(async function (): Promise { // make sure we're running from the workspace root const { default: { workspaceRoot }, diff --git a/packages/rule-tester/src/utils/config-validator.ts b/packages/rule-tester/src/utils/config-validator.ts index 4160f81a4de2..87cd8db38a6a 100644 --- a/packages/rule-tester/src/utils/config-validator.ts +++ b/packages/rule-tester/src/utils/config-validator.ts @@ -78,7 +78,8 @@ function validateRuleSchema( const validateRule = ruleValidators.get(rule); if (validateRule) { - void validateRule(localOptions); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + validateRule(localOptions); if (validateRule.errors) { throw new Error( validateRule.errors diff --git a/packages/utils/src/ts-eslint/Rule.ts b/packages/utils/src/ts-eslint/Rule.ts index baa09533bd5d..c0a3678a00cc 100644 --- a/packages/utils/src/ts-eslint/Rule.ts +++ b/packages/utils/src/ts-eslint/Rule.ts @@ -15,7 +15,7 @@ export interface RuleRecommendationAcrossConfigs< strict: Partial; } -export interface RuleMetaDataDocs { +export interface RuleMetaDataDocs { /** * Concise description of the rule */ @@ -25,7 +25,7 @@ export interface RuleMetaDataDocs { * Used by the build tools to generate the recommended and strict configs. * Exclude to not include it as a recommendation. */ - recommended?: RuleRecommendation | RuleRecommendationAcrossConfigs; + recommended?: RuleRecommendation | RuleRecommendationAcrossConfigs; /** * The URL of the rule's docs */ diff --git a/packages/website/plugins/generated-rule-docs/utils.ts b/packages/website/plugins/generated-rule-docs/utils.ts index e26ce248dbff..d8a2d61aad1d 100644 --- a/packages/website/plugins/generated-rule-docs/utils.ts +++ b/packages/website/plugins/generated-rule-docs/utils.ts @@ -55,8 +55,8 @@ export function getUrlForRuleTest(ruleName: string): string { throw new Error(`Could not find test file for ${ruleName}.`); } -export type RuleMetaDataWithDocs = RuleMetaData & { - docs: RuleMetaDataDocs; +export type RuleMetaDataWithDocs = RuleMetaData & { + docs: RuleMetaDataDocs; }; export type RuleModuleWithMetaDocs = RuleModule & { diff --git a/packages/website/src/hooks/useClipboard.ts b/packages/website/src/hooks/useClipboard.ts index c97ecf383141..d8191784f8e2 100644 --- a/packages/website/src/hooks/useClipboard.ts +++ b/packages/website/src/hooks/useClipboard.ts @@ -8,7 +8,8 @@ export function useClipboard(code: () => string): useClipboardResult { const [copied, setCopied] = useDebouncedToggle(false); const copy = useCallback(() => { - void navigator.clipboard.writeText(code()).then(() => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + navigator.clipboard.writeText(code()).then(() => { setCopied(true); }); }, [setCopied, code]); diff --git a/packages/website/src/theme/MDXComponents/RuleAttributes.tsx b/packages/website/src/theme/MDXComponents/RuleAttributes.tsx index 8340657d7742..5c401fafdd54 100644 --- a/packages/website/src/theme/MDXComponents/RuleAttributes.tsx +++ b/packages/website/src/theme/MDXComponents/RuleAttributes.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import Link from '@docusaurus/Link'; import type { RuleMetaDataDocs } from '@site/../utils/dist/ts-eslint/Rule'; import { useRulesMeta } from '@site/src/hooks/useRulesMeta'; @@ -20,8 +21,12 @@ const recommendations = { stylistic: [STYLISTIC_CONFIG_EMOJI, 'stylistic'], }; -const getRecommendation = (docs: RuleMetaDataDocs): string[] => { - const recommendation = recommendations[docs.recommended!]; +const getRecommendation = (docs: RuleMetaDataDocs): string[] => { + const recommended = docs.recommended!; + const recommendation = + recommendations[ + typeof recommended === 'object' ? 'recommended' : recommended + ]; return docs.requiresTypeChecking ? [recommendation[0], `${recommendation[1]}-type-checked`] From df0bdfb22139e63603fc367cae367a0a1ac51e08 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 5 Mar 2024 10:17:45 -0500 Subject: [PATCH 7/8] Fix configs.test.ts tests --- packages/eslint-plugin/tests/configs.test.ts | 47 +++++++++++++++---- .../typescript-eslint/tests/configs.test.ts | 35 ++++++++++++-- 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/tests/configs.test.ts b/packages/eslint-plugin/tests/configs.test.ts index cc742fbe7aca..fbdcd725d1eb 100644 --- a/packages/eslint-plugin/tests/configs.test.ts +++ b/packages/eslint-plugin/tests/configs.test.ts @@ -23,7 +23,9 @@ function entriesToObject(value: [string, T][]): Record { }, {}); } -function filterRules(values: Record): [string, string][] { +function filterRules( + values: Record, +): [string, string | unknown[]][] { return Object.entries(values).filter(([name]) => name.startsWith(RULE_NAME_PREFIX), ); @@ -39,7 +41,7 @@ function filterAndMapRuleConfigs({ excludeDeprecated, excludeTypeChecked, recommendations, -}: FilterAndMapRuleConfigsSettings = {}): [string, string][] { +}: FilterAndMapRuleConfigsSettings = {}): [string, unknown][] { let result = Object.entries(rules); if (excludeDeprecated) { @@ -51,16 +53,41 @@ function filterAndMapRuleConfigs({ } if (recommendations) { - result = result.filter(([, rule]) => - recommendations.includes(rule.meta.docs?.recommended), - ); + result = result.filter(([, rule]) => { + switch (typeof rule.meta.docs?.recommended) { + case 'undefined': + return false; + case 'object': + return Object.keys(rule.meta.docs.recommended).some(recommended => + recommendations.includes(recommended as RuleRecommendation), + ); + case 'string': + return recommendations.includes(rule.meta.docs.recommended); + } + }); } - return result.map(([name]) => [`${RULE_NAME_PREFIX}${name}`, 'error']); + const highestRecommendation = recommendations?.filter(Boolean).at(-1); + + return result.map(([name, rule]) => { + const customRecommendation = + highestRecommendation && + typeof rule.meta.docs?.recommended === 'object' && + rule.meta.docs.recommended[ + highestRecommendation as 'recommended' | 'strict' + ]; + + return [ + `${RULE_NAME_PREFIX}${name}`, + customRecommendation && typeof customRecommendation !== 'boolean' + ? ['error', customRecommendation[0]] + : 'error', + ]; + }); } function itHasBaseRulesOverriden( - unfilteredConfigRules: Record, + unfilteredConfigRules: Record, ): void { it('has the base rules overriden by the appropriate extension rules', () => { const ruleNames = new Set(Object.keys(unfilteredConfigRules)); @@ -144,7 +171,7 @@ describe('recommended-type-checked.ts', () => { }); describe('strict.ts', () => { - const unfilteredConfigRules: Record = + const unfilteredConfigRules: Record = plugin.configs.strict.rules; it('contains all strict rules, excluding type checked ones', () => { @@ -163,7 +190,7 @@ describe('strict.ts', () => { }); describe('strict-type-checked.ts', () => { - const unfilteredConfigRules: Record = + const unfilteredConfigRules: Record = plugin.configs['strict-type-checked'].rules; it('contains all strict rules', () => { @@ -180,7 +207,7 @@ describe('strict-type-checked.ts', () => { }); describe('stylistic.ts', () => { - const unfilteredConfigRules: Record = + const unfilteredConfigRules: Record = plugin.configs.stylistic.rules; it('contains all stylistic rules, excluding deprecated or type checked ones', () => { diff --git a/packages/typescript-eslint/tests/configs.test.ts b/packages/typescript-eslint/tests/configs.test.ts index f66b638bf1a6..b0a8ea2bb41c 100644 --- a/packages/typescript-eslint/tests/configs.test.ts +++ b/packages/typescript-eslint/tests/configs.test.ts @@ -45,7 +45,7 @@ function filterAndMapRuleConfigs({ excludeDeprecated, excludeTypeChecked, recommendations, -}: FilterAndMapRuleConfigsSettings = {}): [string, string][] { +}: FilterAndMapRuleConfigsSettings = {}): [string, unknown][] { let result = Object.entries(rules); if (excludeDeprecated) { @@ -57,12 +57,37 @@ function filterAndMapRuleConfigs({ } if (recommendations) { - result = result.filter(([, rule]) => - recommendations.includes(rule.meta.docs?.recommended), - ); + result = result.filter(([, rule]) => { + switch (typeof rule.meta.docs?.recommended) { + case 'undefined': + return false; + case 'object': + return Object.keys(rule.meta.docs.recommended).some(recommended => + recommendations.includes(recommended as RuleRecommendation), + ); + case 'string': + return recommendations.includes(rule.meta.docs.recommended); + } + }); } - return result.map(([name]) => [`${RULE_NAME_PREFIX}${name}`, 'error']); + const highestRecommendation = recommendations?.filter(Boolean).at(-1); + + return result.map(([name, rule]) => { + const customRecommendation = + highestRecommendation && + typeof rule.meta.docs?.recommended === 'object' && + rule.meta.docs.recommended[ + highestRecommendation as 'recommended' | 'strict' + ]; + + return [ + `${RULE_NAME_PREFIX}${name}`, + customRecommendation && typeof customRecommendation !== 'boolean' + ? ['error', customRecommendation[0]] + : 'error', + ]; + }); } function itHasBaseRulesOverriden( From 0e8aafa86ed9fd7cf28af7d9651ee1e2c6f295ac Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 17 Mar 2024 08:24:21 -0400 Subject: [PATCH 8/8] Fix test conflict post-merge --- packages/eslint-plugin/tests/configs.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/tests/configs.test.ts b/packages/eslint-plugin/tests/configs.test.ts index 7ce8cb712e5a..db9130e18011 100644 --- a/packages/eslint-plugin/tests/configs.test.ts +++ b/packages/eslint-plugin/tests/configs.test.ts @@ -229,7 +229,7 @@ describe('strict-type-checked.ts', () => { }); describe('strict-type-checked-only.ts', () => { - const unfilteredConfigRules: Record = + const unfilteredConfigRules: Record = plugin.configs['strict-type-checked-only'].rules; it('contains only type-checked strict rules', () => {