From 74484411504c4b7c55c9b221b41f24daca98f54b Mon Sep 17 00:00:00 2001 From: otofu-square Date: Fri, 26 Apr 2019 10:17:28 +0900 Subject: [PATCH 01/15] feat(eslint-plugin): add consistent-type-definitions rule --- .../src/rules/consistent-type-definisions.ts | 105 ++++++++++++ .../rules/consistent-type-definitions.test.ts | 155 ++++++++++++++++++ 2 files changed, 260 insertions(+) create mode 100644 packages/eslint-plugin/src/rules/consistent-type-definisions.ts create mode 100644 packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts diff --git a/packages/eslint-plugin/src/rules/consistent-type-definisions.ts b/packages/eslint-plugin/src/rules/consistent-type-definisions.ts new file mode 100644 index 00000000000..2547718f352 --- /dev/null +++ b/packages/eslint-plugin/src/rules/consistent-type-definisions.ts @@ -0,0 +1,105 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { RuleFix } from 'ts-eslint'; +import * as util from '../util'; + +export default util.createRule({ + name: 'consistent-type-definisions', + meta: { + type: 'suggestion', + docs: { + description: + 'Consistent with type definition either `interface` or `type`', + category: 'Stylistic Issues', + recommended: 'error', + tslintName: 'consistent-type-definisions', + }, + messages: { + interfaceOverType: 'Use an `interface` instead of a `type`', + typeOverInterface: 'Use a `type` instead of an `interface`', + }, + schema: [ + { + enum: ['interface', 'type'], + }, + ], + fixable: 'code', + }, + defaultOptions: ['interface'], + create(context, [option]) { + const sourceCode = context.getSourceCode(); + + return { + // VariableDeclaration with kind type has only one VariableDeclarator + "TSTypeAliasDeclaration[typeAnnotation.type='TSTypeLiteral']"( + node: TSESTree.TSTypeAliasDeclaration, + ) { + if (option === 'interface') { + context.report({ + node: node.id, + messageId: 'interfaceOverType', + fix(fixer) { + const typeNode = node.typeParameters || node.id; + const fixes: RuleFix[] = []; + + const firstToken = sourceCode.getFirstToken(node); + if (firstToken) { + fixes.push(fixer.replaceText(firstToken, 'interface')); + fixes.push( + fixer.replaceTextRange( + [typeNode.range[1], node.typeAnnotation.range[0]], + ' ', + ), + ); + } + + const afterToken = sourceCode.getTokenAfter(node.typeAnnotation); + if ( + afterToken && + afterToken.type === 'Punctuator' && + afterToken.value === ';' + ) { + fixes.push(fixer.remove(afterToken)); + } + + return fixes; + }, + }); + } + }, + TSInterfaceDeclaration: node => { + if (option === 'type') { + context.report({ + node: node.id, + messageId: 'typeOverInterface', + fix(fixer) { + const typeNode = node.typeParameters || node.id; + const fixes: RuleFix[] = []; + + const firstToken = sourceCode.getFirstToken(node); + if (firstToken) { + fixes.push(fixer.replaceText(firstToken, 'type')); + fixes.push( + fixer.replaceTextRange( + [typeNode.range[1], node.body.range[0]], + ' = ', + ), + ); + } + + if (node.extends) { + node.extends.forEach(heritage => { + const typeIdentifier = sourceCode.getText(heritage); + fixes.push( + fixer.insertTextAfter(node.body, ` & ${typeIdentifier}`), + ); + }); + } + + return fixes; + }, + }); + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts new file mode 100644 index 00000000000..831faea22e1 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts @@ -0,0 +1,155 @@ +import rule from '../../src/rules/consistent-type-definisions'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('consistent-type-definisions', rule, { + valid: [ + `var foo = { };`, + `interface A {}`, + `interface A extends B { x: number; }`, + `type U = string;`, + `type V = { x: number; } | { y: string; };`, + ` +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, + }, + ], + }, + { + code: `interface T { x: number; }`, + output: `type T = { x: number; }`, + options: ['type'], + errors: [ + { + messageId: 'typeOverInterface', + line: 1, + column: 11, + }, + ], + }, + { + code: `interface T{ x: number; }`, + output: `type T = { x: number; }`, + options: ['type'], + errors: [ + { + messageId: 'typeOverInterface', + line: 1, + column: 11, + }, + ], + }, + { + code: `interface T { x: number; }`, + output: `type T = { x: number; }`, + options: ['type'], + errors: [ + { + messageId: 'typeOverInterface', + line: 1, + column: 11, + }, + ], + }, + { + code: `interface A extends B, C { x: number; };`, + output: `type A = { x: number; } & B & C;`, + options: ['type'], + errors: [ + { + messageId: 'typeOverInterface', + line: 1, + column: 11, + }, + ], + }, + { + code: `interface A extends B, C { x: number; };`, + output: `type A = { x: number; } & B & C;`, + options: ['type'], + errors: [ + { + messageId: 'typeOverInterface', + line: 1, + column: 11, + }, + ], + }, + { + code: ` +export interface W { + x: T, +}; +`, + output: ` +export type W = { + x: T, +}; +`, + options: ['type'], + errors: [ + { + messageId: 'typeOverInterface', + line: 2, + column: 18, + }, + ], + }, + ], +}); From 770b6562d2f2db9c910c581398444deea753c750 Mon Sep 17 00:00:00 2001 From: otofu-square Date: Sat, 27 Apr 2019 21:18:57 +0900 Subject: [PATCH 02/15] fix(eslint-plugin): deprecate prefer-interface rule --- packages/eslint-plugin/README.md | 2 +- packages/eslint-plugin/src/rules/prefer-interface.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 044ff064372..1b688f121f8 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -117,6 +117,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Enforces that types will not to be used (`ban-types` from TSLint) | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | | | [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names (`class-name` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/consistent-type-definitions`](./docs/rules/consistent-type-definitions.md) | Consistent with type definition either `type` or `interface` | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) | Require explicit return types on functions and class methods | :heavy_check_mark: | | | | [`@typescript-eslint/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods (`member-access` from TSLint) | :heavy_check_mark: | | | | [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Spacing between function identifiers and their invocations | | :wrench: | | @@ -152,7 +153,6 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/prefer-for-of`](./docs/rules/prefer-for-of.md) | Prefer a ‘for-of’ loop over a standard ‘for’ loop if the index is only used to access the array being iterated. | | | | | [`@typescript-eslint/prefer-function-type`](./docs/rules/prefer-function-type.md) | Use function types instead of interfaces with call signatures (`callable-types` from TSLint) | | :wrench: | | | [`@typescript-eslint/prefer-includes`](./docs/rules/prefer-includes.md) | Enforce `includes` method over `indexOf` method. | | :wrench: | :thought_balloon: | -| [`@typescript-eslint/prefer-interface`](./docs/rules/prefer-interface.md) | Prefer an interface declaration over a type literal (type T = { ... }) (`interface-over-type-literal` from TSLint) | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/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) | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/prefer-string-starts-ends-with`](./docs/rules/prefer-string-starts-ends-with.md) | Enforce the use of `String#startsWith` and `String#endsWith` instead of other equivalent methods of checking substrings | | :wrench: | | | [`@typescript-eslint/promise-function-async`](./docs/rules/promise-function-async.md) | Requires any function or method that returns a Promise to be marked async. (`promise-function-async` from TSLint) | | | :thought_balloon: | diff --git a/packages/eslint-plugin/src/rules/prefer-interface.ts b/packages/eslint-plugin/src/rules/prefer-interface.ts index 10308d7e70e..5a3d41329a4 100644 --- a/packages/eslint-plugin/src/rules/prefer-interface.ts +++ b/packages/eslint-plugin/src/rules/prefer-interface.ts @@ -18,6 +18,8 @@ export default util.createRule({ interfaceOverType: 'Use an interface instead of a type literal.', }, schema: [], + deprecated: true, + replacedBy: 'consistent-type-definisions', }, defaultOptions: [], create(context) { From 7c26569cfc2f5bfafb001deda1600ba4f781fbd6 Mon Sep 17 00:00:00 2001 From: otofu-square Date: Sat, 27 Apr 2019 21:51:22 +0900 Subject: [PATCH 03/15] fix(eslint-plugin): fix wrong typing for `replacedBy` in meta --- packages/eslint-plugin/src/rules/prefer-interface.ts | 2 +- packages/eslint-plugin/typings/ts-eslint.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-interface.ts b/packages/eslint-plugin/src/rules/prefer-interface.ts index 5a3d41329a4..9b601fc0066 100644 --- a/packages/eslint-plugin/src/rules/prefer-interface.ts +++ b/packages/eslint-plugin/src/rules/prefer-interface.ts @@ -19,7 +19,7 @@ export default util.createRule({ }, schema: [], deprecated: true, - replacedBy: 'consistent-type-definisions', + replacedBy: ['consistent-type-definisions'], }, defaultOptions: [], create(context) { diff --git a/packages/eslint-plugin/typings/ts-eslint.d.ts b/packages/eslint-plugin/typings/ts-eslint.d.ts index 83c58d548a6..a197e50c2a1 100644 --- a/packages/eslint-plugin/typings/ts-eslint.d.ts +++ b/packages/eslint-plugin/typings/ts-eslint.d.ts @@ -262,7 +262,7 @@ declare module 'ts-eslint' { /** * The name of the rule this rule was replaced by, if it was deprecated. */ - replacedBy?: string; + replacedBy?: string[]; /** * The options schema. Supply an empty array if there are no options. */ From 0427c5884c0e2a64816d5877c662087dbb339e04 Mon Sep 17 00:00:00 2001 From: otofu-square Date: Tue, 30 Apr 2019 23:10:05 +0900 Subject: [PATCH 04/15] refactor(eslint-plugin): modify tests to check fixer removes semi --- .../tests/rules/consistent-type-definitions.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts index 831faea22e1..845b6dedd45 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts @@ -20,7 +20,7 @@ type Record = { ], invalid: [ { - code: `type T = { x: number; }`, + code: `type T = { x: number; };`, output: `interface T { x: number; }`, errors: [ { @@ -31,7 +31,7 @@ type Record = { ], }, { - code: `type T={ x: number; }`, + code: `type T={ x: number; };`, output: `interface T { x: number; }`, errors: [ { @@ -42,7 +42,7 @@ type Record = { ], }, { - code: `type T= { x: number; }`, + code: `type T= { x: number; };`, output: `interface T { x: number; }`, errors: [ { From 3a03995376db36a3f71ef0a6a6e2acdcd2a0ef42 Mon Sep 17 00:00:00 2001 From: otofu-square Date: Thu, 9 May 2019 00:00:24 +0900 Subject: [PATCH 05/15] fix(eslint-plugin): fix typos --- ...ent-type-definisions.ts => consistent-type-definitions.ts} | 4 ++-- packages/eslint-plugin/src/rules/prefer-interface.ts | 2 +- .../tests/rules/consistent-type-definitions.test.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename packages/eslint-plugin/src/rules/{consistent-type-definisions.ts => consistent-type-definitions.ts} (97%) diff --git a/packages/eslint-plugin/src/rules/consistent-type-definisions.ts b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts similarity index 97% rename from packages/eslint-plugin/src/rules/consistent-type-definisions.ts rename to packages/eslint-plugin/src/rules/consistent-type-definitions.ts index 2547718f352..e9a4a55c943 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-definisions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts @@ -3,7 +3,7 @@ import { RuleFix } from 'ts-eslint'; import * as util from '../util'; export default util.createRule({ - name: 'consistent-type-definisions', + name: 'consistent-type-definitions', meta: { type: 'suggestion', docs: { @@ -11,7 +11,7 @@ export default util.createRule({ 'Consistent with type definition either `interface` or `type`', category: 'Stylistic Issues', recommended: 'error', - tslintName: 'consistent-type-definisions', + tslintName: 'consistent-type-definitions', }, messages: { interfaceOverType: 'Use an `interface` instead of a `type`', diff --git a/packages/eslint-plugin/src/rules/prefer-interface.ts b/packages/eslint-plugin/src/rules/prefer-interface.ts index 9b601fc0066..ee995b34809 100644 --- a/packages/eslint-plugin/src/rules/prefer-interface.ts +++ b/packages/eslint-plugin/src/rules/prefer-interface.ts @@ -19,7 +19,7 @@ export default util.createRule({ }, schema: [], deprecated: true, - replacedBy: ['consistent-type-definisions'], + replacedBy: ['consistent-type-definitions'], }, defaultOptions: [], create(context) { diff --git a/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts index 845b6dedd45..7c2e300f6c4 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts @@ -1,11 +1,11 @@ -import rule from '../../src/rules/consistent-type-definisions'; +import rule from '../../src/rules/consistent-type-definitions'; import { RuleTester } from '../RuleTester'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', }); -ruleTester.run('consistent-type-definisions', rule, { +ruleTester.run('consistent-type-definitions', rule, { valid: [ `var foo = { };`, `interface A {}`, From f843fa85b00cf0ab79b5a308441cd69c64a012c7 Mon Sep 17 00:00:00 2001 From: otofu-square Date: Thu, 9 May 2019 00:03:14 +0900 Subject: [PATCH 06/15] fix(eslint-plugin): convert arrow function to method --- packages/eslint-plugin/src/rules/consistent-type-definitions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts index e9a4a55c943..41e5bc384ff 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts @@ -66,7 +66,7 @@ export default util.createRule({ }); } }, - TSInterfaceDeclaration: node => { + TSInterfaceDeclaration(node) { if (option === 'type') { context.report({ node: node.id, From 0e5d28d3f278411718b34c8db67e230d3bb2d542 Mon Sep 17 00:00:00 2001 From: otofu-square Date: Thu, 9 May 2019 00:13:33 +0900 Subject: [PATCH 07/15] fix(eslint-plugin): improve test for consistent-type-definitions --- .../rules/consistent-type-definitions.test.ts | 66 +++++++++++++++++-- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts index 7c2e300f6c4..b5daf40a90e 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts @@ -7,21 +7,72 @@ const ruleTester = new RuleTester({ ruleTester.run('consistent-type-definitions', rule, { valid: [ - `var foo = { };`, - `interface A {}`, - `interface A extends B { x: number; }`, - `type U = string;`, - `type V = { x: number; } | { y: string; };`, - ` + { + code: `var foo = { };`, + options: ['interface'], + }, + { + code: `interface A {}`, + options: ['interface'], + }, + { + code: `interface A extends B { x: number; }`, + options: ['interface'], + }, + { + code: `type U = string;`, + options: ['interface'], + }, + { + code: `type V = { x: number; } | { y: string; };`, + options: ['interface'], + }, + { + code: ` type Record = { [K in T]: U; } `, + options: ['interface'], + }, + { + code: `type V = { x: number; } | { y: string; };`, + options: ['interface'], + }, + { + code: `type T = { x: number; }`, + options: ['type'], + }, + { + code: `type T = { x: number; }`, + options: ['type'], + }, + { + code: `type T = { x: number; }`, + options: ['type'], + }, + { + code: `type A = { x: number; } & B & C;`, + options: ['type'], + }, + { + code: `type A = { x: number; } & B & C;`, + options: ['type'], + }, + { + code: ` +export type W = { + x: T, +}; +`, + options: ['type'], + }, ], invalid: [ { code: `type T = { x: number; };`, output: `interface T { x: number; }`, + options: ['interface'], errors: [ { messageId: 'interfaceOverType', @@ -33,6 +84,7 @@ type Record = { { code: `type T={ x: number; };`, output: `interface T { x: number; }`, + options: ['interface'], errors: [ { messageId: 'interfaceOverType', @@ -44,6 +96,7 @@ type Record = { { code: `type T= { x: number; };`, output: `interface T { x: number; }`, + options: ['interface'], errors: [ { messageId: 'interfaceOverType', @@ -63,6 +116,7 @@ export interface W { x: T, } `, + options: ['interface'], errors: [ { messageId: 'interfaceOverType', From 177973f8ec78c3da6badacfab8a4c81b70ba3ed9 Mon Sep 17 00:00:00 2001 From: otofu-square Date: Thu, 9 May 2019 23:11:26 +0900 Subject: [PATCH 08/15] docs(eslint-plugin): write a doc for consistent-type-definitions --- .../docs/rules/consistent-type-definitions.md | 78 +++++++++++++++++++ .../docs/rules/prefer-interface.md | 34 -------- 2 files changed, 78 insertions(+), 34 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/consistent-type-definitions.md delete mode 100644 packages/eslint-plugin/docs/rules/prefer-interface.md diff --git a/packages/eslint-plugin/docs/rules/consistent-type-definitions.md b/packages/eslint-plugin/docs/rules/consistent-type-definitions.md new file mode 100644 index 00000000000..2669d72323c --- /dev/null +++ b/packages/eslint-plugin/docs/rules/consistent-type-definitions.md @@ -0,0 +1,78 @@ +# Consistent with type definition either `interface` or `type` (consistent-type-definitions) + +There are two ways to define a type. + +```js +// type alias +type T1 = { + a: string, + b: number, +}; + +// interface +interface T2 { + a: string; + b: number; +} +``` + +## Rule Details + +Examples of **incorrect** code with `interface` option. + +```ts +type T = { x: number }; +``` + +Examples of **correct** code with `interface` option. + +```ts +type T = string; +type Foo = string | {}; + +interface T { + x: number; +} +``` + +Examples of **incorrect** code with `type` option. + +```ts +interface T { + x: number; +} +``` + +Examples of **correct** code with `interface` option. + +```ts +type T = { x: number }; +``` + +## Options + +This rule has two options: + +```CJSON +{ + // Consistent with type definition by `interface` + "@typescript-eslint/ban-types": ["error", "interface"] +} +``` + +Or for tabbed indentation: + +```CJSON +{ + // Consistent with type definition by `type` + "@typescript-eslint/ban-types": ["error", "type"] +} +``` + +## When Not To Use It + +If you specifically want to use an interface or type literal for stylistic reasons, you can disable this rule. + +## Compatibility + +- TSLint: [interface-over-type-literal](https://palantir.github.io/tslint/rules/interface-over-type-literal/) diff --git a/packages/eslint-plugin/docs/rules/prefer-interface.md b/packages/eslint-plugin/docs/rules/prefer-interface.md deleted file mode 100644 index 39c8ca24321..00000000000 --- a/packages/eslint-plugin/docs/rules/prefer-interface.md +++ /dev/null @@ -1,34 +0,0 @@ -# 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. - -## 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/) From 4ebaf2246ba5f0a71a746a0d7a4f5b1f167a1612 Mon Sep 17 00:00:00 2001 From: otofu-square Date: Thu, 9 May 2019 23:19:52 +0900 Subject: [PATCH 09/15] docs(eslint-plugin): fix docs --- .../eslint-plugin/docs/rules/consistent-type-definitions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/consistent-type-definitions.md b/packages/eslint-plugin/docs/rules/consistent-type-definitions.md index 2669d72323c..22b2009cb4c 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-definitions.md +++ b/packages/eslint-plugin/docs/rules/consistent-type-definitions.md @@ -43,7 +43,7 @@ interface T { } ``` -Examples of **correct** code with `interface` option. +Examples of **correct** code with `type` option. ```ts type T = { x: number }; @@ -56,7 +56,7 @@ This rule has two options: ```CJSON { // Consistent with type definition by `interface` - "@typescript-eslint/ban-types": ["error", "interface"] + "@typescript-eslint/consistent-type-definitions": ["error", "interface"] } ``` @@ -65,7 +65,7 @@ Or for tabbed indentation: ```CJSON { // Consistent with type definition by `type` - "@typescript-eslint/ban-types": ["error", "type"] + "@typescript-eslint/consistent-type-definitions": ["error", "type"] } ``` From b0d705b89cbf95b2774ce56559890fbc60d9e14d Mon Sep 17 00:00:00 2001 From: otofu-square Date: Fri, 10 May 2019 10:45:27 +0900 Subject: [PATCH 10/15] docs(eslint-plugin): improve consistent-type-definitions doc --- .../docs/rules/consistent-type-definitions.md | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/consistent-type-definitions.md b/packages/eslint-plugin/docs/rules/consistent-type-definitions.md index 22b2009cb4c..c9a2a7773a2 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-definitions.md +++ b/packages/eslint-plugin/docs/rules/consistent-type-definitions.md @@ -2,20 +2,36 @@ There are two ways to define a type. -```js +```ts // type alias type T1 = { - a: string, - b: number, + a: string; + b: number; }; -// interface +// interface keyword interface T2 { a: string; b: number; } ``` +### Options + +This rule accepts one string option: + +- `"interface"`: enforce using `interface`s for object type definitions. +- `"type"`: enforce using `type`s for object type definitions. + +For example: + +```CJSON +{ + // Use type for object definitions + "@typescript-eslint/consistent-type-definitions": ["error", "type"] +} +``` + ## Rule Details Examples of **incorrect** code with `interface` option. @@ -49,26 +65,6 @@ Examples of **correct** code with `type` option. type T = { x: number }; ``` -## Options - -This rule has two options: - -```CJSON -{ - // Consistent with type definition by `interface` - "@typescript-eslint/consistent-type-definitions": ["error", "interface"] -} -``` - -Or for tabbed indentation: - -```CJSON -{ - // Consistent with type definition by `type` - "@typescript-eslint/consistent-type-definitions": ["error", "type"] -} -``` - ## When Not To Use It If you specifically want to use an interface or type literal for stylistic reasons, you can disable this rule. From 8443aa4bfbd21e6c3e9e4b233c4771404ea1f7b8 Mon Sep 17 00:00:00 2001 From: otofu-square Date: Mon, 17 Jun 2019 16:40:05 +0900 Subject: [PATCH 11/15] fix(eslint-plugin): use typings in @typescript-eslint/experimental-utils --- .../eslint-plugin/src/rules/consistent-type-definitions.ts | 7 +++---- packages/experimental-utils/src/ts-eslint/Rule.ts | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts index 41e5bc384ff..26fe3ca33e8 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts @@ -1,5 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; -import { RuleFix } from 'ts-eslint'; +import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; export default util.createRule({ @@ -39,7 +38,7 @@ export default util.createRule({ messageId: 'interfaceOverType', fix(fixer) { const typeNode = node.typeParameters || node.id; - const fixes: RuleFix[] = []; + const fixes: TSESLint.RuleFix[] = []; const firstToken = sourceCode.getFirstToken(node); if (firstToken) { @@ -73,7 +72,7 @@ export default util.createRule({ messageId: 'typeOverInterface', fix(fixer) { const typeNode = node.typeParameters || node.id; - const fixes: RuleFix[] = []; + const fixes: TSESLint.RuleFix[] = []; const firstToken = sourceCode.getFirstToken(node); if (firstToken) { diff --git a/packages/experimental-utils/src/ts-eslint/Rule.ts b/packages/experimental-utils/src/ts-eslint/Rule.ts index 388f64e99fc..2759a3d33d2 100644 --- a/packages/experimental-utils/src/ts-eslint/Rule.ts +++ b/packages/experimental-utils/src/ts-eslint/Rule.ts @@ -62,7 +62,7 @@ interface RuleMetaData { /** * The name of the rule this rule was replaced by, if it was deprecated. */ - replacedBy?: string; + replacedBy?: string[]; /** * The options schema. Supply an empty array if there are no options. */ From 9ba3a902ab64e0846e4e6675bfe3968dafe00731 Mon Sep 17 00:00:00 2001 From: otofu-square Date: Tue, 18 Jun 2019 09:08:56 +0900 Subject: [PATCH 12/15] fix(eslint-plugin): remove unnecessary `tslintName` property --- packages/eslint-plugin/src/rules/consistent-type-definitions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts index 26fe3ca33e8..d8d9bb675a2 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts @@ -10,7 +10,6 @@ export default util.createRule({ 'Consistent with type definition either `interface` or `type`', category: 'Stylistic Issues', recommended: 'error', - tslintName: 'consistent-type-definitions', }, messages: { interfaceOverType: 'Use an `interface` instead of a `type`', From 97ecab432ead8d1bd21c423f4b71ea8c9dc667a6 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 20 Jun 2019 09:39:02 -0700 Subject: [PATCH 13/15] docs: ixed documentation and checker for deprecated rules --- packages/eslint-plugin/README.md | 2 +- .../docs/rules/consistent-type-definitions.md | 2 +- .../docs/rules/prefer-interface.md | 36 +++++++++++++++++++ packages/eslint-plugin/src/rules/index.ts | 2 ++ .../validate-docs/check-for-rule-docs.ts | 4 +-- .../validate-docs/validate-table-rules.ts | 4 +-- .../validate-docs/validate-table-structure.ts | 8 +++-- 7 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/prefer-interface.md diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 8fa404c982f..e9df4f0faa3 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -131,7 +131,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Enforces that types will not to be used | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | | | [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names | :heavy_check_mark: | | | -| [`@typescript-eslint/consistent-type-definitions`](./docs/rules/consistent-type-definitions.md) | Consistent with type definition either `type` or `interface` | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/consistent-type-definitions`](./docs/rules/consistent-type-definitions.md) | Consistent with type definition either `interface` or `type` | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) | Require explicit return types on functions and class methods | :heavy_check_mark: | | | | [`@typescript-eslint/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods | :heavy_check_mark: | | | | [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/consistent-type-definitions.md b/packages/eslint-plugin/docs/rules/consistent-type-definitions.md index c9a2a7773a2..3e60f2ced7c 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-definitions.md +++ b/packages/eslint-plugin/docs/rules/consistent-type-definitions.md @@ -16,7 +16,7 @@ interface T2 { } ``` -### Options +## Options This rule accepts one string option: diff --git a/packages/eslint-plugin/docs/rules/prefer-interface.md b/packages/eslint-plugin/docs/rules/prefer-interface.md new file mode 100644 index 00000000000..8b995e6b06c --- /dev/null +++ b/packages/eslint-plugin/docs/rules/prefer-interface.md @@ -0,0 +1,36 @@ +# 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. + +## DEPRECATED - this rule has been deprecated in favour of [`consistent-type-definitions`](./consistent-type-definitions.md) + +## 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/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index c92f6a36d8b..c2a18f7db0f 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -5,6 +5,7 @@ import banTsIgnore from './ban-ts-ignore'; import banTypes from './ban-types'; import camelcase from './camelcase'; import classNameCasing from './class-name-casing'; +import consistentTypeDefinitions from './consistent-type-definitions'; import explicitFunctionReturnType from './explicit-function-return-type'; import explicitMemberAccessibility from './explicit-member-accessibility'; import funcCallSpacing from './func-call-spacing'; @@ -62,6 +63,7 @@ export default { 'ban-types': banTypes, camelcase: camelcase, 'class-name-casing': classNameCasing, + 'consistent-type-definitions': consistentTypeDefinitions, 'explicit-function-return-type': explicitFunctionReturnType, 'explicit-member-accessibility': explicitMemberAccessibility, 'func-call-spacing': funcCallSpacing, diff --git a/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts b/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts index 6012d46a3f1..eeb67c9d528 100644 --- a/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts +++ b/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts @@ -3,8 +3,8 @@ import fs from 'fs'; import path from 'path'; import { logRule } from './log'; -function checkForRuleDocs( - rules: Record>, +function checkForRuleDocs( + rules: Record>>, ): boolean { const ruleDocs = new Set( fs.readdirSync(path.resolve(__dirname, '../../docs/rules')), diff --git a/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts b/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts index 5268bc67556..1ab532e3160 100644 --- a/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts +++ b/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts @@ -5,8 +5,8 @@ import marked from 'marked'; import path from 'path'; import { logRule } from './log'; -function validateTableRules( - rules: Record>, +function validateTableRules( + rules: Record>>, rulesTable: marked.Tokens.Table, ): boolean { let hasErrors = false; diff --git a/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts b/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts index 4b5bb4151f4..02da5a66151 100644 --- a/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts +++ b/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts @@ -3,11 +3,13 @@ import chalk from 'chalk'; import marked from 'marked'; import { logError } from './log'; -function validateTableStructure( - rules: Record>, +function validateTableStructure( + rules: Record>>, rulesTable: marked.Tokens.Table, ): boolean { - const ruleNames = Object.keys(rules).sort(); + const ruleNames = Object.keys(rules) + .filter(ruleName => rules[ruleName].meta.deprecated !== true) + .sort(); let hasErrors = false; rulesTable.cells.forEach((row, rowIndex) => { From 83da20ee1975df4d164e589ef5bf4d0a13150804 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 20 Jun 2019 10:03:42 -0700 Subject: [PATCH 14/15] Update all.json --- packages/eslint-plugin/src/configs/all.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index cc2f9778671..2b3d838d6dd 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -9,6 +9,7 @@ "camelcase": "off", "@typescript-eslint/camelcase": "error", "@typescript-eslint/class-name-casing": "error", + "@typescript-eslint/consistent-type-definitions", "@typescript-eslint/explicit-function-return-type": "error", "@typescript-eslint/explicit-member-accessibility": "error", "func-call-spacing": "off", @@ -23,11 +24,13 @@ "@typescript-eslint/no-angle-bracket-type-assertion": "error", "no-array-constructor": "off", "@typescript-eslint/no-array-constructor": "error", + "@typescript-eslint/no-empty-function", "@typescript-eslint/no-empty-interface": "error", "@typescript-eslint/no-explicit-any": "error", "no-extra-parens": "off", "@typescript-eslint/no-extra-parens": "error", "@typescript-eslint/no-extraneous-class": "error", + "@typescript-eslint/no-floating-promises", "@typescript-eslint/no-for-in-array": "error", "@typescript-eslint/no-inferrable-types": "error", "no-magic-numbers": "off", @@ -53,7 +56,6 @@ "@typescript-eslint/prefer-for-of": "error", "@typescript-eslint/prefer-function-type": "error", "@typescript-eslint/prefer-includes": "error", - "@typescript-eslint/prefer-interface": "error", "@typescript-eslint/prefer-namespace-keyword": "error", "@typescript-eslint/prefer-regexp-exec": "error", "@typescript-eslint/prefer-string-starts-ends-with": "error", From 16abfd5997611df4cb04930a07da35af806898b0 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 20 Jun 2019 10:04:14 -0700 Subject: [PATCH 15/15] Update all.json --- packages/eslint-plugin/src/configs/all.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index 2b3d838d6dd..2efc8d24323 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -9,7 +9,7 @@ "camelcase": "off", "@typescript-eslint/camelcase": "error", "@typescript-eslint/class-name-casing": "error", - "@typescript-eslint/consistent-type-definitions", + "@typescript-eslint/consistent-type-definitions": "error", "@typescript-eslint/explicit-function-return-type": "error", "@typescript-eslint/explicit-member-accessibility": "error", "func-call-spacing": "off", @@ -24,13 +24,13 @@ "@typescript-eslint/no-angle-bracket-type-assertion": "error", "no-array-constructor": "off", "@typescript-eslint/no-array-constructor": "error", - "@typescript-eslint/no-empty-function", + "@typescript-eslint/no-empty-function": "error", "@typescript-eslint/no-empty-interface": "error", "@typescript-eslint/no-explicit-any": "error", "no-extra-parens": "off", "@typescript-eslint/no-extra-parens": "error", "@typescript-eslint/no-extraneous-class": "error", - "@typescript-eslint/no-floating-promises", + "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-for-in-array": "error", "@typescript-eslint/no-inferrable-types": "error", "no-magic-numbers": "off",