diff --git a/README.md b/README.md index 5dec1c1..8bae878 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ This guarantees 100% compatibility between the plugin and the parser. * [`typescript/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) — Require that member overloads be consecutive +* [`typescript/ban-types`](./docs/rules/ban-types.md) — Enforces that types will not to be used (`ban-types` from TSLint) * [`typescript/camelcase`](./docs/rules/camelcase.md) — Enforce camelCase naming convention * [`typescript/class-name-casing`](./docs/rules/class-name-casing.md) — Require PascalCased class and interface names (`class-name` from TSLint) * [`typescript/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) — Require explicit return types on functions and class methods diff --git a/docs/rules/ban-types.md b/docs/rules/ban-types.md new file mode 100644 index 0000000..263cc0d --- /dev/null +++ b/docs/rules/ban-types.md @@ -0,0 +1,73 @@ +# Enforces that types will not to be used (ban-types) + +Bans specific types from being used. Does not ban the corresponding runtime objects from being used. + +## Rule Details + +Examples of **incorrect** code for this rule `"String": "Use string instead"` + +```ts +class Foo extends Bar implements Baz { + constructor (foo: String) { + } + + exit () : Array { + const foo: String = 1 as String + } +} +``` + +Examples of **correct** code for this rule `"String": "Use string instead"` + +```ts +class Foo extends Bar implements Baz { + constructor (foo: string) { + } + + exit () : Array { + const foo: string = 1 as string + } +} +``` + +## Options +```CJSON +{ + "typescript/ban-types": ["error", { + "types": { + // report usages of the type using the default error message + "Foo": null, + + // add a custom message to help explain why not to use it + "Bar": "Don't use bar!", + + // add a custom message, AND tell the plugin how to fix it + "String": { + "message": "Use string instead", + "fixWith": "string" + } + } + } +} +``` + +### Example +```json +{ + "typescript/ban-types": ["error", { + "types": { + "Array": null, + "Object": "Use {} instead", + "String": { + "message": "Use string instead", + "fixWith": "string" + } + } + }] +} +``` + + +## Compatibility + +* TSLint: [ban-types](https://palantir.github.io/tslint/rules/ban-types/) diff --git a/lib/rules/ban-types.js b/lib/rules/ban-types.js new file mode 100644 index 0000000..3eaed98 --- /dev/null +++ b/lib/rules/ban-types.js @@ -0,0 +1,96 @@ +/** + * @fileoverview Enforces that types will not to be used + * @author Armano + */ +"use strict"; + +const util = require("../util"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: "Enforces that types will not to be used", + extraDescription: [util.tslintRule("ban-types")], + category: "TypeScript", + url: + "https://github.com/nzakas/eslint-plugin-typescript/blob/master/docs/rules/ban-types.md", + }, + fixable: "code", + messages: { + bannedTypeMessage: + "Don't use '{{name}}' as a type.{{customMessage}}", + }, + schema: [ + { + type: "object", + properties: { + types: { + type: "object", + additionalProperties: { + oneOf: [ + { type: "null" }, + { type: "string" }, + { + type: "object", + properties: { + message: { type: "string" }, + fixWith: { type: "string" }, + }, + additionalProperties: false, + }, + ], + }, + }, + }, + additionalProperties: false, + }, + ], + }, + + create(context) { + const banedTypes = (context.options[0] || {}).types || {}; + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + "TSTypeReference Identifier"(node) { + if (node.parent && node.parent.type !== "TSQualifiedName") { + if (node.name in banedTypes) { + let customMessage = ""; + const bannedCfgValue = banedTypes[node.name]; + let fixWith = null; + + if (typeof bannedCfgValue === "string") { + customMessage += ` ${bannedCfgValue}`; + } else if (bannedCfgValue !== null) { + if (bannedCfgValue.message) { + customMessage += ` ${bannedCfgValue.message}`; + } + if (bannedCfgValue.fixWith) { + fixWith = bannedCfgValue.fixWith; + } + } + + context.report({ + node, + messageId: "bannedTypeMessage", + data: { + name: node.name, + customMessage, + }, + fix: + fixWith !== null && + (fixer => fixer.replaceText(node, fixWith)), + }); + } + } + }, + }; + }, +}; diff --git a/tests/lib/rules/ban-types.js b/tests/lib/rules/ban-types.js new file mode 100644 index 0000000..4b7ed64 --- /dev/null +++ b/tests/lib/rules/ban-types.js @@ -0,0 +1,195 @@ +/** + * @fileoverview Enforces that types will not to be used + * @author Armano + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/ban-types"), + RuleTester = require("eslint").RuleTester; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parser: "typescript-eslint-parser", +}); + +const options = [ + { + types: { + String: { + message: "Use string instead.", + fixWith: "string", + }, + Object: "Use '{}' instead.", + Array: null, + F: null, + }, + }, +]; + +ruleTester.run("ban-types", rule, { + valid: [ + "let f = Object();", // Should not fail if there is no options set + { + code: "let f = Object();", + options, + }, + { + code: "let g = Object.create(null);", + options, + }, + { + code: "let h = String(false);", + options, + }, + { + code: "let e: foo.String;", + options, + }, + ], + invalid: [ + { + code: "let a: Object;", + errors: [ + { + message: "Don't use 'Object' as a type. Use '{}' instead.", + line: 1, + column: 8, + }, + ], + options, + }, + { + code: "let b: {c: String};", + output: "let b: {c: string};", + errors: [ + { + message: + "Don't use 'String' as a type. Use string instead.", + line: 1, + column: 12, + }, + ], + options, + }, + { + code: "function foo(a: String) {}", + output: "function foo(a: string) {}", + errors: [ + { + message: + "Don't use 'String' as a type. Use string instead.", + line: 1, + column: 17, + }, + ], + options, + }, + { + code: "'a' as String;", + output: "'a' as string;", + errors: [ + { + message: + "Don't use 'String' as a type. Use string instead.", + line: 1, + column: 8, + }, + ], + options, + }, + { + code: "let c: F;", + errors: [ + { + message: "Don't use 'F' as a type.", + line: 1, + column: 8, + }, + ], + options, + }, + { + code: ` + class Foo extends Bar implements Baz { + constructor (foo: String | Object) { + } + + exit () : Array { + const foo: String = 1 as String + } + } + `, + output: ` + class Foo extends Bar implements Baz { + constructor (foo: string | Object) { + } + + exit () : Array { + const foo: string = 1 as string + } + } + `, + errors: [ + { + message: + "Don't use 'String' as a type. Use string instead.", + line: 2, + column: 27, + }, + { + message: + "Don't use 'String' as a type. Use string instead.", + line: 2, + column: 47, + }, + { + message: "Don't use 'Object' as a type. Use '{}' instead.", + line: 2, + column: 70, + }, + { + message: + "Don't use 'String' as a type. Use string instead.", + line: 3, + column: 35, + }, + { + message: "Don't use 'Object' as a type. Use '{}' instead.", + line: 3, + column: 44, + }, + { + message: "Don't use 'Array' as a type.", + line: 6, + column: 27, + }, + { + message: + "Don't use 'String' as a type. Use string instead.", + line: 6, + column: 33, + }, + { + message: + "Don't use 'String' as a type. Use string instead.", + line: 7, + column: 32, + }, + { + message: + "Don't use 'String' as a type. Use string instead.", + line: 7, + column: 46, + }, + ], + options, + }, + ], +});