diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/ban-types.ts index f21dda8a249..9e7d9f69f4b 100644 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ b/packages/eslint-plugin/src/rules/ban-types.ts @@ -11,6 +11,7 @@ type Types = Record< | { message: string; fixWith?: string; + suggest?: readonly string[]; } >; @@ -20,7 +21,7 @@ export type Options = [ extendDefaults?: boolean; }, ]; -export type MessageIds = 'bannedTypeMessage'; +export type MessageIds = 'bannedTypeMessage' | 'bannedTypeReplacement'; function removeSpaces(str: string): string { return str.replace(/\s/g, ''); @@ -88,7 +89,9 @@ const defaultTypes: Types = { 'The `Object` type actually means "any non-nullish value", so it is marginally better than `unknown`.', '- If you want a type meaning "any object", you probably want `object` instead.', '- If you want a type meaning "any value", you probably want `unknown` instead.', + '- If you really want a type meaning "any non-nullish value", you probably want `NonNullable` instead.', ].join('\n'), + suggest: ['object', 'unknown', 'NonNullable'], }, '{}': { message: [ @@ -96,7 +99,14 @@ const defaultTypes: Types = { '- If you want a type meaning "any object", you probably want `object` instead.', '- If you want a type meaning "any value", you probably want `unknown` instead.', '- If you want a type meaning "empty object", you probably want `Record` instead.', + '- If you really want a type meaning "any non-nullish value", you probably want `NonNullable` instead.', ].join('\n'), + suggest: [ + 'object', + 'unknown', + 'Record', + 'NonNullable', + ], }, }; @@ -123,8 +133,10 @@ export default util.createRule({ recommended: 'error', }, fixable: 'code', + hasSuggestions: true, messages: { bannedTypeMessage: "Don't use `{{name}}` as a type.{{customMessage}}", + bannedTypeReplacement: 'Replace `{{name}}` with `{{replacement}}`.', }, schema: [ { @@ -142,6 +154,10 @@ export default util.createRule({ properties: { message: { type: 'string' }, fixWith: { type: 'string' }, + suggest: { + type: 'array', + items: { type: 'string' }, + }, }, additionalProperties: false, }, @@ -182,6 +198,10 @@ export default util.createRule({ const customMessage = getCustomMessage(bannedType); const fixWith = bannedType && typeof bannedType === 'object' && bannedType.fixWith; + const suggest = + bannedType && typeof bannedType === 'object' + ? bannedType.suggest + : undefined; context.report({ node: typeNode, @@ -193,6 +213,15 @@ export default util.createRule({ fix: fixWith ? (fixer): TSESLint.RuleFix => fixer.replaceText(typeNode, fixWith) : null, + suggest: suggest?.map(replacement => ({ + messageId: 'bannedTypeReplacement', + data: { + name, + replacement, + }, + fix: (fixer): TSESLint.RuleFix => + fixer.replaceText(typeNode, replacement), + })), }); } diff --git a/packages/eslint-plugin/tests/rules/ban-types.test.ts b/packages/eslint-plugin/tests/rules/ban-types.test.ts index 7883f0f45df..831d17a59a1 100644 --- a/packages/eslint-plugin/tests/rules/ban-types.test.ts +++ b/packages/eslint-plugin/tests/rules/ban-types.test.ts @@ -136,6 +136,43 @@ ruleTester.run('ban-types', rule, { ], options, }, + { + code: 'let a: Object;', + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'Object', + customMessage: [ + ' The `Object` type actually means "any non-nullish value", so it is marginally better than `unknown`.', + '- If you want a type meaning "any object", you probably want `object` instead.', + '- If you want a type meaning "any value", you probably want `unknown` instead.', + '- If you really want a type meaning "any non-nullish value", you probably want `NonNullable` instead.', + ].join('\n'), + }, + line: 1, + column: 8, + suggestions: [ + { + messageId: 'bannedTypeReplacement', + data: { name: 'Object', replacement: 'object' }, + output: 'let a: object;', + }, + { + messageId: 'bannedTypeReplacement', + data: { name: 'Object', replacement: 'unknown' }, + output: 'let a: unknown;', + }, + { + messageId: 'bannedTypeReplacement', + data: { name: 'Object', replacement: 'NonNullable' }, + output: 'let a: NonNullable;', + }, + ], + }, + ], + options: [{}], + }, { code: 'let aa: Foo;', errors: [