Skip to content
This repository has been archived by the owner on Jan 19, 2019. It is now read-only.

Add ban-types rule #181

Merged
merged 1 commit into from
Dec 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ This guarantees 100% compatibility between the plugin and the parser.
<!-- Please run `npm run docs` to update this section -->
<!-- begin rule list -->
* [`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
Expand Down
73 changes: 73 additions & 0 deletions docs/rules/ban-types.md
Original file line number Diff line number Diff line change
@@ -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<F = String> extends Bar<String> implements Baz<String> {
constructor (foo: String) {
}

exit () : Array<String> {
const foo: String = 1 as String
}
}
```

Examples of **correct** code for this rule `"String": "Use string instead"`

```ts
class Foo<F = string> extends Bar<string> implements Baz<string> {
constructor (foo: string) {
}

exit () : Array<string> {
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/)
96 changes: 96 additions & 0 deletions lib/rules/ban-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* @fileoverview Enforces that types will not to be used
* @author Armano <https://github.com/armano2>
*/
"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)),
});
}
}
},
};
},
};
195 changes: 195 additions & 0 deletions tests/lib/rules/ban-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/**
* @fileoverview Enforces that types will not to be used
* @author Armano <https://github.com/armano2>
*/
"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<F = String> extends Bar<String> implements Baz<Object> {
constructor (foo: String | Object) {
}

exit () : Array<String> {
const foo: String = 1 as String
}
}
`,
output: `
class Foo<F = string> extends Bar<string> implements Baz<Object> {
constructor (foo: string | Object) {
}

exit () : Array<string> {
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,
},
],
});