diff --git a/packages/eslint-plugin/docs/rules/ban-types.md b/packages/eslint-plugin/docs/rules/ban-types.md index d4764328448..e92e57b521f 100644 --- a/packages/eslint-plugin/docs/rules/ban-types.md +++ b/packages/eslint-plugin/docs/rules/ban-types.md @@ -58,6 +58,23 @@ The banned type can either be a type name literal (`Foo`), a type name with gene } ``` +By default, this rule includes types which are likely to be mistakes, such as `String` and `Number`. If you don't want these enabled, set the `extendDefaults` option to `false`: + +```CJSON +{ + "@typescript-eslint/ban-types": ["error", { + "types": { + // add a custom message, AND tell the plugin how to fix it + "String": { + "message": "Use string instead", + "fixWith": "string" + } + }, + "extendDefaults": false + }] +} +``` + ### Example ```json diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/ban-types.ts index 429b5fd6895..7e056959a3f 100644 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ b/packages/eslint-plugin/src/rules/ban-types.ts @@ -13,7 +13,8 @@ type Types = Record< type Options = [ { - types: Types; + types?: Types; + extendDefaults?: boolean; }, ]; type MessageIds = 'bannedTypeMessage'; @@ -51,6 +52,35 @@ function getCustomMessage( return ''; } +/* + Defaults for this rule should be treated as an "all or nothing" + merge, so we need special handling here. + + See: https://github.com/typescript-eslint/typescript-eslint/issues/686 + */ +const defaultTypes = { + String: { + message: 'Use string instead', + fixWith: 'string', + }, + Boolean: { + message: 'Use boolean instead', + fixWith: 'boolean', + }, + Number: { + message: 'Use number instead', + fixWith: 'number', + }, + Object: { + message: 'Use Record instead', + fixWith: 'Record', + }, + Symbol: { + message: 'Use symbol instead', + fixWith: 'symbol', + }, +}; + export default util.createRule({ name: 'ban-types', meta: { @@ -85,38 +115,22 @@ export default util.createRule({ ], }, }, + extendDefaults: { + type: 'boolean', + }, }, additionalProperties: false, }, ], }, - defaultOptions: [ - { - types: { - String: { - message: 'Use string instead', - fixWith: 'string', - }, - Boolean: { - message: 'Use boolean instead', - fixWith: 'boolean', - }, - Number: { - message: 'Use number instead', - fixWith: 'number', - }, - Object: { - message: 'Use Record instead', - fixWith: 'Record', - }, - Symbol: { - message: 'Use symbol instead', - fixWith: 'symbol', - }, - }, - }, - ], - create(context, [{ types }]) { + defaultOptions: [{}], + create(context, [options]) { + const extendDefaults = options.extendDefaults ?? true; + const customTypes = options.types ?? {}; + const types: Types = { + ...(extendDefaults ? defaultTypes : {}), + ...customTypes, + }; const bannedTypes = new Map( Object.entries(types).map(([type, data]) => [removeSpaces(type), data]), ); diff --git a/packages/eslint-plugin/tests/rules/ban-types.test.ts b/packages/eslint-plugin/tests/rules/ban-types.test.ts index aad95c06e4d..0edeb74f8d1 100644 --- a/packages/eslint-plugin/tests/rules/ban-types.test.ts +++ b/packages/eslint-plugin/tests/rules/ban-types.test.ts @@ -72,6 +72,21 @@ ruleTester.run('ban-types', rule, { code: 'let a: NS.Bad._', options, }, + // Replace default options instead of merging with extendDefaults: false + { + code: 'let a: String;', + options: [ + { + types: { + Number: { + message: 'Use number instead.', + fixWith: 'number', + }, + }, + extendDefaults: false, + }, + ], + }, { code: 'let a: undefined', options: options2, @@ -82,6 +97,16 @@ ruleTester.run('ban-types', rule, { }, ], invalid: [ + { + code: 'let a: String;', + errors: [ + { + messageId: 'bannedTypeMessage', + line: 1, + column: 8, + }, + ], + }, { code: 'let a: Object;', errors: [