diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/ban-types.ts index d15d822de06e..429b5fd6895c 100644 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ b/packages/eslint-plugin/src/rules/ban-types.ts @@ -23,7 +23,11 @@ function removeSpaces(str: string): string { } function stringifyTypeName( - node: TSESTree.EntityName | TSESTree.TSTypeLiteral, + node: + | TSESTree.EntityName + | TSESTree.TSTypeLiteral + | TSESTree.TSNullKeyword + | TSESTree.TSUndefinedKeyword, sourceCode: TSESLint.SourceCode, ): string { return removeSpaces(sourceCode.getText(node)); @@ -113,18 +117,21 @@ export default util.createRule({ }, ], create(context, [{ types }]) { - const bannedTypes: Types = Object.keys(types).reduce( - (res, type) => ({ ...res, [removeSpaces(type)]: types[type] }), - {}, + const bannedTypes = new Map( + Object.entries(types).map(([type, data]) => [removeSpaces(type), data]), ); function checkBannedTypes( - typeNode: TSESTree.EntityName | TSESTree.TSTypeLiteral, + typeNode: + | TSESTree.EntityName + | TSESTree.TSTypeLiteral + | TSESTree.TSNullKeyword + | TSESTree.TSUndefinedKeyword, + name = stringifyTypeName(typeNode, context.getSourceCode()), ): void { - const name = stringifyTypeName(typeNode, context.getSourceCode()); + const bannedType = bannedTypes.get(name); - if (name in bannedTypes) { - const bannedType = bannedTypes[name]; + if (bannedType !== undefined) { const customMessage = getCustomMessage(bannedType); const fixWith = bannedType && typeof bannedType === 'object' && bannedType.fixWith; @@ -144,6 +151,18 @@ export default util.createRule({ } return { + ...(bannedTypes.has('null') && { + TSNullKeyword(node): void { + checkBannedTypes(node, 'null'); + }, + }), + + ...(bannedTypes.has('undefined') && { + TSUndefinedKeyword(node): void { + checkBannedTypes(node, 'undefined'); + }, + }), + TSTypeLiteral(node): void { if (node.members.length) { return; diff --git a/packages/eslint-plugin/tests/rules/ban-types.test.ts b/packages/eslint-plugin/tests/rules/ban-types.test.ts index bbf2ab29db48..aad95c06e4d9 100644 --- a/packages/eslint-plugin/tests/rules/ban-types.test.ts +++ b/packages/eslint-plugin/tests/rules/ban-types.test.ts @@ -24,6 +24,25 @@ const options: InferOptionsTypeFromRule = [ }, ]; +const options2: InferOptionsTypeFromRule = [ + { + types: { + null: { + message: 'Use undefined instead.', + fixWith: 'undefined', + }, + }, + }, +]; + +const options3: InferOptionsTypeFromRule = [ + { + types: { + undefined: null, + }, + }, +]; + ruleTester.run('ban-types', rule, { valid: [ 'let f = Object();', // Should not fail if there is no options set @@ -53,6 +72,14 @@ ruleTester.run('ban-types', rule, { code: 'let a: NS.Bad._', options, }, + { + code: 'let a: undefined', + options: options2, + }, + { + code: 'let a: null', + options: options3, + }, ], invalid: [ { @@ -70,6 +97,30 @@ ruleTester.run('ban-types', rule, { ], options, }, + { + code: 'let a: undefined;', + errors: [ + { + messageId: 'bannedTypeMessage', + data: { name: 'undefined', customMessage: '' }, + line: 1, + column: 8, + }, + ], + options: options3, + }, + { + code: 'let a: null;', + errors: [ + { + messageId: 'bannedTypeMessage', + data: { name: 'null', customMessage: ' Use undefined instead.' }, + line: 1, + column: 8, + }, + ], + options: options2, + }, { code: 'let aa: Foo;', errors: [