From d0557fa7cbab80e3a449810f77275e87961eca89 Mon Sep 17 00:00:00 2001 From: Armano Date: Sun, 16 Dec 2018 19:58:09 +0100 Subject: [PATCH 1/3] Add rule `interface-over-type-literal` --- README.md | 1 + docs/rules/interface-over-type-literal.md | 33 +++++++ lib/rules/interface-over-type-literal.js | 72 ++++++++++++++ .../lib/rules/interface-over-type-literal.js | 95 +++++++++++++++++++ 4 files changed, 201 insertions(+) create mode 100644 docs/rules/interface-over-type-literal.md create mode 100644 lib/rules/interface-over-type-literal.js create mode 100644 tests/lib/rules/interface-over-type-literal.js diff --git a/README.md b/README.md index d33b879..5f2077e 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ This guarantees 100% compatibility between the plugin and the parser. | [`typescript/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods (`member-access` from TSLint) | | | | [`typescript/generic-type-naming`](./docs/rules/generic-type-naming.md) | Enforces naming of generic type variables | | | | [`typescript/interface-name-prefix`](./docs/rules/interface-name-prefix.md) | Require that interface names be prefixed with `I` (`interface-name` from TSLint) | | | +| [`typescript/interface-over-type-literal`](./docs/rules/interface-over-type-literal.md) | Prefer an interface declaration over a type literal (type T = { ... }) (`interface-over-type-literal` from TSLint) | | :wrench: | | [`typescript/member-delimiter-style`](./docs/rules/member-delimiter-style.md) | Require a specific member delimiter style for interfaces and type literals | | :wrench: | | [`typescript/member-naming`](./docs/rules/member-naming.md) | Enforces naming conventions for class members by visibility. | | | | [`typescript/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order (`member-ordering` from TSLint) | | | diff --git a/docs/rules/interface-over-type-literal.md b/docs/rules/interface-over-type-literal.md new file mode 100644 index 0000000..dd16fb2 --- /dev/null +++ b/docs/rules/interface-over-type-literal.md @@ -0,0 +1,33 @@ +# Prefer an interface declaration over a type literal (type T = { ... }) (interface-over-type-literal) + +Interfaces are generally preferred over type literals because interfaces can be implemented, extended and merged. + +## Rule Details + +Examples of **incorrect** code for this rule. + +```ts +type T = { x: number; } +``` + +Examples of **correct** code for this rule. + +```ts +type T = string; +type Foo = string | { } + +interface T { + x: number; +} +``` + +## Options +```CJSON +{ + "interface-over-type-literal": "error" +} +``` + +## Compatibility + +* TSLint: [interface-over-type-literal](https://palantir.github.io/tslint/rules/interface-over-type-literal/) diff --git a/lib/rules/interface-over-type-literal.js b/lib/rules/interface-over-type-literal.js new file mode 100644 index 0000000..8717c42 --- /dev/null +++ b/lib/rules/interface-over-type-literal.js @@ -0,0 +1,72 @@ +/** + * @fileoverview Prefer an interface declaration over a type literal (type T = { ... }) + * @author Armano + */ +"use strict"; + +const util = require("../util"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: + "Prefer an interface declaration over a type literal (type T = { ... })", + extraDescription: [util.tslintRule("interface-over-type-literal")], + category: "TypeScript", + url: util.metaDocsUrl("interface-over-type-literal"), + }, + fixable: "code", + messages: { + interfaceOverType: "Use an interface instead of a type literal.", + }, + schema: [], + }, + create(context) { + const sourceCode = context.getSourceCode(); + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + return { + // VariableDeclaration with kind type has only one VariableDeclarator + "VariableDeclaration[kind='type'] > VariableDeclarator[init.type='TSTypeLiteral']"( + node + ) { + context.report({ + node, + messageId: "interfaceOverType", + fix(fixer) { + const typeNode = node.typeParameters || node.id; + + const fixes = [ + fixer.replaceText( + sourceCode.getFirstToken(node.parent), + "interface" + ), + fixer.replaceTextRange( + [typeNode.range[1], node.init.range[0]], + " " + ), + ]; + + const afterToken = sourceCode.getTokenAfter(node.init); + + if ( + afterToken && + afterToken.type === "Punctuator" && + afterToken.value === ";" + ) { + fixes.push(fixer.remove(afterToken)); + } + + return fixes; + }, + }); + }, + }; + }, +}; diff --git a/tests/lib/rules/interface-over-type-literal.js b/tests/lib/rules/interface-over-type-literal.js new file mode 100644 index 0000000..4fe9b9d --- /dev/null +++ b/tests/lib/rules/interface-over-type-literal.js @@ -0,0 +1,95 @@ +/** + * @fileoverview Prefer an interface declaration over a type literal (type T = { ... }) + * @author Armano + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/interface-over-type-literal"), + RuleTester = require("eslint").RuleTester; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parser: "typescript-eslint-parser", +}); + +ruleTester.run("interface-over-type-literal", rule, { + valid: [ + { + code: `var foo = { };`, + }, + { + code: `type U = string;`, + }, + { + code: `type V = { x: number; } | { y: string; };`, + }, + { + code: ` +type Record = { + [K in T]: U; +} + `, + }, + ], + invalid: [ + { + code: `type T = { x: number; }`, + output: `interface T { x: number; }`, + errors: [ + { + messageId: "interfaceOverType", + line: 1, + column: 6, + }, + ], + }, + { + code: `type T={ x: number; }`, + output: `interface T { x: number; }`, + errors: [ + { + messageId: "interfaceOverType", + line: 1, + column: 6, + }, + ], + }, + { + code: `type T= { x: number; }`, + output: `interface T { x: number; }`, + errors: [ + { + messageId: "interfaceOverType", + line: 1, + column: 6, + }, + ], + }, + { + code: ` +export type W = { + x: T, +}; +`, + output: ` +export interface W { + x: T, +} +`, + errors: [ + { + messageId: "interfaceOverType", + line: 2, + column: 13, + }, + ], + }, + ], +}); From a7dbf1d895ac151ec92683d39683e36ed674d7ef Mon Sep 17 00:00:00 2001 From: Armano Date: Sun, 16 Dec 2018 22:18:03 +0100 Subject: [PATCH 2/3] rename rule as `prefer-interface` --- README.md | 2 +- ...er-type-literal.md => prefer-interface.md} | 2 +- ...er-type-literal.js => prefer-interface.js} | 2 +- ...er-type-literal.js => prefer-interface.js} | 20 ++++++------------- 4 files changed, 9 insertions(+), 17 deletions(-) rename docs/rules/{interface-over-type-literal.md => prefer-interface.md} (93%) rename lib/rules/{interface-over-type-literal.js => prefer-interface.js} (97%) rename tests/lib/rules/{interface-over-type-literal.js => prefer-interface.js} (86%) diff --git a/README.md b/README.md index 5f2077e..3a1d642 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,6 @@ This guarantees 100% compatibility between the plugin and the parser. | [`typescript/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods (`member-access` from TSLint) | | | | [`typescript/generic-type-naming`](./docs/rules/generic-type-naming.md) | Enforces naming of generic type variables | | | | [`typescript/interface-name-prefix`](./docs/rules/interface-name-prefix.md) | Require that interface names be prefixed with `I` (`interface-name` from TSLint) | | | -| [`typescript/interface-over-type-literal`](./docs/rules/interface-over-type-literal.md) | Prefer an interface declaration over a type literal (type T = { ... }) (`interface-over-type-literal` from TSLint) | | :wrench: | | [`typescript/member-delimiter-style`](./docs/rules/member-delimiter-style.md) | Require a specific member delimiter style for interfaces and type literals | | :wrench: | | [`typescript/member-naming`](./docs/rules/member-naming.md) | Enforces naming conventions for class members by visibility. | | | | [`typescript/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order (`member-ordering` from TSLint) | | | @@ -80,6 +79,7 @@ This guarantees 100% compatibility between the plugin and the parser. | [`typescript/no-unused-vars`](./docs/rules/no-unused-vars.md) | Prevent TypeScript-specific constructs from being erroneously flagged as unused | :heavy_check_mark: | | | [`typescript/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | | | | [`typescript/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements (`no-var-requires` from TSLint) | | | +| [`typescript/prefer-interface`](./docs/rules/prefer-interface.md) | Prefer an interface declaration over a type literal (type T = { ... }) (`interface-over-type-literal` from TSLint) | | :wrench: | | [`typescript/prefer-namespace-keyword`](./docs/rules/prefer-namespace-keyword.md) | Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules. (`no-internal-module` from TSLint) | | :wrench: | | [`typescript/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations (`typedef-whitespace` from TSLint) | | :wrench: | diff --git a/docs/rules/interface-over-type-literal.md b/docs/rules/prefer-interface.md similarity index 93% rename from docs/rules/interface-over-type-literal.md rename to docs/rules/prefer-interface.md index dd16fb2..676f1bd 100644 --- a/docs/rules/interface-over-type-literal.md +++ b/docs/rules/prefer-interface.md @@ -1,4 +1,4 @@ -# Prefer an interface declaration over a type literal (type T = { ... }) (interface-over-type-literal) +# Prefer an interface declaration over a type literal (type T = { ... }) (prefer-interface) Interfaces are generally preferred over type literals because interfaces can be implemented, extended and merged. diff --git a/lib/rules/interface-over-type-literal.js b/lib/rules/prefer-interface.js similarity index 97% rename from lib/rules/interface-over-type-literal.js rename to lib/rules/prefer-interface.js index 8717c42..16c2e45 100644 --- a/lib/rules/interface-over-type-literal.js +++ b/lib/rules/prefer-interface.js @@ -17,7 +17,7 @@ module.exports = { "Prefer an interface declaration over a type literal (type T = { ... })", extraDescription: [util.tslintRule("interface-over-type-literal")], category: "TypeScript", - url: util.metaDocsUrl("interface-over-type-literal"), + url: util.metaDocsUrl("prefer-interface"), }, fixable: "code", messages: { diff --git a/tests/lib/rules/interface-over-type-literal.js b/tests/lib/rules/prefer-interface.js similarity index 86% rename from tests/lib/rules/interface-over-type-literal.js rename to tests/lib/rules/prefer-interface.js index 4fe9b9d..da54734 100644 --- a/tests/lib/rules/interface-over-type-literal.js +++ b/tests/lib/rules/prefer-interface.js @@ -8,7 +8,7 @@ // Requirements //------------------------------------------------------------------------------ -const rule = require("../../../lib/rules/interface-over-type-literal"), +const rule = require("../../../lib/rules/prefer-interface"), RuleTester = require("eslint").RuleTester; //------------------------------------------------------------------------------ @@ -21,22 +21,14 @@ const ruleTester = new RuleTester({ ruleTester.run("interface-over-type-literal", rule, { valid: [ - { - code: `var foo = { };`, - }, - { - code: `type U = string;`, - }, - { - code: `type V = { x: number; } | { y: string; };`, - }, - { - code: ` + `var foo = { };`, + `type U = string;`, + `type V = { x: number; } | { y: string; };`, + ` type Record = { [K in T]: U; } - `, - }, + `, ], invalid: [ { From c54f97ebdb22e00dabe73d1034b96224f90f6640 Mon Sep 17 00:00:00 2001 From: Armano Date: Sun, 16 Dec 2018 23:16:53 +0100 Subject: [PATCH 3/3] update roadmap --- ROADMAP.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index efe53cb..f53f7c0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -135,7 +135,7 @@ | [`file-name-casing`] | 🔌 | [`unicorn/filename-case`](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/master/docs/rules/filename-case.md) | | [`import-spacing`] | 🔌 | Use [Prettier] | | [`interface-name`] | ✅ | [`typescript/interface-name-prefix`] | -| [`interface-over-type-literal`] | 🛑 | N/A | +| [`interface-over-type-literal`] | 🛑 | [`typescript/prefer-interface`] | | [`jsdoc-format`] | 🛑 | N/A | | [`match-default-export-name`] | 🛑 | N/A | | [`newline-before-return`] | 🌟 | [`padding-line-between-statements`](https://eslint.org/docs/rules/padding-line-between-statements) (`{ blankLine: "always", prev: "*", next: "return" }`) | @@ -351,4 +351,5 @@ [`typescript/no-angle-bracket-type-assertion`]: https://github.com/bradzacher/eslint-plugin-typescript/blob/master/docs/rules/no-angle-bracket-type-assertion.md [`typescript/no-parameter-properties`]: https://github.com/bradzacher/eslint-plugin-typescript/blob/master/docs/rules/no-parameter-properties.md [`typescript/member-delimiter-style`]: https://github.com/bradzacher/eslint-plugin-typescript/blob/master/docs/rules/member-delimiter-style.md +[`typescript/prefer-interface`]: https://github.com/bradzacher/eslint-plugin-typescript/blob/master/docs/rules/prefer-interface.md