Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-plugin): add consistent-type-definitions rule #463

Merged
merged 21 commits into from Jun 20, 2019
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7448441
feat(eslint-plugin): add consistent-type-definitions rule
otofu-square Apr 26, 2019
770b656
fix(eslint-plugin): deprecate prefer-interface rule
otofu-square Apr 27, 2019
7c26569
fix(eslint-plugin): fix wrong typing for `replacedBy` in meta
otofu-square Apr 27, 2019
0427c58
refactor(eslint-plugin): modify tests to check fixer removes semi
otofu-square Apr 30, 2019
2f5fdea
Merge branch 'master' into prefer-type-alias
otofu-square May 8, 2019
3a03995
fix(eslint-plugin): fix typos
otofu-square May 8, 2019
f843fa8
fix(eslint-plugin): convert arrow function to method
otofu-square May 8, 2019
0e5d28d
fix(eslint-plugin): improve test for consistent-type-definitions
otofu-square May 8, 2019
533c4a3
Merge branch 'master' into prefer-type-alias
otofu-square May 9, 2019
177973f
docs(eslint-plugin): write a doc for consistent-type-definitions
otofu-square May 9, 2019
4ebaf22
docs(eslint-plugin): fix docs
otofu-square May 9, 2019
ce3f4e0
Merge branch 'master' into prefer-type-alias
otofu-square May 10, 2019
b0d705b
docs(eslint-plugin): improve consistent-type-definitions doc
otofu-square May 10, 2019
9908a89
Merge branch 'master' into prefer-type-alias
otofu-square Jun 17, 2019
8443aa4
fix(eslint-plugin): use typings in @typescript-eslint/experimental-utils
otofu-square Jun 17, 2019
9ba3a90
fix(eslint-plugin): remove unnecessary `tslintName` property
otofu-square Jun 18, 2019
5d619ad
Merge branch 'master' into prefer-type-alias
otofu-square Jun 18, 2019
97ecab4
docs: ixed documentation and checker for deprecated rules
bradzacher Jun 20, 2019
83da20e
Update all.json
bradzacher Jun 20, 2019
16abfd5
Update all.json
bradzacher Jun 20, 2019
838b487
Merge branch 'master' into prefer-type-alias
bradzacher Jun 20, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/eslint-plugin/README.md
Expand Up @@ -131,6 +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 `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: | |
Expand Down Expand Up @@ -168,7 +169,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 | | :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 = { ... }) | :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 | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/prefer-regexp-exec`](./docs/rules/prefer-regexp-exec.md) | Prefer RegExp#exec() over String#match() if no global flag is provided | | | :thought_balloon: |
| [`@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: | :thought_balloon: |
Expand Down
74 changes: 74 additions & 0 deletions packages/eslint-plugin/docs/rules/consistent-type-definitions.md
@@ -0,0 +1,74 @@
# Consistent with type definition either `interface` or `type` (consistent-type-definitions)

There are two ways to define a type.

```ts
// type alias
type T1 = {
a: string;
b: number;
};

// 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.

```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 `type` option.

```ts
type T = { x: number };
```

## 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/)
4 changes: 3 additions & 1 deletion packages/eslint-plugin/docs/rules/prefer-interface.md
@@ -1,7 +1,9 @@
# Prefer an interface declaration over a type literal (type T = { ... }) (prefer-interface)
# 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.
Expand Down
4 changes: 3 additions & 1 deletion packages/eslint-plugin/src/configs/all.json
Expand Up @@ -9,6 +9,7 @@
"camelcase": "off",
"@typescript-eslint/camelcase": "error",
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/explicit-member-accessibility": "error",
"func-call-spacing": "off",
Expand All @@ -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": "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": "error",
"@typescript-eslint/no-for-in-array": "error",
"@typescript-eslint/no-inferrable-types": "error",
"no-magic-numbers": "off",
Expand All @@ -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",
Expand Down
103 changes: 103 additions & 0 deletions packages/eslint-plugin/src/rules/consistent-type-definitions.ts
@@ -0,0 +1,103 @@
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
import * as util from '../util';

export default util.createRule({
name: 'consistent-type-definitions',
meta: {
type: 'suggestion',
docs: {
description:
'Consistent with type definition either `interface` or `type`',
category: 'Stylistic Issues',
recommended: 'error',
},
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: TSESLint.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: TSESLint.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;
},
});
}
},
};
},
});
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/rules/index.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/rules/prefer-interface.ts
Expand Up @@ -16,6 +16,8 @@ export default util.createRule({
interfaceOverType: 'Use an interface instead of a type literal.',
},
schema: [],
deprecated: true,
replacedBy: ['consistent-type-definitions'],
},
defaultOptions: [],
create(context) {
Expand Down